// Standalone diagnostic addon; never include in the Escape Workshop package. class AID_Probe { static int Job; static int PendingJob; static int SentAt; static int QueryCount; static int Accepted; static int LastAcceptedAt; static bool Ordered; static bool DroppedLogged; static vector JobOrigin; static ref array Reference; static IEntity Body; static SCR_AIGroup Group; static int Stage; static int Started; static int Last; static bool Ready; static bool Finished; static bool ObserverInserted; static ref map BT = new map(); static ref map Utility = new map(); static ref map GroupBT = new map(); static void Count(map counts, IEntity entity) { if (!entity) return; int count = counts.Get(entity); counts.Set(entity, count + 1); } static RplComponent Rpl(IEntity entity) { if (!entity) return null; return RplComponent.Cast(entity.FindComponent(RplComponent)); } static IEntity Resolve(RplId id) { RplComponent rpl = RplComponent.Cast(Replication.FindItem(id)); if (rpl) return rpl.GetEntity(); return null; } static void Sample(string side, IEntity entity, string kind) { RplComponent rpl = Rpl(entity); if (!rpl) { PrintFormat("AID SAMPLE side=%1 stage=%2 kind=%3 missing=1", side, Stage, kind); return; } string row = string.Format("AID SAMPLE side=%1 stage=%2 kind=%3 role=%4 owner=%5 master=%6", side, Stage, kind, rpl.Role(), rpl.IsOwner(), rpl.IsMaster()); row += string.Format(" ownerProxy=%1 remoteProxy=%2 rpl=%3", rpl.IsOwnerProxy(), rpl.IsRemoteProxy(), rpl.Id()); AIControlComponent control = AIControlComponent.Cast(entity.FindComponent(AIControlComponent)); AIAgent agent; if (control) agent = control.GetControlAIAgent(); else agent = AIAgent.Cast(entity); bool controlActive, agentActive; int lod = -1; if (control) controlActive = control.IsAIActivated(); if (agent) { agentActive = agent.IsAIActivated(); lod = agent.GetLOD(); } PerceptionComponent perception = PerceptionComponent.Cast(entity.FindComponent(PerceptionComponent)); int simulations = -1; if (perception) simulations = perception.GetSimulateCounter(); row += string.Format(" control=%1 agent=%2 controlActive=%3 agentActive=%4 lod=%5 perception=%6", control != null, agent != null, controlActive, agentActive, lod, simulations); row += string.Format(" bt=%1 utility=%2 groupBT=%3 pos=%4 wall=%5", BT.Get(entity), Utility.Get(entity), GroupBT.Get(entity), entity.GetOrigin(), System.GetTickCount()); Print(row); } static void Spawn() { vector position = "6090 0 7160"; position[1] = GetGame().GetWorld().GetSurfaceY(position[0], position[2]) + 0.2; EntitySpawnParams params = new EntitySpawnParams(); params.TransformMode = ETransformMode.WORLD; params.Transform[3] = position; SCR_AIGroup.IgnoreSpawning(true); Group = SCR_AIGroup.Cast(GetGame().SpawnEntityPrefab(Resource.Load("{EACD97CF4A702FAE}Prefabs/Groups/BLUFOR/Group_US_Base.et"), GetGame().GetWorld(), params)); SCR_AIGroup.IgnoreSpawning(false); Body = GetGame().SpawnEntityPrefab(Resource.Load("{E398E44759DA1A43}Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_TL.et"), GetGame().GetWorld(), params); if (!Body || !Group) { Print("AID FAIL spawn"); Finished = true; return; } AIControlComponent control = AIControlComponent.Cast(Body.FindComponent(AIControlComponent)); AIAgent agent; if (control) agent = control.GetControlAIAgent(); if (!agent) { Print("AID FAIL server agent absent"); Finished = true; return; } Group.AddAgent(agent); Group.SetPermanentLOD(0); Group.ActivateAI(); agent.SetPermanentLOD(0); agent.ActivateAI(); Rpl(Body).EnableStreaming(true); Rpl(Group).EnableStreaming(true); Stage = 1; Started = System.GetTickCount(); PrintFormat("AID READY groupAgents=%1 body=%2 group=%3", Group.GetAgentsCount(), Rpl(Body).Id(), Rpl(Group).Id()); } static void Tick() { if (Finished || !System.IsCLIParam("aidProbe") || !GetGame().GetGameMode()) return; int now = System.GetTickCount(); if (now - Last < 1000) return; Last = now; if (!Replication.IsServer()) { if (Body) { Sample("worker", Body, "body"); Sample("worker", Group, "group"); } return; } if (!Body) { Spawn(); return; } Sample("dedicated", Body, "body"); Sample("dedicated", Group, "group"); if (Stage == 3 && Ready && now - LastAcceptedAt > 2500) { array fallback = CastBatch(Body.GetOrigin()); PrintFormat("AID QUERY FALLBACK stage=%1 casts=%2 accepted=%3", Stage, QueryCount, Accepted); } array players = {}; GetGame().GetPlayerManager().GetPlayers(players); foreach (int player : players) { SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(player)); if (!controller) continue; RplIdentity identity = controller.GetRplIdentity(); if (!identity.IsValid() || identity == RplIdentity.Local()) continue; if (!ObserverInserted) { ObserversSystem observers = ObserversSystem.Cast(GetGame().GetWorld().FindSystem(ObserversSystem)); if (observers) observers.InsertObserverMP(identity, Body.GetOrigin()[0], Body.GetOrigin()[2], null); Rpl(Body).EnableStreamingConNode(identity, true); Rpl(Group).EnableStreamingConNode(identity, true); ObserverInserted = true; } controller.AID_Ping(Rpl(Body).Id(), Rpl(Group).Id(), Stage); if (Ready && (!PendingJob || now - SentAt > 2500)) { Job++; PendingJob = Job; SentAt = now; JobOrigin = Body.GetOrigin(); Reference = null; if (Stage == 1) Reference = CastBatch(JobOrigin); controller.AID_Query(Job, Stage, JobOrigin); PrintFormat("AID QUERY SENT stage=%1 job=%2 localCasts=%3", Stage, Job, QueryCount); } if (!Ready || now - Started < 20000) return; if (Stage == 1) Stage = 2; else if (Stage == 2) Stage = 3; else if (Stage == 3) { Print("AID PROBE COMPLETE"); Finished = true; return; } PendingJob = 0; Started = now; PrintFormat("AID TRANSITION stage=%1 wall=%2", Stage, now); return; } } static vector Endpoint(vector origin, int index) { if (index == 0) return origin - "0 20 0"; if (index == 1) return origin + "0 40 0"; float angle = (index - 2) * Math.PI2 / 254; return origin + Vector(Math.Cos(angle) * 40, 0.5, Math.Sin(angle) * 40); } static array CastBatch(vector origin) { array results = {}; TraceParam trace = new TraceParam(); trace.Flags = TraceFlags.WORLD; trace.Exclude = Body; for (int i = 0; i < 256; i++) { trace.Start = origin + "0 1.6 0"; trace.End = Endpoint(origin, i); results.Insert(GetGame().GetWorld().TraceMove(trace, null)); QueryCount++; } return results; } static void Accept(int job, int phase, array results) { int now = System.GetTickCount(); if (Finished || job != PendingJob || phase != Stage || now - SentAt > 2500 || !results || results.Count() != 256) return; foreach (float value : results) { if (value != value || value < 0 || value > 1) return; } // Down ray must hit terrain; sky ray must be clear. Refuse unready collision data. if (results[0] >= 1 || results[1] < 0.99) { Print("AID QUERY REJECT scene-not-ready"); PendingJob = 0; return; } int mismatches; if (Reference) for (int k = 0; k < 256; k++) { if (Math.AbsFloat(Reference[k] - results[k]) > 0.005) mismatches++; } Accepted++; PendingJob = 0; LastAcceptedAt = now; PrintFormat("AID QUERY ACCEPT stage=%1 job=%2 rttMs=%3 mismatches=%4 serverCasts=%5 accepted=%6", Stage, job, now - SentAt, mismatches, QueryCount, Accepted); if (Stage == 2 && !Ordered) { int chosen = -1; for (int j = 2; j < 256; j++) { if (results[j] >= 0.99) { chosen = j; break; } } if (chosen >= 0) { vector destination = Endpoint(JobOrigin, chosen); destination[1] = GetGame().GetWorld().GetSurfaceY(destination[0],destination[2]); EntitySpawnParams params = new EntitySpawnParams(); params.TransformMode = ETransformMode.WORLD; params.Transform[3] = destination; AIWaypoint waypoint = AIWaypoint.Cast(GetGame().SpawnEntityPrefab(Resource.Load("{750A8D1695BD6998}Prefabs/AI/Waypoints/AIWaypoint_Move.et"), GetGame().GetWorld(), params)); if (waypoint) { Group.AddWaypoint(waypoint); Ordered = true; PrintFormat("AID QUERY APPLIED destination=%1 serverRole=%2", destination, Rpl(Body).Role()); } } } } static void Reset() { Body = null; Group = null; Stage = 0; Started = 0; Last = 0; Ready = false; Finished = false; ObserverInserted = false; Job = 0; PendingJob = 0; QueryCount = 0; Accepted = 0; Ordered = false; DroppedLogged = false; Reference = null; LastAcceptedAt = 0; BT.Clear(); Utility.Clear(); GroupBT.Clear(); } } modded class SCR_AIDecideBehavior { override ENodeResult EOnTaskSimulate(AIAgent owner, float dt) { AID_Probe.Count(AID_Probe.BT, owner.GetControlledEntity()); return super.EOnTaskSimulate(owner, dt); } } modded class SCR_AIDecideActivity { override ENodeResult EOnTaskSimulate(AIAgent owner, float dt) { AID_Probe.Count(AID_Probe.GroupBT, owner); return super.EOnTaskSimulate(owner, dt); } } modded class SCR_AIUtilityComponent { override SCR_AIBehaviorBase EvaluateBehavior(BaseTarget unknownTarget) { AID_Probe.Count(AID_Probe.Utility, m_OwnerEntity); return super.EvaluateBehavior(unknownTarget); } } modded class SCR_PlayerController { void AID_Query(int job, int phase, vector origin) { Rpc(AID_RpcQuery, job, phase, origin); } [RplRpc(RplChannel.Reliable, RplRcver.Owner)] void AID_RpcQuery(int job, int phase, vector origin) { if (Replication.IsServer()) return; if (phase == 3) { if (!AID_Probe.DroppedLogged) { Print("AID QUERY WORKER deliberately silent for failure test"); AID_Probe.DroppedLogged = true; } return; } int start = System.GetTickCount(); array results = AID_Probe.CastBatch(origin); PrintFormat("AID QUERY WORKER stage=%1 job=%2 casts=%3 wallMs=%4 ground=%5 sky=%6", phase, job, AID_Probe.QueryCount, System.GetTickCount() - start, results[0], results[1]); Rpc(AID_RpcResult, job, phase, results); } [RplRpc(RplChannel.Reliable, RplRcver.Server)] void AID_RpcResult(int job, int phase, array results) { if (Replication.IsServer()) AID_Probe.Accept(job, phase, results); } void AID_Ping(RplId body, RplId group, int stage) { Rpc(AID_RpcWorker, body, group, stage); } [RplRpc(RplChannel.Reliable, RplRcver.Owner)] void AID_RpcWorker(RplId body, RplId group, int stage) { if (Replication.IsServer()) return; AID_Probe.Body = AID_Probe.Resolve(body); AID_Probe.Group = SCR_AIGroup.Cast(AID_Probe.Resolve(group)); AID_Probe.Stage = stage; // Small deterministic script job proves actual worker-side computation/RPC. int result; for (int i = 1; i <= 100; i++) result += i; PrintFormat("AID RPC worker stage=%1 body=%2 group=%3 computed=%4", stage, AID_Probe.Body != null, AID_Probe.Group != null, result); Rpc(AID_RpcAck, AID_Probe.Body != null && AID_Probe.Group != null, stage, result); } [RplRpc(RplChannel.Reliable, RplRcver.Server)] void AID_RpcAck(bool ready, int stage, int result) { if (!Replication.IsServer()) return; if (ready && result == 5050 && !AID_Probe.Ready) { AID_Probe.Ready = true; AID_Probe.Started = System.GetTickCount(); } PrintFormat("AID RPC dedicated stage=%1 resolved=%2 result=%3", stage, ready, result); } } modded class ArmaReforgerScripted { override void OnUpdate(BaseWorld world, float timeslice) { super.OnUpdate(world, timeslice); if (world) AID_Probe.Tick(); } override protected void OnBeforeWorldCleanup() { AID_Probe.Reset(); super.OnBeforeWorldCleanup(); } }