Fix faction ammunition resupply and settings recovery
Wait for both convoy cargo phases and use actual magazine stock. Resolve empty primary weapon ammunition from muzzle metadata and recover cleanly from failed initial settings saves. Update native fixtures, scoped cleanup guards, packaging evidence and validation documentation. Make reviewed fixture tests checkout-relative. All repository checks pass against the isolated staged tree; native resupply completion and multiplayer qualification remain outstanding.
This commit is contained in:
@@ -53,3 +53,59 @@ int main(){
|
||||
}
|
||||
''')
|
||||
print('PASS: air logistics reservation preserves delivered search until radio handoff, excludes guards/combat/in-flight orders, and quarantines unresolved native loads/moves')
|
||||
|
||||
# A rifle with its last magazine removed must still identify supply cargo.
|
||||
# Execute the real selector with native weapon/muzzle/catalog calls doubled.
|
||||
import os
|
||||
from pathlib import Path
|
||||
selector_source=Path(os.environ.get('ERR_AIR_AMMO_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirAmmoMission.c'))
|
||||
selector=body(selector_source,'protected ResourceName Magazine(')
|
||||
selector=selector.replace('protected ','').replace('ResourceName','std::string').replace('IEntity actor','Actor* actor').replace('IEntity owner = null;','Item* owner=nullptr;')
|
||||
selector=selector.replace('BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent));','Manager* manager=actor->manager;')
|
||||
selector=selector.replace('array<BaseWeaponComponent>','std::vector<Weapon*>').replace('array<BaseMuzzleComponent>','std::vector<Muzzle*>')
|
||||
selector=selector.replace('foreach (','for (').replace('BaseWeaponComponent weapon','Weapon* weapon').replace('BaseMuzzleComponent muzzle','Muzzle* muzzle').replace('BaseMagazineComponent magazine','LoadedMagazine* magazine')
|
||||
selector=selector.replace('EWeaponType.','EWeaponType::').replace('EMuzzleType.','EMuzzleType::').replace('.IsEmpty()','.empty()')
|
||||
for name in ('manager','weapon','muzzle','magazine','owner'):selector=selector.replace(name+'.',name+'->')
|
||||
selector=selector.replace('GetOwner().','GetOwner()->').replace('GetPrefabData().','GetPrefabData()->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
enum class EWeaponType{WT_RIFLE,WT_MACHINEGUN,WT_SNIPERRIFLE,WT_LAUNCHER};
|
||||
enum class EMuzzleType{MT_BaseMuzzle,MT_UGLMuzzle};
|
||||
struct Prefab {std::string name="loaded-custom-mag";std::string GetPrefabName(){return name;}};
|
||||
struct Item {Prefab prefab;bool valid=true;Prefab* GetPrefabData(){return valid?&prefab:nullptr;}};
|
||||
struct LoadedMagazine {Item* owner=nullptr;Item* GetOwner(){return owner;}};
|
||||
struct Muzzle {LoadedMagazine* loaded=nullptr;std::string fallback="default-rifle-mag";EMuzzleType type=EMuzzleType::MT_BaseMuzzle;bool well=true;
|
||||
LoadedMagazine* GetMagazine(){return loaded;}bool GetMagazineWell(){return well;}EMuzzleType GetMuzzleType(){return type;}std::string GetDefaultMagazineOrProjectileName(){return fallback;}};
|
||||
struct Weapon {EWeaponType type=EWeaponType::WT_RIFLE;std::vector<Muzzle*> muzzles;EWeaponType GetWeaponType(){return type;}void GetMuzzlesList(std::vector<Muzzle*>& out){out=muzzles;}};
|
||||
struct Manager {std::vector<Weapon*> weapons;void GetWeapons(std::vector<Weapon*>&out){out=weapons;}};
|
||||
struct Actor {Manager* manager=nullptr;};
|
||||
struct Mission {
|
||||
SELECTOR
|
||||
};
|
||||
int main(){
|
||||
Mission mission;Muzzle rifle;Weapon weapon;weapon.muzzles={&rifle};Manager manager;manager.weapons={&weapon};Actor actor{&manager};
|
||||
// This failed before the fix: no inserted magazine after the final reload.
|
||||
assert(mission.Magazine(&actor)=="default-rifle-mag");
|
||||
Item item;LoadedMagazine loaded{&item};rifle.loaded=&loaded;
|
||||
assert(mission.Magazine(&actor)=="loaded-custom-mag");
|
||||
item.prefab.name="";assert(mission.Magazine(&actor)=="default-rifle-mag");
|
||||
item.valid=false;assert(mission.Magazine(&actor)=="default-rifle-mag");
|
||||
loaded.owner=nullptr;assert(mission.Magazine(&actor)=="default-rifle-mag");
|
||||
// An earlier auxiliary launcher must not choose grenade cargo for a rifle.
|
||||
Item grenade;grenade.prefab.name="grenade";LoadedMagazine grenadeMag{&grenade};
|
||||
Muzzle auxiliary;auxiliary.type=EMuzzleType::MT_UGLMuzzle;auxiliary.loaded=&grenadeMag;
|
||||
weapon.muzzles={nullptr,&auxiliary,&rifle};assert(mission.Magazine(&actor)=="default-rifle-mag");
|
||||
rifle.well=false;assert(mission.Magazine(&actor).empty());rifle.well=true;
|
||||
rifle.loaded=nullptr;rifle.fallback="";assert(mission.Magazine(&actor).empty());
|
||||
Muzzle alternate;alternate.fallback="second-rifle-mag";weapon.muzzles.push_back(&alternate);
|
||||
assert(mission.Magazine(&actor)=="second-rifle-mag");
|
||||
weapon.type=EWeaponType::WT_MACHINEGUN;assert(mission.Magazine(&actor)=="second-rifle-mag");
|
||||
weapon.type=EWeaponType::WT_SNIPERRIFLE;assert(mission.Magazine(&actor)=="second-rifle-mag");
|
||||
weapon.type=EWeaponType::WT_LAUNCHER;assert(mission.Magazine(&actor).empty());
|
||||
manager.weapons={nullptr};assert(mission.Magazine(&actor).empty());
|
||||
actor.manager=nullptr;assert(mission.Magazine(&actor).empty());assert(mission.Magazine(nullptr).empty());
|
||||
}
|
||||
'''.replace('SELECTOR',selector))
|
||||
print('PASS: actual ammunition cargo selector handles empty rifle/machinegun/sniper magazines, preserves loaded variants, excludes auxiliary launchers and tolerates missing native metadata')
|
||||
|
||||
Reference in New Issue
Block a user