Files
EscapistsReintroduced/docs/evidence/hunter-capture-2026-10-02/proposal/prepare.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

52 lines
3.7 KiB
Python

from pathlib import Path
import hashlib,json,subprocess
r=Path('/home/jakub/devel/escapistsrereintroduced');p=Path('/tmp/erri-hunter-capture-2237')
files=['addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c','addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c','tests/test_hunter_lifecycle.py','tests/test_convoy_survivors.py']
b={}
for f in files:
d=(r/f).read_bytes();t=p/f;t.parent.mkdir(parents=True,exist_ok=True);t.write_bytes(d);s=p/'baseline'/f;s.parent.mkdir(parents=True,exist_ok=True);s.write_bytes(d)
q=subprocess.run(['git','show',':'+f],cwd=r,capture_output=True);b[f]={'working':hashlib.sha256(d).hexdigest(),'index':hashlib.sha256(q.stdout).hexdigest() if q.returncode==0 else None}
(p/'baseline.json').write_text(json.dumps(b,indent=2)+'\n')
f=p/files[0];s=f.read_text().replace(' bool Registered;',' bool Registered;\n // Latched only for an exact tracked hunter at authoritative player entry.\n bool PlayerCaptureObserved;',1).replace('if (Registered && hq && !ERR_ConvoyRules.Captured(Entity, Group))','if (Registered && !PlayerCaptureObserved && hq && !ERR_ConvoyRules.Captured(Entity, Group))',1);f.write_text(s)
f=p/files[1];s=f.read_text().replace('if (!mode || !mode.Active() || ERR_ConvoyRules.Captured(Entity, Group)) { super.Release(mode); return; }','if (PlayerCaptureObserved || !mode || !mode.Active() || ERR_ConvoyRules.Captured(Entity, Group)) { Downlink = null; super.Release(mode); return; }',1)
method=''' // Entry is an ownership event, not a later occupancy sample. Keep the fact
// even if the player exits/disconnects before the one-second hunter tick.
void PlayerEntered(IEntity actor, IEntity entered)
{
if (!Replication.IsServer() || m_Stopped || !m_Mode || m_Mode != ERR_GameMode.Get()) return;
if (!m_Mode.Active() || !actor || !entered || !m_Mode.Players()) return;
ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
if (!hq || hq.Hunters() != this || !m_Mode.Players().CurrentActor(actor)) return;
if (m_Hunters.Count() > 15) return;
foreach (ERR_HunterVehicle unit : m_Hunters)
{
// Released survivors already have independent HQ ownership. Do not revoke
// their infantry role just because the abandoned original vehicle is entered.
if (!unit || unit.Released || !unit.Entity || !ERR_ConvoyRules.PartOf(entered, unit.Entity)) continue;
unit.PlayerCaptureObserved = true;
unit.Release(m_Mode);
}
}
'''
pos=s.index(' protected bool PrepareVehicleRadio(');s=s[:pos]+method+s[pos:]
s+='''
// Match the original hunter before native callbacks can erase current player
// occupancy. Other vehicles and already-released crews are not adopted here.
modded class SCR_CompartmentAccessComponent
{
override protected void OnCompartmentEntered(IEntity targetEntity, BaseCompartmentManagerComponent manager, int mgrID, int slotID, bool move)
{
if (Replication.IsServer())
{
ERR_GameMode mode = ERR_GameMode.Get();
ERR_HQ hq;
if (mode) hq = ERR_HQ.Cast(mode.Command());
if (hq && hq.Hunters()) hq.Hunters().PlayerEntered(GetOwner(), targetEntity);
}
super.OnCompartmentEntered(targetEntity, manager, mgrID, slotID, move);
}
}
''';f.write_text(s)
f=p/files[2];s=f.read_text().replace('from policy_harness import ROOT, body, run','import os\nfrom pathlib import Path\nfrom policy_harness import ROOT, body, run').replace("source=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'","source=Path(os.environ.get('ERR_HUNTER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'))").replace('bool Released=false;','bool Released=false,PlayerCaptureObserved=false;');f.write_text(s)
f=p/files[3];f.write_text(f.read_text().replace('bool Released=false,Registered=true;','bool Released=false,Registered=true,PlayerCaptureObserved=false;'))