Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// 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<float> 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<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) { 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<float> fallback = CastBatch(Body.GetOrigin());
|
||||
PrintFormat("AID QUERY FALLBACK stage=%1 casts=%2 accepted=%3", Stage, QueryCount, Accepted);
|
||||
}
|
||||
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 && (!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<float> CastBatch(vector origin)
|
||||
{
|
||||
array<float> 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<float> 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<float> 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<float> 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user