Add configurable crash-site salvage; record Workshop 0.0.146
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Random crash-site loot — 2026-11-06
|
||||
|
||||
Random quality is now the default, including profiles without the new key. Each site draws once, ranging from pistol magazines with an optional pistol to scoped rifles, ammunition and launcher rolls. Custom mode retains the previous quantity/chance controls. Blocks still exclude content and can leave a random crate empty.
|
||||
|
||||
Validation:
|
||||
- The production loot planner passed deterministic checks for every quality value, range endpoints, ammunition-only poor rolls, rich rolls and disabled content.
|
||||
- Settings migration/bounds, menu command routing, generated layouts, the prior Custom planner, finite site lifecycle and inventory race regressions passed.
|
||||
- Isolated Workbench compiled the current production addon, checked native poor/rich plans, accepted Random/Custom aliases, verified default Random and saved/reloaded settings, then filled a real randomly planned crate with one rifle and three magazines despite Custom launcher chances of 100%. Smoke, static wreck and menu bounds passed.
|
||||
- All 728 production source/resource files matched the native snapshot, excluding the test project configuration and generated resource database. Cleanup and unchanged source/external inputs were confirmed.
|
||||
|
||||
No script/runtime failures occurred. The runner still returned nonzero for pre-existing external resource GUID, vehicle-field and DLC ownership errors. This was a local Workbench test, not dedicated multiplayer validation. No publication or push was performed.
|
||||
@@ -0,0 +1,174 @@
|
||||
#ifdef WORKBENCH
|
||||
class ERR_CrashProbeSites : ERR_CrashSites
|
||||
{
|
||||
protected int m_Captured;
|
||||
protected int m_ExpectedStock;
|
||||
protected bool m_DestroyedGrant;
|
||||
bool WaitingCorrectly() { return !m_Stopped && m_Attempts==0 && Count()==0; }
|
||||
protected override void Fill()
|
||||
{
|
||||
if(false)
|
||||
{
|
||||
m_DestroyedGrant=true;ERR_Actors.DeleteSpawned(m_Item);
|
||||
Print("ERR CRASH: removed first issued rifle before insertion to test spent-grant handling");
|
||||
}
|
||||
super.Fill();
|
||||
}
|
||||
void ProofCamera()
|
||||
{
|
||||
vector point=At(0).Position;
|
||||
vector camera[4];vector eye=point+"10 12 -16";
|
||||
Math3D.DirectionAndUpMatrix(vector.Direction(eye,point+"0 2 0"),"0 1 0",camera);camera[3]=eye;
|
||||
GetGame().GetCameraManager().SetCamera(m_Camera);
|
||||
m_Camera.SetWorldTransform(camera);m_Camera.SetFOVDegree(65);m_Camera.SetDirty(true);m_Camera.ApplyTransform(0);
|
||||
}
|
||||
void ReportCamera() { Print("ERR CONTENT STAGE: 2");System.MakeScreenshot("$logs:crash-helicopter"); }
|
||||
|
||||
protected SCR_ManualCamera m_Camera;
|
||||
void ERR_CrashProbeSites(ERR_GameMode mode) { m_Mode = mode; }
|
||||
protected override bool Ready() { return true; }
|
||||
protected override bool Survey(out vector point, out float heading)
|
||||
{
|
||||
if(Count()==0 && m_Attempts==1)
|
||||
{
|
||||
point="11180.619 0 11534.291";heading=0;float floor;
|
||||
if(Separated(point) && m_Terrain.Clear(point,heading,11,0.6,floor,5)) {point=ERR_Actors.Ground(point);return true;}
|
||||
}
|
||||
return super.Survey(point,heading);
|
||||
}
|
||||
protected override bool Create(vector point, float heading)
|
||||
{
|
||||
m_HeliChance = 100;
|
||||
|
||||
bool created=super.Create(point, heading);
|
||||
m_ExpectedStock=m_Loot.Count();
|
||||
return created;
|
||||
}
|
||||
void Begin()
|
||||
{
|
||||
if(m_Mode.Settings().Get("crashLootMode")!=1){Fail("random mode not default");return;}
|
||||
array<ResourceName> planned={};
|
||||
foreach(int quality:{0,29,30,64,65,89,90,99})
|
||||
{
|
||||
ERR_CrashLoot.QualityPlan(quality,planned);
|
||||
int count=planned.Count();
|
||||
bool valid=count>=20 && count<=58;
|
||||
if(quality<30)valid=count>=1 && count<=4;
|
||||
else if(quality<65)valid=count>=2 && count<=4;
|
||||
else if(quality<90)valid=count>=6 && count<=22;
|
||||
if(!valid){Fail("quality plan boundaries");return;}
|
||||
}
|
||||
int selected;
|
||||
if(!ERR_CommandArguments.Selection("crashLootMode","random",selected) || selected!=1){Fail("random command");return;}
|
||||
if(!ERR_CommandArguments.Selection("crashLootMode","custom",selected) || selected!=0){Fail("custom command");return;}
|
||||
m_Mode.Settings().Set("crashLootMode",1);
|
||||
Print("ERR SMOKE PASS: default random, native poor-to-rich plans and mode command aliases");
|
||||
m_Mode.Settings().Set("crashSiteCount",1);
|
||||
m_Mode.Settings().Set("crashRifleCount",3); m_Mode.Settings().Set("crashMagazineCount",4);
|
||||
m_Mode.Settings().Set("crashAntiAirChancePercent",100); m_Mode.Settings().Set("crashRocketChancePercent",100);
|
||||
Start();
|
||||
GetGame().GetCallqueue().CallLater(Check,1000,true);
|
||||
}
|
||||
void Fail(string text)
|
||||
{
|
||||
Print("ERR SMOKE FAIL: crash sites " + text,LogLevel.ERROR);
|
||||
GetGame().GetCallqueue().Remove(Check); Stop();
|
||||
}
|
||||
void Check()
|
||||
{
|
||||
PrintFormat("ERR CRASH PROGRESS: count=%1 captured=%2 stopped=%3 consumed=%4 attempts=%5 item=%6",Count(),m_Captured,m_Stopped,m_Consumed,m_Attempts,m_ItemIndex);
|
||||
Print("ERR CRASH TERRAIN: " + m_Terrain.Rejection());
|
||||
if(m_Skipped>0){Fail("unexpected skipped slot");return;}
|
||||
if(Count()<=m_Captured || !At(m_Captured).Ready)return;
|
||||
ERR_CrashSite site=At(m_Captured);
|
||||
if(!site.Wreck || Vehicle.Cast(site.Wreck) || site.Wreck.FindComponent(VehicleControllerComponent)){Fail("repairable vehicle");return;}
|
||||
vector low, high;site.Wreck.GetBounds(low,high);
|
||||
float bottom=site.Wreck.GetOrigin()[1]+low[1];
|
||||
if(Math.AbsFloat(bottom-site.Position[1])>0.02){Fail("helicopter ground contact");return;}
|
||||
ParticleEffectEntity smoke=ParticleEffectEntity.Cast(site.Smoke);
|
||||
if(!smoke || !smoke.HasActiveParticles()){Fail("inactive smoke");return;}
|
||||
if(site.Stock.Count()!=m_ExpectedStock){Fail("maximum crate stock count="+site.Stock.Count().ToString());return;}
|
||||
int rifles, mags, rockets, aa;
|
||||
foreach(IEntity item:site.Stock)
|
||||
{
|
||||
string path=item.GetPrefabData().GetPrefabName();
|
||||
if(path.Contains("Rifle_"))rifles++;
|
||||
if(path.Contains("Magazine_"))mags++;
|
||||
if(path.Contains("Ammo_Rocket"))rockets++;
|
||||
if(path.Contains("FIM92_Stinger.et"))
|
||||
{
|
||||
aa++;if(!ERR_EnemyLoadoutCatalog.Loaded(item)){Fail("unloaded AA");return;}
|
||||
}
|
||||
}
|
||||
if(m_LootMode!=1 || m_ExpectedStock<1 || m_ExpectedStock>58){Fail("random loot dispatch");return;}
|
||||
PrintFormat("ERR SMOKE PASS: crash %1 random crate: rifles=%2 magazines=%3 rockets=%4 AA=%5 static wreck and active smoke",m_Captured,rifles,mags,rockets,aa);
|
||||
if(!m_Camera)
|
||||
{
|
||||
EntitySpawnParams params=new EntitySpawnParams();
|
||||
m_Camera=SCR_ManualCamera.Cast(GetGame().SpawnEntity(SCR_ManualCamera,GetGame().GetWorld(),params));
|
||||
GetGame().GetCameraManager().SetCamera(m_Camera);
|
||||
}
|
||||
vector camera[4];vector eye=site.Position+"18 10 22";
|
||||
Math3D.DirectionAndUpMatrix(vector.Direction(eye,site.Position+"0 2 0"),"0 1 0",camera);camera[3]=eye;m_Camera.SetWorldTransform(camera);m_Camera.SetVerticalFOV(65);
|
||||
GetGame().GetCallqueue().CallLater(ProofCamera,100,true);
|
||||
GetGame().GetCallqueue().CallLater(ReportCamera,2000,false);
|
||||
m_Captured++;
|
||||
if(m_Captured==1)GetGame().GetCallqueue().CallLater(Done,5000,false);
|
||||
}
|
||||
void Done()
|
||||
{
|
||||
GetGame().GetCallqueue().Remove(Check);
|
||||
GetGame().GetCallqueue().Remove(ProofCamera);
|
||||
if(Count()!=1 || !m_Stopped){Fail("finite completion");return;}
|
||||
ERR_AdminMenu.Open();
|
||||
GetGame().GetCallqueue().CallLater(MenuDone,5000,false);
|
||||
}
|
||||
void MenuDone()
|
||||
{
|
||||
Print("ERR SMOKE COMPLETE: random crash-site loot");
|
||||
}
|
||||
}
|
||||
modded class SCR_RespawnSystemComponent
|
||||
{
|
||||
override void UpdateLoadingPlaceholder(float dt)
|
||||
{
|
||||
super.UpdateLoadingPlaceholder(dt);
|
||||
if(m_wLoadingPlaceholder)m_wLoadingPlaceholder.SetVisible(false);
|
||||
}
|
||||
}
|
||||
modded class ERR_AdminMenu
|
||||
{
|
||||
override void OnMenuOpen()
|
||||
{
|
||||
super.OnMenuOpen(); ShowPage(ERR_AdminPage.CRASH_SITES);
|
||||
if(!ERR_ProbeNavigation(ERR_AdminPage.CRASH_SITES))Print("ERR SMOKE FAIL: crash menu bounds",LogLevel.ERROR);
|
||||
Print("ERR CONTENT STAGE: 4");
|
||||
}
|
||||
}
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected ref ERR_CrashProbeSites m_CrashProbe;
|
||||
protected bool m_CrashActive;
|
||||
protected override void StartScenario() {}
|
||||
override bool Active() { return m_CrashActive; }
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
if(SCR_Global.IsEditMode() || !Replication.IsServer())return;
|
||||
GetGame().GetCallqueue().CallLater(CrashBegin,7000,false);
|
||||
}
|
||||
void ActivateCrash()
|
||||
{
|
||||
if(!m_CrashProbe.WaitingCorrectly()) {Print("ERR SMOKE FAIL: preparation waiting",LogLevel.ERROR);return;}
|
||||
ERR_Settings loaded=new ERR_Settings();
|
||||
if(loaded.Get("crashLootMode")!=1 || loaded.Get("crashSiteCount")!=1 || loaded.Get("crashRifleCount")!=3 || loaded.Get("crashMagazineCount")!=4) {Print("ERR SMOKE FAIL: persisted crash settings",LogLevel.ERROR);return;}
|
||||
Print("ERR SMOKE PASS: preparation waits, settings saved and reloaded");m_CrashActive=true;
|
||||
}
|
||||
void CrashBegin()
|
||||
{
|
||||
|
||||
m_CrashProbe=new ERR_CrashProbeSites(this);m_CrashProbe.Begin();OnStop().Insert(m_CrashProbe.Stop);
|
||||
GetGame().GetCallqueue().CallLater(ActivateCrash,3000,false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"result": "smoke-completed",
|
||||
"cleanup_confirmed": true,
|
||||
"source_unchanged_after_run": true,
|
||||
"external_addons_unchanged_after_run": true,
|
||||
"assertions": [
|
||||
"23:15:29.194 SCRIPT : ERR SMOKE PASS: default random, native poor-to-rich plans and mode command aliases",
|
||||
"23:15:32.174 SCRIPT : ERR SMOKE PASS: preparation waits, settings saved and reloaded",
|
||||
"23:15:36.202 SCRIPT : ERR SMOKE PASS: crash 0 random crate: rifles=1 magazines=3 rockets=0 AA=0 static wreck and active smoke",
|
||||
"23:15:46.274 SCRIPT : ERR SMOKE COMPLETE: random crash-site loot"
|
||||
],
|
||||
"matching_production_files": 728
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 463 KiB |
Reference in New Issue
Block a user