// Standalone diagnostic addon; never include in the Escape Workshop package.
class AID_Probe
{
 static IEntity Shadow;
 static SCR_AIGroup ShadowGroup;
 static bool ShadowAttempted;
 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<IEntity, int> BT = new map<IEntity, int>();
 static ref map<IEntity, int> Utility = new map<IEntity, int>();
 static ref map<IEntity, int> GroupBT = new map<IEntity, int>();
 static void Count(map<IEntity, int> 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)
   {
    if (!ShadowAttempted) SpawnShadow();
    Sample("worker", Body, "body"); Sample("worker", Group, "group");
    Sample("worker", Shadow, "shadow"); Sample("worker", ShadowGroup, "shadowGroup");
    SampleNavigation();
   }
   return;
  }
  if (!Body) { Spawn(); return; }
  Sample("dedicated", Body, "body"); Sample("dedicated", Group, "group");
  array<int> 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 || 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; }
   Started = now; PrintFormat("AID TRANSITION stage=%1 wall=%2", Stage, now);
   return;
  }
 }
 static void SpawnShadow()
 {
  ShadowAttempted = true;
  vector position = Body.GetOrigin() + "5 0 0";
  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);
  ShadowGroup = SCR_AIGroup.Cast(GetGame().SpawnEntityPrefabLocal(Resource.Load("{EACD97CF4A702FAE}Prefabs/Groups/BLUFOR/Group_US_Base.et"), GetGame().GetWorld(), params));
  SCR_AIGroup.IgnoreSpawning(false);
  Shadow = GetGame().SpawnEntityPrefabLocal(Resource.Load("{E398E44759DA1A43}Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_TL.et"), GetGame().GetWorld(), params);
  AIControlComponent control;
  if (Shadow) control = AIControlComponent.Cast(Shadow.FindComponent(AIControlComponent));
  AIAgent agent;
  if (control) { control.ActivateAI(); agent = control.GetControlAIAgent(); }
  if (agent && ShadowGroup) { ShadowGroup.AddAgent(agent); agent.SetPermanentLOD(0); agent.ActivateAI(); ShadowGroup.SetPermanentLOD(0); ShadowGroup.ActivateAI(); }
  PrintFormat("AID LOCAL SPAWN body=%1 group=%2 control=%3 agent=%4 aiWorld=%5", Shadow != null, ShadowGroup != null, control != null, agent != null, GetGame().GetAIWorld() != null);
  params.Transform[3] = position + "35 0 0";
  params.Transform[3][1] = GetGame().GetWorld().GetSurfaceY(params.Transform[3][0],params.Transform[3][2]);
  AIWaypoint waypoint = AIWaypoint.Cast(GetGame().SpawnEntityPrefabLocal(Resource.Load("{750A8D1695BD6998}Prefabs/AI/Waypoints/AIWaypoint_Move.et"), GetGame().GetWorld(), params));
  if (waypoint && ShadowGroup) ShadowGroup.AddWaypoint(waypoint);
 }
 static void SampleNavigation()
 {
  AIWorld world = GetGame().GetAIWorld();
  NavmeshWorldComponent mesh;
  if (world) mesh = world.GetNavmeshWorldComponent("Soldiers");
  vector position = Body.GetOrigin();
  vector reachable;
  bool loaded, valid, queried;
  if (mesh)
  {
   mesh.LoadTileIn(position); loaded = mesh.IsTileLoaded(position); valid = mesh.IsTileValid(position);
   if (loaded && valid) queried = mesh.GetReachablePoint(position, 25, reachable);
  }
  PrintFormat("AID NATIVE QUERY mesh=%1 loaded=%2 valid=%3 success=%4 position=%5", mesh != null, loaded, valid, queried, reachable);
 }
 static void Reset()
 {
  Shadow = null; ShadowGroup = null; ShadowAttempted = false; Body = null; Group = null; Stage = 0; Started = 0; Last = 0; Ready = false; Finished = false; ObserverInserted = false;
  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_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();
 }
}
