Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
88 lines
4.6 KiB
Diff
88 lines
4.6 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c
|
|
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c
|
|
@@ -4,6 +4,10 @@
|
|
modded class SCR_ChimeraAIAgent
|
|
{
|
|
protected ERR_GameMode m_ERRPrisonMode;
|
|
+#ifdef WORKBENCH
|
|
+ // Observe only this mission hold; native distance LOD may independently deactivate AI.
|
|
+ bool ERR_PrisonHeldForTest() { return m_ERRPrisonMode != null; }
|
|
+#endif
|
|
|
|
void ERR_HoldForPrison()
|
|
{
|
|
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
@@ -541,6 +541,29 @@
|
|
PrintFormat("ERR PRISON AI: checked=%1 active=%2", checked, active);
|
|
return checked > 0;
|
|
}
|
|
+ protected bool ERR_CrashPrisonHoldReleased()
|
|
+ {
|
|
+ if (!Forces() || Forces().Count() > 256) return false;
|
|
+ int checked, held, active, dormant;
|
|
+ for (int i = 0; i < Forces().Count(); i++)
|
|
+ {
|
|
+ ERR_ForceRecord record = Forces().At(i);
|
|
+ if (!record || !record.Group) continue;
|
|
+ array<AIAgent> agents = {};
|
|
+ record.Group.GetAgents(agents);
|
|
+ if (agents.Count() > 128) return false;
|
|
+ foreach (AIAgent original : agents)
|
|
+ {
|
|
+ SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(original);
|
|
+ if (!agent || !agent.GetControlledEntity()) return false;
|
|
+ checked++;
|
|
+ if (agent.ERR_PrisonHeldForTest()) held++;
|
|
+ if (agent.IsAIActivated()) active++; else dormant++;
|
|
+ }
|
|
+ }
|
|
+ PrintFormat("ERR CRASH PRISON HOLD: checked=%1 held=%2 active=%3 dormant=%4; native activation is independent", checked, held, active, dormant);
|
|
+ return checked > 0 && held == 0;
|
|
+ }
|
|
protected void ERR_EndScreenshot() { System.MakeScreenshot("$logs:err-result"); }
|
|
protected void ERR_BeginProtectedDeathProbe(float now)
|
|
{
|
|
@@ -906,7 +929,7 @@
|
|
}
|
|
if (m_ERRTestCrashStart)
|
|
{
|
|
- if (!ERR_Check(!m_ERRCrashEnteredPrison && ERR_PrisonAIState(true), "crash start never enters prison and existing enemies are active")) return;
|
|
+ if (!ERR_Check(!m_ERRCrashEnteredPrison && ERR_CrashPrisonHoldReleased(), "crash start never enters prison and existing enemies have no prison hold")) return;
|
|
if (!ERR_NativeCrashStartProbe.Run(this, m_ERRSmokePlayer)) { m_ERRSmokeDone = true; GetGame().GetCallqueue().Remove(ERR_SmokeTick); return; }
|
|
}
|
|
else
|
|
--- a/tests/test_prison_ai.py
|
|
+++ b/tests/test_prison_ai.py
|
|
@@ -1,6 +1,8 @@
|
|
"""Run the production prison hold/release callbacks with engine boundaries stubbed."""
|
|
from policy_harness import ROOT, body, run
|
|
-source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'
|
|
+from pathlib import Path
|
|
+import os
|
|
+source = Path(os.environ.get('ERR_PRISON_AI_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'))
|
|
def cpp(signature):
|
|
s=body(source,signature).replace('protected ','')
|
|
for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Phase','ERR_Actors','SCR_FactionManager'):
|
|
@@ -32,16 +34,17 @@
|
|
struct Agent {ERR_GameMode* m_ERRPrisonMode=nullptr;Actor* actor=nullptr;bool pending=false,active=true;int starts=0;
|
|
Actor* GetControlledEntity(){return actor;}bool IsPlayerPending_S(){return pending;}
|
|
void DeactivateAI(){active=false;}void ActivateAI(){active=true;starts++;}
|
|
-'''+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
|
|
+'''+cpp('bool ERR_PrisonHeldForTest(')+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
|
|
};
|
|
int main(){
|
|
for(int phase=0;phase<=1;phase++){
|
|
ERR_GameMode mode;mode.phase=phase;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
|
|
- a.ERR_HoldForPrison();assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
|
|
+ a.ERR_HoldForPrison();assert(a.ERR_PrisonHeldForTest());assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
|
|
a.active=true;a.ERR_HoldForPrison();assert(!a.active&&mode.phases.inserted==1);
|
|
a.ERR_PrisonPhaseChanged(0,1);assert(!a.active&&a.m_ERRPrisonMode==&mode);
|
|
- mode.phase=2;a.ERR_PrisonPhaseChanged(1,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
|
|
+ mode.phase=2;a.ERR_PrisonPhaseChanged(phase,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
|
|
a.ERR_HoldForPrison();assert(a.active&&mode.phases.inserted==1);
|
|
+ assert(!a.ERR_PrisonHeldForTest());a.DeactivateAI();assert(!a.active&&!a.ERR_PrisonHeldForTest());
|
|
}
|
|
for(int fault=0;fault<8;fault++){
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
|