Arma Reforger Explorer  1.1.0.42
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
SCR_GameModeStatistics.c
Go to the documentation of this file.
1 #ifdef GMSTATS
2 #define GM_AI_STATS
3 #endif
4 #ifdef AISTATS
5 #define GM_AI_STATS
6 #endif
7 
8 #ifdef GM_AI_STATS
9 
10 enum SCR_EGameModeRecordType
11 {
12  Death,
13  Movement,
14  Connection,
15  Spawn,
16  AILOD,
17 }
18 
19 enum ETypeOfAgent // TODO: SCR_ETypeOfAgent / SCR_EAgentType
20 {
21  Group,
22  Agent,
23  Bird,
24  Player
25 }
26 
28 class SCR_IGameModeRecord
29 {
31  protected float m_fTimestamp;
32 
33  //------------------------------------------------------------------------------------------------
35  SCR_EGameModeRecordType GetRecordType();
36 
37  //------------------------------------------------------------------------------------------------
39  float GetTimestamp()
40  {
41  return m_fTimestamp;
42  }
43 
44  //------------------------------------------------------------------------------------------------
46  string SaveToString()
47  {
48  return string.Format("%1;%2;%3",
49  GetRecordType().ToString(),
50  GetTimestamp().ToString(),
51  Save()
52  );
53  }
54 
55  //------------------------------------------------------------------------------------------------
57  protected string Save();
58 
59  //------------------------------------------------------------------------------------------------
61  protected void Load(array<string> tokens);
62 
63  //------------------------------------------------------------------------------------------------
65  static ref SCR_IGameModeRecord LoadFromString(string str)
66  {
67  array<string> tokens = {};
68  str.Split(";", tokens, false);
69 
70  int type = tokens[0].ToInt();
71  tokens.RemoveOrdered(0);
72 
73  float timeStamp = tokens[0].ToFloat();
74  tokens.RemoveOrdered(0);
75 
76  switch (type)
77  {
78  case SCR_EGameModeRecordType.Death:
79  {
80  ref SCR_IGameModeRecord record = new SCR_DeathRecord();
81  record.m_fTimestamp = timeStamp;
82  record.Load(tokens);
83  return record;
84  }
85  break;
86 
87  case SCR_EGameModeRecordType.Movement:
88  {
89  ref SCR_IGameModeRecord record = new SCR_MovementRecord();
90  record.m_fTimestamp = timeStamp;
91  record.Load(tokens);
92  return record;
93  }
94  break;
95 
96  case SCR_EGameModeRecordType.Connection:
97  {
98  ref SCR_IGameModeRecord record = new SCR_ConnectionRecord();
99  record.m_fTimestamp = timeStamp;
100  record.Load(tokens);
101  return record;
102  }
103  break;
104 
105  case SCR_EGameModeRecordType.Spawn:
106  {
107  ref SCR_IGameModeRecord record = new SCR_SpawnRecord();
108  record.m_fTimestamp = timeStamp;
109  record.Load(tokens);
110  return record;
111  }
112  break;
113 
114  case SCR_EGameModeRecordType.AILOD:
115  {
116  ref SCR_IGameModeRecord record = new SCR_AILODRecord();
117  record.m_fTimestamp = timeStamp;
118  record.Load(tokens);
119  return record;
120  }
121  }
122 
123  return null;
124  }
125 }
126 
128 sealed class SCR_DeathRecord : SCR_IGameModeRecord
129 {
130  vector m_Position;
131  vector m_InstigatorPosition;
132  FactionKey m_Faction;
133  FactionKey m_InstigatorFaction;
134  int m_InstigatorId;
135  int m_PlayerId;
136  EWeaponType m_WeaponType = EWeaponType.WT_NONE;
137 
138  //------------------------------------------------------------------------------------------------
139  sealed override SCR_EGameModeRecordType GetRecordType()
140  {
141  return SCR_EGameModeRecordType.Death;
142  }
143 
144  //------------------------------------------------------------------------------------------------
145  override string Save()
146  {
147  string pos = string.Format("%1,%2,%3", m_Position[0], m_Position[1], m_Position[2]);
148  string ipos = string.Format("%1,%2,%3", m_InstigatorPosition[0], m_InstigatorPosition[1], m_InstigatorPosition[2]);
149  return string.Format("%1;%2;%3;%4;%5;%6;%7", pos, ipos, m_Faction, m_InstigatorFaction, (int)m_WeaponType, m_PlayerId, m_InstigatorId);
150  }
151 
152  //------------------------------------------------------------------------------------------------
153  protected sealed override void Load(array<string> tokens)
154  {
155  array <string> pos = {};
156  tokens[0].Split(",", pos, true);
157  m_Position = Vector(pos[0].ToFloat(), pos[1].ToFloat(), pos[2].ToFloat());
158 
159  array <string> ipos = {};
160  tokens[1].Split(",", ipos, true);
161  m_InstigatorPosition = Vector(ipos[0].ToFloat(), ipos[1].ToFloat(), ipos[2].ToFloat());
162 
163  m_Faction = tokens[2];
164  m_InstigatorFaction = tokens[3];
165 
166  m_WeaponType = tokens[4].ToInt();
167  m_PlayerId = tokens[5].ToInt();
168  m_InstigatorId = tokens[6].ToInt();
169  }
170 
171  //------------------------------------------------------------------------------------------------
178  static ref SCR_DeathRecord CreateNew(int playerId, IEntity playerEntity, IEntity killerEntity, Instigator killer)
179  {
180  SCR_DeathRecord record = new SCR_DeathRecord();
181  record.m_Position = playerEntity.GetOrigin();
182  record.m_fTimestamp = playerEntity.GetWorld().GetWorldTime();
183  record.m_PlayerId = playerId;
184  record.m_InstigatorId = killer.GetInstigatorPlayerID();
185  FactionAffiliationComponent factionComponent = FactionAffiliationComponent.Cast(playerEntity.FindComponent(FactionAffiliationComponent));
186  if (factionComponent)
187  {
188  Faction faction = factionComponent.GetAffiliatedFaction();
189  if (faction)
190  record.m_Faction = faction.GetFactionKey();
191  }
192 
193  if (killerEntity)
194  {
195  record.m_InstigatorPosition = killerEntity.GetOrigin();
196 
197  ChimeraCharacter ch = ChimeraCharacter.Cast(killerEntity);
198  if (ch)
199  {
200  BaseWeaponManagerComponent wpm = ch.GetCharacterController().GetWeaponManagerComponent();
201  if (wpm)
202  {
203  BaseWeaponComponent weapon = wpm.GetCurrentWeapon();
204  if (weapon)
205  record.m_WeaponType = weapon.GetWeaponType();
206  }
207  }
208 
209  FactionAffiliationComponent instigatorFactionComponent = FactionAffiliationComponent.Cast(killerEntity.FindComponent(FactionAffiliationComponent));
210  if (instigatorFactionComponent)
211  {
212  Faction instigatorFaction = instigatorFactionComponent.GetAffiliatedFaction();
213  if (instigatorFaction)
214  record.m_InstigatorFaction = instigatorFaction.GetFactionKey();
215  }
216  }
217 
218  // Keep track of instigator stuff..
219  return record;
220  }
221 }
222 
224 sealed class SCR_MovementRecord : SCR_IGameModeRecord
225 {
226  vector m_Position;
227  int m_PlayerId;
228 
229  //------------------------------------------------------------------------------------------------
230  sealed override SCR_EGameModeRecordType GetRecordType()
231  {
232  return SCR_EGameModeRecordType.Movement;
233  }
234 
235  //------------------------------------------------------------------------------------------------
236  override string Save()
237  {
238  string pos = string.Format("%1,%2,%3", m_Position[0], m_Position[1], m_Position[2]);
239  return string.Format("%1;%2;", pos, m_PlayerId);
240  }
241 
242  //------------------------------------------------------------------------------------------------
243  protected sealed override void Load(array<string> tokens)
244  {
245  array <string> pos = {};
246  tokens[0].Split(",", pos, true);
247  m_Position = Vector(pos[0].ToFloat(), pos[1].ToFloat(), pos[2].ToFloat());
248  m_PlayerId = tokens[1].ToInt();
249  }
250 
251  //------------------------------------------------------------------------------------------------
256  static ref SCR_MovementRecord CreateNew(IEntity player, int playerId)
257  {
258  SCR_MovementRecord record = new SCR_MovementRecord();
259  record.m_Position = player.GetOrigin();
260  record.m_PlayerId = playerId;
261  record.m_fTimestamp = player.GetWorld().GetWorldTime();
262  return record;
263  }
264 }
265 
267 sealed class SCR_ConnectionRecord : SCR_IGameModeRecord
268 {
269  int m_PlayerId;
270  string m_PlayerName;
271 
272  //------------------------------------------------------------------------------------------------
273  sealed override SCR_EGameModeRecordType GetRecordType()
274  {
275  return SCR_EGameModeRecordType.Connection;
276  }
277 
278  //------------------------------------------------------------------------------------------------
279  override string Save()
280  {
281  return string.Format("%1;%2;", m_PlayerId, m_PlayerName);
282  }
283 
284  //------------------------------------------------------------------------------------------------
285  protected sealed override void Load(array<string> tokens)
286  {
287  m_PlayerId = tokens[0].ToInt();
288  m_PlayerName = tokens[1];
289  }
290 
291  //------------------------------------------------------------------------------------------------
296  static ref SCR_ConnectionRecord CreateNew(int playerId, string playerName)
297  {
298  SCR_ConnectionRecord record = new SCR_ConnectionRecord();
299  record.m_PlayerId = playerId;
300  record.m_PlayerName = playerName;
301  record.m_fTimestamp = GetGame().GetWorld().GetWorldTime();
302  return record;
303  }
304 }
305 
306 sealed class SCR_SpawnRecord : SCR_IGameModeRecord
307 {
308  int m_PlayerId;
309  int m_FactionColor;
310 
311  sealed override SCR_EGameModeRecordType GetRecordType()
312  {
313  return SCR_EGameModeRecordType.Spawn;
314  }
315 
316  override string Save()
317  {
318  return string.Format("%1;%2;", m_PlayerId, m_FactionColor);
319  }
320 
321  protected sealed override void Load(array<string> tokens)
322  {
323  m_PlayerId = tokens[0].ToInt();
324  m_FactionColor = tokens[1].ToInt();
325  }
326 
327  //------------------------------------------------------------------------------------------------
329  void SCR_SpawnRecord()
330  {}
331 
332  //------------------------------------------------------------------------------------------------
337  static ref SCR_SpawnRecord CreateNew(int playerId, int factionColor)
338  {
339  SCR_SpawnRecord record = new SCR_SpawnRecord();
340  record.m_PlayerId = playerId;
341  record.m_FactionColor = factionColor;
342  record.m_fTimestamp = GetGame().GetWorld().GetWorldTime();
343  return record;
344  }
345 }
346 
347 sealed class SCR_AILODRecord : SCR_IGameModeRecord
348 {
349  string m_ID;
350  vector m_Position;
351  int m_LOD;
352  ETypeOfAgent m_Type;
353  int m_BTCounter = 0;
354  int m_PerceptionCounter = 0;
355 
356  sealed override SCR_EGameModeRecordType GetRecordType()
357  {
358  return SCR_EGameModeRecordType.AILOD;
359  }
360 
361  override string Save()
362  {
363  string pos = string.Format("%1,%2,%3", m_Position[0], m_Position[1], m_Position[2]);
364  return string.Format("%1;%2;%3;%4;%5;%6", m_ID, pos, m_LOD, m_Type, m_BTCounter, m_PerceptionCounter);
365  }
366 
367  protected sealed override void Load(array<string> tokens)
368  {
369  array <string> pos = {};
370  m_ID = tokens[0];
371  tokens[1].Split(",", pos, true);
372  m_Position = Vector(pos[0].ToFloat(), pos[1].ToFloat(), pos[2].ToFloat());
373  m_LOD = tokens[2].ToInt();
374  m_Type = tokens[3].ToInt();
375  m_BTCounter = tokens[4].ToInt();
376  m_PerceptionCounter = tokens[5].ToInt();
377  }
378 
379  //------------------------------------------------------------------------------------------------
383  static ref SCR_AILODRecord CreateNew(AIAgent agent)
384  {
385  SCR_AILODRecord record = new SCR_AILODRecord();
386  record.m_fTimestamp = GetGame().GetWorld().GetWorldTime();
387  record.m_LOD = agent.GetLOD();
388  SCR_AIGroup group = SCR_AIGroup.Cast(agent);
389  IEntity ent = agent.GetControlledEntity();
390  if (group)
391  {
392  record.m_Type = ETypeOfAgent.Group;
393  record.m_Position = group.GetCenterOfMass();
394  }
395  else
396  {
397  if (ent)
398  record.m_Position = ent.GetOrigin();
399  if (AIFlock.Cast(agent))
400  record.m_Type = ETypeOfAgent.Bird;
401  else if (EntityUtils.IsPlayer(ent))
402  record.m_Type = ETypeOfAgent.Player;
403  else
404  record.m_Type = ETypeOfAgent.Agent;
405  }
406 
407  AIChimeraBehaviorTreeComponent btComp = AIChimeraBehaviorTreeComponent.Cast(agent.FindComponent(AIChimeraBehaviorTreeComponent));
408  if (btComp)
409  record.m_BTCounter = btComp.GetSimulateCounter();
410 
411  if (ent)
412  {
413  PerceptionComponent percComp = PerceptionComponent.Cast(ent.FindComponent(PerceptionComponent));
414  if (percComp)
415  record.m_PerceptionCounter = percComp.GetSimulateCounter();
416  }
417  record.m_ID = agent.GetID().ToString().Substring(2,16);
418  return record;
419  }
420 }
421 
423 class SCR_GameModeStatistics
424 {
426  static const int RECORD_INTERVAL_MS = 1000;
427 
429  private static ref array<ref SCR_IGameModeRecord> s_aRecordBuffer = {};
430 
432  private static ref FileHandle s_pFileHandle;
433 
435  private static const string k_sExtension = ".txt";
436 
437  //------------------------------------------------------------------------------------------------
439  static bool IsRecording()
440  {
441  if (s_pFileHandle)
442  return true;
443 
444  return false;
445  }
446 
447  //------------------------------------------------------------------------------------------------
449  static void StartRecording()
450  {
451  if (IsRecording())
452  {
453  Debug.Error("Already recording!");
454  return;
455  }
456 
457  string filename = "GameStatistics_" + FormatTimestamp() + k_sExtension;
458  s_pFileHandle = FileIO.OpenFile("$logs:/" + filename, FileMode.WRITE);
459  }
460 
461  //------------------------------------------------------------------------------------------------
463  static bool CanFlush()
464  {
465  return s_aRecordBuffer.Count() > 0;
466  }
467 
468  //------------------------------------------------------------------------------------------------
470  static void Flush()
471  {
472  if (!IsRecording())
473  {
474  Debug.Error("Cannot flush buffer, statistics are not being recorded!");
475  return;
476  }
477 
478  // Empty
479  if (!CanFlush())
480  return;
481 
482  for (int i = 0, count = s_aRecordBuffer.Count(); i < count; i++)
483  {
484  SCR_IGameModeRecord record = s_aRecordBuffer[i];
485  if (!record) // Skip broken record(s)
486  continue;
487 
488  string entry = record.SaveToString();
489  if (entry.IsEmpty()) // Keep the file tidy
490  continue;
491 
492  s_pFileHandle.WriteLine(entry);
493  }
494 
495  // Clear the buffer
496  s_aRecordBuffer.Clear();
497  }
498 
499  //------------------------------------------------------------------------------------------------
501  static void StopRecording()
502  {
503  if (!IsRecording())
504  {
505  Debug.Error("Not recording!");
506  return;
507  }
508 
509  // Write remaining data, if any
510  if (CanFlush())
511  Flush();
512 
513  s_pFileHandle.Close();
514  s_pFileHandle = null;
515  }
516 
517  //------------------------------------------------------------------------------------------------
523  static void RecordDeath(int playerId, IEntity playerEntity, IEntity killerEntity, Instigator killer)
524  {
525  SCR_DeathRecord rec = SCR_DeathRecord.CreateNew(playerId, playerEntity, killerEntity, killer);
526  s_aRecordBuffer.Insert(rec);
527  }
528 
529  //------------------------------------------------------------------------------------------------
533  static void RecordMovement(IEntity player, int playerId)
534  {
535  SCR_MovementRecord rec = SCR_MovementRecord.CreateNew(player, playerId);
536  s_aRecordBuffer.Insert(rec);
537  }
538 
539  //------------------------------------------------------------------------------------------------
543  static void RecordConnection(int playerId, string playerName)
544  {
545  SCR_ConnectionRecord rec = SCR_ConnectionRecord.CreateNew(playerId, playerName);
546  s_aRecordBuffer.Insert(rec);
547  }
548 
549  //------------------------------------------------------------------------------------------------
553  static void RecordSpawn(int playerId, int factionColor)
554  {
555  SCR_SpawnRecord rec = SCR_SpawnRecord.CreateNew(playerId, factionColor);
556  s_aRecordBuffer.Insert(rec);
557  }
558 
559  //------------------------------------------------------------------------------------------------
562  static void RecordAILOD(AIAgent aiagent)
563  {
564  SCR_AILODRecord rec = SCR_AILODRecord.CreateNew(aiagent);
565  s_aRecordBuffer.Insert(rec);
566  }
567 
568  //------------------------------------------------------------------------------------------------
570  static int OpenStatistics(string filename, notnull array<ref SCR_IGameModeRecord> outRecords)
571  {
572  outRecords.Clear();
573  if (!filename.EndsWith(k_sExtension))
574  filename += k_sExtension;
575 
576  if (!FileIO.FileExists(filename))
577  return 0;
578 
579  FileHandle fileHnd = FileIO.OpenFile(filename, FileMode.READ);
580  if (!fileHnd)
581  return 0;
582 
583  int count = 0;
584  string temp = "";
585  while (fileHnd.ReadLine(temp) > 0)
586  {
587  ref SCR_IGameModeRecord record = SCR_IGameModeRecord.LoadFromString(temp);
588  if (record != null)
589  {
590  outRecords.Insert(record);
591  ++count;
592  }
593  }
594 
595  // Finally dispose of handle
596  fileHnd.Close();
597  return count;
598  }
599 
600  //------------------------------------------------------------------------------------------------
602  static void FilterRecords(typename predicate, notnull array<SCR_IGameModeRecord> records, out array<SCR_IGameModeRecord> filteredRecords)
603  {
604  foreach (SCR_IGameModeRecord record : records)
605  {
606  if (record.Type().IsInherited(predicate))
607  filteredRecords.Insert(record);
608  }
609  }
610 
611  //------------------------------------------------------------------------------------------------
613  static void FilterRecordsRefRef(typename predicate, notnull array<ref SCR_IGameModeRecord> records, out array<ref SCR_IGameModeRecord> filteredRecords)
614  {
615  foreach (SCR_IGameModeRecord record : records)
616  {
617  if (record.Type().IsInherited(predicate))
618  filteredRecords.Insert(record);
619  }
620  }
621 
622  //------------------------------------------------------------------------------------------------
624  static void FilterRecordsWeakRef(typename predicate, notnull array<SCR_IGameModeRecord> records, out array<ref SCR_IGameModeRecord> filteredRecords)
625  {
626  foreach (SCR_IGameModeRecord record : records)
627  {
628  if (record.Type().IsInherited(predicate))
629  filteredRecords.Insert(record);
630  }
631  }
632 
633  //------------------------------------------------------------------------------------------------
635  private static string FormatTimestamp()
636  {
637  int year, month, day;
638  System.GetYearMonthDay(year, month, day);
639  string smonth, sday;
640  if (month < 10)
641  smonth = string.Format("0%1", month);
642  else
643  smonth = string.Format("%1", month);
644 
645  if (day < 10)
646  sday = string.Format("0%1", day);
647  else
648  sday = string.Format("%1", day);
649 
650  int h, m, s;
651  System.GetHourMinuteSecond(h, m, s);
652  string sh, sm, ss;
653  if (h < 10)
654  sh = string.Format("0%1", h);
655  else
656  sh = string.Format("%1", h);
657 
658  if (m < 10)
659  sm = string.Format("0%1", m);
660  else
661  sm = string.Format("%1", m);
662  if (s < 10)
663  ss = string.Format("0%1", s);
664  else
665  ss = string.Format("%1", s);
666 
667  return string.Format("%1%2%3-%4%5%6", year, smonth, sday, sh, sm, ss);
668  }
669 }
670 
671 [EntityEditorProps(category: "GameDiag", description: "")]
672 class SCR_StatisticsDrawerEntityClass : GenericEntityClass
673 {
674 }
675 
676 class SCR_StatisticsDrawerEntity : GenericEntity
677 {
679  protected ref array<ref SCR_IGameModeRecord> m_aRecords;
680 
682  protected bool m_bUseHistory = false;
683 
685  protected bool m_bNoZBuffer = false;
686 
688  protected float m_fGizmosScaleMultiplier = 1.0;
689 
691  protected int m_iUniquePlayerCount = 0;
692 
694  protected int m_iTargetPlayer = 0;
695 
697  protected bool m_bDraw = true;
698 
700  protected bool m_bDrawDeaths = true;
701 
703  protected bool m_bDrawDeathsInfo = true;
704 
706  protected bool m_bDrawMovement = true;
707 
709  protected bool m_bDrawPlayerNames = true;
710 
712  protected bool m_bDrawAILODs = true;
713 
715  protected bool m_bAutoPlay;
716 
718  protected float m_fPlaybackSpeed = 1.0;
719 
721  protected float m_fCurrentTime;
722 
724  protected float m_fMaxTime;
725 
727  protected float m_fHistoryLength = 10000;
728 
730  protected ref map<int, ref array<ref SCR_IGameModeRecord>> m_aDeathRecords = new map<int, ref array<ref SCR_IGameModeRecord>>();
731 
733  protected ref map<int, ref array<ref SCR_IGameModeRecord>> m_aMovementRecords = new map<int, ref array<ref SCR_IGameModeRecord>>();
734 
736  protected ref map<int, ref array<ref SCR_IGameModeRecord>> m_aSpawnRecords = new map<int, ref array<ref SCR_IGameModeRecord>>();
737 
739  protected ref array<ref SCR_IGameModeRecord> m_aConnectionRecords = {};
740 
742  protected ref map<string, ref array<ref SCR_IGameModeRecord>> m_aAILODRecords = new map<string, ref array<ref SCR_IGameModeRecord>>();
743 
744  protected ref array<ref Shape> m_aShapes = {};
745  protected ref array<ref DebugText> m_aTexts = {};
746 
748  protected ref map<int, string> m_mPlayerNames = new map<int, string>();
749 
750  protected vector m_vMins;
751  protected vector m_vMaxs;
752 
753  private static const float MAX_MOVEMENT_SPEED = 11.1;
755  protected static const float TELEPORT_DISTANCE = MAX_MOVEMENT_SPEED * (SCR_GameModeStatistics.RECORD_INTERVAL_MS / 1000.0);
756  protected static const float TELEPORT_DISTANCE_SQ = TELEPORT_DISTANCE * TELEPORT_DISTANCE;
757 
759  protected static const int PLAYER_COLORS_COUNT = 27;
760  protected static const vector PLAYER_COLORS_ARRAY[PLAYER_COLORS_COUNT] = {
761  "255 255 255", "169 244 0", "244 67 54", "233 30 99", "156 39 176", "63 81 181", "0 188 212", "0 150 136", "76 175 80", "205 220 57",
762  "255 235 59", "255 152 0", "121 85 72", "95 125 139","62 69 81","3 159 244","244 67 54","233 30 99", "156 39 176",
763  "63 81 181", "0 188 212", "0 150 136", "76 175 80", "205 220 57", "255 235 59", "255 157 0", "121 85 139"
764  };
765 
766  protected static const vector AILOD_COLORS_ARRAY[11] = {
767  "255 255 255",
768  "244 225 244",
769  "233 195 233",
770  "222 165 222",
771  "211 135 211",
772  "200 105 200",
773  "189 75 189",
774  "178 45 178",
775  "167 15 167",
776  "156 0 156",
777  "0 0 0"
778  };
779 
780  protected bool m_bFactionColors = false;
781 
782  //------------------------------------------------------------------------------------------------
784  protected bool IsEmpty()
785  {
786  if (!m_aRecords || m_aRecords.Count() < 1)
787  return true;
788 
789  return false;
790  }
791 
792  //------------------------------------------------------------------------------------------------
794  protected bool OpenStatisticsFile(string filename)
795  {
796  m_aRecords = {};
797  int count = SCR_GameModeStatistics.OpenStatistics(filename, m_aRecords);
798  return count > 0;
799  }
800 
801  //------------------------------------------------------------------------------------------------
803  protected void Clear()
804  {
805  m_aShapes.Clear();
806  m_aTexts.Clear();
807  m_aDeathRecords.Clear();
808  m_aMovementRecords.Clear();
809  m_aConnectionRecords.Clear();
810  m_aAILODRecords.Clear();
811  m_vMins = vector.Zero;
812  m_vMaxs = vector.Zero;
813  m_iTargetPlayer = 0;
814  }
815 
816  //------------------------------------------------------------------------------------------------
818  protected void Close()
819  {
820  Clear();
821  m_aRecords.Clear();
822  }
823 
824  //------------------------------------------------------------------------------------------------
826  protected void LoadRecords(array<ref SCR_IGameModeRecord> records)
827  {
828  // Remove any previous entry
829  Clear();
830 
831  array<ref SCR_IGameModeRecord> deaths = {};
832  SCR_GameModeStatistics.FilterRecordsRefRef(SCR_DeathRecord, records, deaths);
833 
834  array<ref SCR_IGameModeRecord> movement = {};
835  SCR_GameModeStatistics.FilterRecordsRefRef(SCR_MovementRecord, records, movement);
836 
837  array<ref SCR_IGameModeRecord> lodRecords = {};
838  SCR_GameModeStatistics.FilterRecordsRefRef(SCR_AILODRecord, records, lodRecords);
839 
840  SCR_GameModeStatistics.FilterRecordsRefRef(SCR_ConnectionRecord, records, m_aConnectionRecords);
841 
842  array<ref SCR_IGameModeRecord> spawn = {};
843  SCR_GameModeStatistics.FilterRecordsRefRef(SCR_SpawnRecord, records, spawn);
844  // Values used for finding "bounding box"
845  vector max = "-999999 -999999 -999999";
846  vector min = "999999 999999 999999";
847 
848  // Sort movement per player id
849  m_aMovementRecords.Clear();
850  foreach (SCR_IGameModeRecord record : movement)
851  {
852  SCR_MovementRecord movementRecord = SCR_MovementRecord.Cast(record);
853  if (!m_aMovementRecords.Contains(movementRecord.m_PlayerId))
854  {
855  // Initialize the array inside first
856  m_aMovementRecords.Insert(movementRecord.m_PlayerId, new array<ref SCR_IGameModeRecord>());
857  }
858 
859  // Insert record for that player
860  m_aMovementRecords[movementRecord.m_PlayerId].Insert(movementRecord);
861 
862  // Additionally check the current position against previously known mins/maxes
863  for (int i = 0; i < 3; i++)
864  {
865  if (movementRecord.m_Position[i] < min[i])
866  min[i] = movementRecord.m_Position[i];
867 
868  if (movementRecord.m_Position[i] > max[i])
869  max[i] = movementRecord.m_Position[i];
870  }
871  }
872 
873  // Sort deaths per player id
874  m_aDeathRecords.Clear();
875  foreach (SCR_IGameModeRecord record : deaths)
876  {
877  SCR_DeathRecord deathRecord = SCR_DeathRecord.Cast(record);
878  if (!m_aDeathRecords.Contains(deathRecord.m_PlayerId))
879  {
880  // Initialize the array inside first
881  m_aDeathRecords.Insert(deathRecord.m_PlayerId, new array<ref SCR_IGameModeRecord>());
882  }
883 
884  // Insert record for that player
885  m_aDeathRecords[deathRecord.m_PlayerId].Insert(deathRecord);
886  }
887 
888 
889  // Sort deaths per player id
890  m_aSpawnRecords.Clear();
891  foreach (SCR_IGameModeRecord record : spawn)
892  {
893  SCR_SpawnRecord spawnRecord = SCR_SpawnRecord.Cast(record);
894  if (!m_aSpawnRecords.Contains(spawnRecord.m_PlayerId))
895  {
896  // Initialize the array inside first
897  m_aSpawnRecords.Insert(spawnRecord.m_PlayerId, new array<ref SCR_IGameModeRecord>());
898  }
899 
900  // Insert record for that player
901  m_aSpawnRecords[spawnRecord.m_PlayerId].Insert(spawnRecord);
902  }
903 
904  // Sort LODs per EntityID
905  m_aAILODRecords.Clear();
906  foreach (SCR_IGameModeRecord record : lodRecords)
907  {
908  SCR_AILODRecord lodRecord = SCR_AILODRecord.Cast(record);
909  if (!m_aAILODRecords.Contains(lodRecord.m_ID))
910  {
911  // Initialize the array inside first
912  m_aAILODRecords.Insert(lodRecord.m_ID, new array<ref SCR_IGameModeRecord>());
913  }
914 
915  // Insert record for that player
916  m_aAILODRecords[lodRecord.m_ID].Insert(lodRecord);
917  }
918 
919  // Get records duration
920  if (records.Count() > 0)
921  m_fMaxTime = records[records.Count() - 1].GetTimestamp();
922  else
923  m_fMaxTime = 0.0;
924 
925  // Fill player names map
926  m_iUniquePlayerCount = 0;
927  m_mPlayerNames.Clear();
928  for (int i = 0, count = m_aConnectionRecords.Count(); i < count; ++i)
929  {
930  SCR_ConnectionRecord record = SCR_ConnectionRecord.Cast(m_aConnectionRecords[i]);
931  int playerId = record.m_PlayerId;
932  if (playerId <= 0)
933  continue;
934 
935  if (!m_mPlayerNames.Contains(playerId))
936  {
937  string name = "Unnamed";
938  if (!record.m_PlayerName.IsEmpty())
939  name = record.m_PlayerName;
940 
941  m_mPlayerNames.Insert(playerId, name);
942  ++m_iUniquePlayerCount;
943  }
944  }
945 
946  m_vMins = min;
947  m_vMaxs = max;
948  }
949 
950  //------------------------------------------------------------------------------------------------
952  private bool DrawCheck(string label, inout bool outValue)
953  {
954  bool previousValue = outValue;
955  DbgUI.Check(label, outValue);
956  return previousValue != outValue;
957  }
958 
959  //------------------------------------------------------------------------------------------------
961  private bool DrawSlider(string label, inout float outValue, float min, float max, int pxWidth)
962  {
963  float previousValue = outValue;
964  DbgUI.SliderFloat(label, outValue, min, max, pxWidth);
965  return previousValue != outValue;
966  }
967 
968  //------------------------------------------------------------------------------------------------
970  private bool DrawAdvancementButton(string negativeLabel, string positiveLabel, inout float refValue, float addValue)
971  {
972  float previousValue = refValue;
973  if (DbgUI.Button(negativeLabel)) refValue = refValue - addValue;
974  DbgUI.SameLine();
975  if (DbgUI.Button(positiveLabel)) refValue = refValue + addValue;
976  return (previousValue != refValue);
977  }
978 
979  //------------------------------------------------------------------------------------------------
980  protected override void EOnFrame(IEntity owner, float timeSlice)
981  {
982  bool isDirty = false;
983  int pxWidth = 300;
984  int spacerHeight = 32;
985 
986  DbgUI.Begin("GameMode Statistics");
987  {
988  // File selection
989  if (IsEmpty())
990  {
991  DbgUI.Text("File to open: ");
992  string filename = "MyFile.gmstats";
993  DbgUI.InputText("", filename, pxWidth);
994  DbgUI.SameLine();
995  if (DbgUI.Button("Open"))
996  {
997  // Open file and create runtime data.
998  // Mark as dirty so all items are drawn
999  OpenStatisticsFile(filename);
1000  LoadRecords(m_aRecords);
1001 
1002  // Usually there would be isDirty = true,
1003  // but to prevent crazy scopes, we directly repaint and leave
1004  Repaint();
1005  }
1006 
1007  DbgUI.Text("No data, load statistics first!");
1008 
1009  DbgUI.End();
1010  return;
1011  }
1012 
1013  // Closing file
1014  if (DbgUI.Button("Close"))
1015  {
1016  Close();
1017 
1018  // Reset playback
1019  m_fCurrentTime = 0.0;
1020  m_bAutoPlay = false;
1021 
1022  // No need to repaint, items are cleared
1023  DbgUI.End();
1024  return;
1025  }
1026 
1027  DbgUI.Text(string.Format("Total Records: %1", m_aRecords.Count()));
1028 
1029  // Individual filters and properties
1030  DbgUI.Spacer(spacerHeight);
1031  DbgUI.Text("Properties");
1032  if (DrawCheck("Enabled", m_bDraw)) isDirty = true;
1033  if (DrawCheck("Draw Death Records", m_bDrawDeaths)) isDirty = true;
1034  if (m_bDrawDeaths && DrawCheck("Draw Death Infoboxes", m_bDrawDeathsInfo)) isDirty = true;
1035  if (DrawCheck("Draw Movement Records", m_bDrawMovement)) isDirty = true;
1036  if (DrawCheck("Draw Player List", m_bDrawPlayerNames)) isDirty = true;
1037  if (DrawCheck("Draw AILODs", m_bDrawAILODs)) isDirty = true;
1038  if (DrawCheck("Limit History Length", m_bUseHistory)) isDirty = true;
1039  if (DrawCheck("No Z Buffer", m_bNoZBuffer)) isDirty = true;
1040  if (DrawCheck("Faction Colors", m_bFactionColors)) isDirty = true;
1041  if (DrawSlider("Gizmos Scale", m_fGizmosScaleMultiplier, 0.1, 25.0, pxWidth)) isDirty = true;
1042  if (DbgUI.Button("Re-center camera"))
1043  FocusCamera(m_vMins, m_vMaxs);
1044 
1045  // Draw playback and history specific properties
1046  if (m_bUseHistory)
1047  {
1048  DbgUI.Text("History Length = " + m_fHistoryLength * 0.001 + "s");
1049  if (DrawSlider("History Length", m_fHistoryLength, 100, 1000000, pxWidth)) isDirty = true;
1050 
1051  DbgUI.Spacer(spacerHeight);
1052  DbgUI.Text("Playback");
1053  DbgUI.Text(string.Format("Time: %1 / %2 s", m_fCurrentTime * 0.001, m_fMaxTime * 0.001));
1054 
1055  string autoPlayText;
1056  if (m_bAutoPlay)
1057  autoPlayText = "Pause";
1058  else
1059  autoPlayText = "Play";
1060 
1061  // Play/Pause
1062  if (DbgUI.Button(autoPlayText))
1063  m_bAutoPlay = !m_bAutoPlay;
1064  // Stop
1065  DbgUI.SameLine();
1066  if (DbgUI.Button("Stop"))
1067  {
1068  m_bAutoPlay = false;
1069  m_fCurrentTime = 0.0;
1070  isDirty = true;
1071  }
1072 
1073  // Playback speed
1074  if (DrawSlider("Playback Speed: " + m_fPlaybackSpeed + "x", m_fPlaybackSpeed, 0.0, 16.0, pxWidth)) {}
1075 
1076  // Seeking
1077  int seekTime;
1078  DbgUI.InputInt("Seek Time: (s)", seekTime, pxWidth);
1079  DbgUI.SameLine();
1080  if (DbgUI.Button("Seek"))
1081  {
1082  m_fCurrentTime = seekTime * 1000.0;
1083  isDirty = true;
1084  }
1085 
1086  // Draw manual advancement buttons
1087  if (DrawAdvancementButton("-1s", "+1s", m_fCurrentTime, 1000)) isDirty = true;
1088  DbgUI.SameLine();
1089  if (DrawAdvancementButton("-2s", "+2s", m_fCurrentTime, 2000)) isDirty = true;
1090  DbgUI.SameLine();
1091  if (DrawAdvancementButton("-5s", "+5s", m_fCurrentTime, 5000)) isDirty = true;
1092 
1093  if (DrawAdvancementButton("-10s", "+10s", m_fCurrentTime, 10000)) isDirty = true;
1094  DbgUI.SameLine();
1095  if (DrawAdvancementButton("-30s", "+30s", m_fCurrentTime, 30000)) isDirty = true;
1096  DbgUI.SameLine();
1097  if (DrawAdvancementButton("-60s", "+60s", m_fCurrentTime, 60000)) isDirty = true;
1098 
1099  if (DrawAdvancementButton("-100s", "+100s", m_fCurrentTime, 100000)) isDirty = true;
1100  DbgUI.SameLine();
1101  if (DrawAdvancementButton("-300s", "+300s", m_fCurrentTime, 300000)) isDirty = true;
1102  DbgUI.SameLine();
1103  if (DrawAdvancementButton("-600s", "+600s", m_fCurrentTime, 600000)) isDirty = true;
1104 
1105  // Clamp time to reasonable values
1106  m_fCurrentTime = Math.Clamp(m_fCurrentTime, 0, m_fMaxTime);
1107 
1108  // Advance playback if enabled
1109  if (m_bAutoPlay)
1110  {
1111  m_fCurrentTime += timeSlice * m_fPlaybackSpeed * 1000.0; // 1 second per second
1112  isDirty = true;
1113  }
1114  }
1115 
1116  }
1117  DbgUI.End();
1118 
1119  // Draw player list with utilities
1120  if (m_bDrawPlayerNames)
1121  if (DrawPlayerList(pxWidth, spacerHeight)) isDirty = true;
1122 
1123  // When dirty, re-load and filter records + repaint all shapes
1124  if (isDirty)
1125  {
1126  Repaint();
1127  }
1128  }
1129 
1130  //------------------------------------------------------------------------------------------------
1131  protected void Repaint()
1132  {
1133  // Let all shapes begone!
1134  m_aShapes.Clear();
1135  m_aTexts.Clear();
1136  // And do not create new ones
1137  if (!m_bDraw)
1138  return;
1139 
1140  // Unless drawing something...
1141  if (m_bDrawMovement)
1142  RepaintMovement(m_iTargetPlayer);
1143 
1144  if (m_bDrawDeaths)
1145  RepaintDeaths(m_iTargetPlayer);
1146 
1147  if (m_bDrawAILODs)
1148  RepaintAILODs();
1149 
1150  }
1151 
1152  //------------------------------------------------------------------------------------------------
1153  protected bool DrawPlayerList(int pxWidth = 150, int spacerHeight = 16)
1154  {
1155  bool isDirty = false;
1156  DbgUI.Begin("Player List");
1157  {
1158  // Build text to display based on filtering
1159  string filterText = "Filter : ";
1160  if (m_iTargetPlayer <= 0)
1161  filterText += "All players";
1162  else
1163  {
1164  // Use target player name
1165  if (m_mPlayerNames.Contains(m_iTargetPlayer))
1166  filterText += m_mPlayerNames[m_iTargetPlayer];
1167  else
1168  filterText += "N/A";
1169 
1170  // Append id to text
1171  filterText = string.Format("%1 (Id: %2)", filterText, m_iTargetPlayer);
1172  }
1173 
1174  // Make sure to propagate changes to caller
1175  int previousValue = m_iTargetPlayer;
1176  DbgUI.Text(filterText);
1177  if (DrawSlider("", m_iTargetPlayer, 0, m_iUniquePlayerCount, pxWidth)) isDirty = true;
1178 
1179  // Offset player list
1180  DbgUI.Spacer(spacerHeight);
1181  DbgUI.Text("Players: ");
1182  foreach (int playerId, string playerName : m_mPlayerNames)
1183  {
1184  string playerText = string.Format("%1: %2", playerId, playerName);
1185 
1186  // Draw color panel to
1187  vector rgb = PLAYER_COLORS_ARRAY[ playerId % PLAYER_COLORS_COUNT ];
1188  int color = ARGB(255, rgb[0], rgb[1], rgb[2]);
1189  DbgUI.Panel(playerText+"_panel", 16, 16, color);
1190  DbgUI.SameLine();
1191  // Player id/name itself
1192  DbgUI.Text(playerText);
1193  }
1194  }
1195  DbgUI.End();
1196  return isDirty;
1197  }
1198 
1199  //------------------------------------------------------------------------------------------------
1200  protected float GetAlpha(float timeStamp)
1201  {
1202  return Math.InverseLerp(m_fCurrentTime - m_fHistoryLength, m_fCurrentTime, timeStamp);
1203  }
1204 
1205  //------------------------------------------------------------------------------------------------
1206  protected int GetShapeFlags()
1207  {
1208  int flags = ShapeFlags.TRANSP;
1209  if (m_bNoZBuffer)
1210  flags |= ShapeFlags.NOZBUFFER;
1211 
1212  return flags;
1213  }
1214 
1215  //------------------------------------------------------------------------------------------------
1216  protected void RepaintAILODs()
1217  {
1218  vector characterRGB = PLAYER_COLORS_ARRAY[0];
1219  vector groupRGB = PLAYER_COLORS_ARRAY[1];
1220  int color;
1221  int shapeFlags = GetShapeFlags();
1222  float baseSphereScale = m_fGizmosScaleMultiplier * 0.25;
1223  float baseCylinderScale = m_fGizmosScaleMultiplier * 2.5;
1224  foreach(array<ref SCR_IGameModeRecord> lodRecords : m_aAILODRecords)
1225  {
1226 
1227  for (int j = 0; j < lodRecords.Count(); j++)
1228  {
1229  SCR_AILODRecord aiRecord = SCR_AILODRecord.Cast(lodRecords[j]);
1230  SCR_AILODRecord prevAiRecord;
1231  if (j)
1232  prevAiRecord = SCR_AILODRecord.Cast(lodRecords[j-1]);
1233  float timeStamp = aiRecord.GetTimestamp();
1234  float alpha = 1.0;
1235 
1236  if (m_bUseHistory)
1237  {
1238  // We can skip if record is in the future
1239  if (timeStamp > m_fCurrentTime)
1240  break;
1241 
1242  // Otherwise blend out
1243  alpha = GetAlpha(timeStamp);
1244  if (alpha <= 0.0 || alpha > 1.0) // If history is used and item is <almost> completely transparent, skip drawing it
1245  {
1246  continue; // additionally skip items which are further than now
1247  }
1248  }
1249  vector LODcolor = AILOD_COLORS_ARRAY[aiRecord.m_LOD];
1250  color = ARGB(255 * alpha, LODcolor[0], LODcolor[1], LODcolor[2]);
1251 
1252  float scaleAlpha = Math.Lerp(0.7, 1.0, alpha); // 70-100% scale based on age
1253  if (aiRecord.m_Type == ETypeOfAgent.Group)
1254  {
1255  ref Shape cylinder = Shape.CreateCylinder(color, shapeFlags | ShapeFlags.NOOUTLINE, aiRecord.m_Position, baseSphereScale * scaleAlpha, baseCylinderScale * scaleAlpha);
1256  m_aShapes.Insert(cylinder);
1257  }
1258  else if ((aiRecord.m_Type == ETypeOfAgent.Agent))
1259  {
1260  ref Shape sphere = Shape.CreateSphere(color, shapeFlags | ShapeFlags.NOOUTLINE, aiRecord.m_Position, baseSphereScale * scaleAlpha);
1261  m_aShapes.Insert(sphere);
1262  }
1263  else if ((aiRecord.m_Type == ETypeOfAgent.Bird))
1264  {
1265  vector points[7];
1266  points[0] = aiRecord.m_Position - Vector(m_fGizmosScaleMultiplier, 0, 0);
1267  points[1] = aiRecord.m_Position + Vector(m_fGizmosScaleMultiplier, 0, 0);
1268  points[2] = aiRecord.m_Position - Vector(0, m_fGizmosScaleMultiplier, 0);
1269  points[3] = aiRecord.m_Position + Vector(0, m_fGizmosScaleMultiplier, 0);
1270  points[4] = aiRecord.m_Position - Vector(0, 0, m_fGizmosScaleMultiplier);
1271  points[5] = aiRecord.m_Position + Vector(0, 0, m_fGizmosScaleMultiplier);
1272  points[6] = aiRecord.m_Position - Vector(m_fGizmosScaleMultiplier, 0, 0);
1273  ref Shape sphere = Shape.CreateLines(color, shapeFlags | ShapeFlags.NOOUTLINE, points, 7);
1274  m_aShapes.Insert(sphere);
1275  }
1276  else
1277  {
1278  ref Shape cylinder = Shape.CreateCylinder(ARGB(255 * alpha, 0xff, 0xff, 0 ),
1279  shapeFlags | ShapeFlags.NOOUTLINE, aiRecord.m_Position, baseSphereScale * scaleAlpha, baseCylinderScale * scaleAlpha);
1280  m_aShapes.Insert(cylinder);
1281  }
1282  int btCounterNum = aiRecord.m_BTCounter;
1283  int percCounterNum = aiRecord.m_PerceptionCounter;
1284  if (!btCounterNum && !percCounterNum)
1285  continue;
1286  if (prevAiRecord)
1287  {
1288  btCounterNum -= prevAiRecord.m_BTCounter;
1289  percCounterNum -= prevAiRecord.m_PerceptionCounter;
1290  }
1291  string text = string.Format("%1,%2", btCounterNum, percCounterNum);
1292  vector mat[4];
1293  mat[0] = "1 0 0";
1294  mat[1] = "0 1 0";
1295  mat[2] = "0 0 1";
1296  mat[3] = aiRecord.m_Position + 3 * vector.Up;
1297 
1298  ref DebugText textShape = DebugTextWorldSpace.CreateInWorld(GetWorld(), text, DebugTextFlags.FACE_CAMERA | DebugTextFlags.CENTER, mat, 0.5, color, 0xFF111111);
1299  m_aTexts.Insert(textShape);
1300  }
1301  }
1302  }
1303 
1304  //------------------------------------------------------------------------------------------------
1305  protected void RepaintDeaths(int filterPlayerId = -1)
1306  {
1307  const int black50A = ARGB(100, 0, 0, 0); // fallback color, so we don't call ARGB for each iteration
1308  int shapeFlags = GetShapeFlags();
1309 
1310  foreach (int playerId, array<ref SCR_IGameModeRecord> playerRecords : m_aDeathRecords)
1311  {
1312  // Draw elements only for target player
1313  if (filterPlayerId > 0)
1314  {
1315  if (filterPlayerId != playerId)
1316  continue;
1317  }
1318 
1319  array<ref SCR_IGameModeRecord> spawnRecords= m_aSpawnRecords.Get(playerId);
1320  int spawnId = 0;
1321 
1322  const vector extents = "0.25 0.25 0.25";
1323  vector scaledExtents = m_fGizmosScaleMultiplier * extents;
1324  foreach (SCR_IGameModeRecord genericRecord : playerRecords)
1325  {
1326  SCR_DeathRecord record = SCR_DeathRecord.Cast(genericRecord); // We assume filtering is proper
1327  float timeStamp = record.GetTimestamp();
1328  float alpha = 1.0;
1329 
1330  if (m_bUseHistory)
1331  {
1332  // We can skip if record is in the future
1333  if (timeStamp > m_fCurrentTime)
1334  continue;
1335 
1336  // Otherwise blend out
1337  alpha = GetAlpha(timeStamp);
1338  if (alpha <= 0.0 || alpha > 1.0) // If history is used and item is <almost> completely transparent, skip drawing it
1339  continue; // additionally skip items which are further than now
1340  }
1341 
1342  int instigatorColor = black50A;
1343 
1344  vector victimRGB = PLAYER_COLORS_ARRAY[ record.m_PlayerId % PLAYER_COLORS_COUNT ];
1345  int victimColor;
1346  if (m_bFactionColors && spawnRecords)
1347  {
1348  while(spawnId > -1 && spawnRecords[spawnId].GetTimestamp() > timeStamp)
1349  spawnId--;
1350  while(spawnId+1 < spawnRecords.Count() && spawnRecords[spawnId+1].GetTimestamp() < timeStamp)
1351  spawnId++;
1352  victimColor = SCR_SpawnRecord.Cast(spawnRecords[spawnId]).m_FactionColor;
1353  }
1354  else
1355  victimColor = ARGB(255 * alpha, victimRGB[0], victimRGB[1], victimRGB[2]);
1356 
1357  // The position at which kill was instigated from or self position if no instigator
1358  vector instigatorPosition;
1359 
1360  if (m_bDrawDeathsInfo)
1361  {
1362 
1363  // Text communicating the means of death
1364  string deathText;
1365 
1366  // Try to fetch player name for victim
1367  string victimName = "Unknown";
1368  if (m_mPlayerNames.Contains(record.m_PlayerId))
1369  victimName = m_mPlayerNames[record.m_PlayerId];
1370 
1371  // Try to fetch player name for instigator
1372  string instigatorName = "Unknown";
1373  if (record.m_InstigatorId > 0)
1374  if (m_mPlayerNames.Contains(record.m_InstigatorId))
1375  instigatorName = m_mPlayerNames[record.m_InstigatorId];
1376 
1377  // Draw an arrow from instigator origin towards victim origin, if posible
1378  // also skip suicidal cases, these do not need any
1379  if (record.m_InstigatorId != record.m_PlayerId &&
1380  record.m_InstigatorPosition != record.m_Position)
1381  {
1382  vector instigatorRGB = PLAYER_COLORS_ARRAY[ record.m_InstigatorId % PLAYER_COLORS_COUNT ];
1383  instigatorColor = ARGB(alpha * 255, instigatorRGB[0], instigatorRGB[1], instigatorRGB[2]);
1384 
1385  ref Shape arrowShape = Shape.CreateArrow(record.m_InstigatorPosition, record.m_Position, 0.5, instigatorColor, shapeFlags);
1386  m_aShapes.Insert(arrowShape);
1387 
1388  float distance = vector.Distance(record.m_InstigatorPosition, record.m_Position);
1389  deathText = string.Format("Victim: %1\nFaction: %2\n\nInstigator: %3\nFaction: %4\n\nDistance: %5m\nWeapon: %6\nTime: %7s",
1390  victimName,
1391  record.m_Faction,
1392  instigatorName,
1393  record.m_InstigatorFaction,
1394  distance,
1395  typename.EnumToString(EWeaponType, record.m_WeaponType),
1396  timeStamp * 0.001
1397  ); // for ms -> s
1398 
1399  instigatorPosition = record.m_InstigatorPosition;
1400  }
1401  // In other case we explain that the user most likely died of unknown means or suicide
1402  else
1403  {
1404  deathText = string.Format("Victim: %1\nFaction:(%2)\n\nInstigator: (Self/Unknown)\n\nTime: %3s",
1405  victimName,
1406  record.m_Faction,
1407  timeStamp * 0.001
1408  );
1409 
1410  // Instigator is self
1411  instigatorPosition = record.m_Position;
1412  instigatorColor = victimColor;
1413  }
1414 
1415  // Background needs to interpolate color based on history as well
1416  int backgroundColor = ARGB(100 * alpha, 0.0, 0.0, 0.0);
1417 
1418  // Create text halway between instigator and victim position
1419  vector textPosition = vector.Lerp(record.m_Position, instigatorPosition, 0.5) + "0 0.1 0"; // Add slight offset upwards to prevent certain clipping issues
1420  ref DebugText textShape = DebugTextWorldSpace.Create(GetWorld(), deathText, DebugTextFlags.FACE_CAMERA | DebugTextFlags.CENTER, textPosition[0], textPosition[1], textPosition[2], 16.0, color: instigatorColor, bgColor: backgroundColor);
1421  m_aTexts.Insert(textShape);
1422  }
1423 
1424  // Draw an obvious shape (box) at the position of death
1425  ref Shape deathShape = Shape.Create(ShapeType.BBOX, victimColor, shapeFlags, -scaledExtents, scaledExtents);
1426  vector transformation[4];
1427  Math3D.MatrixIdentity3(transformation);
1428  transformation[3] = record.m_Position;
1429  deathShape.SetMatrix(transformation);
1430 
1431  m_aShapes.Insert(deathShape);
1432  }
1433  }
1434  }
1435 
1436  //------------------------------------------------------------------------------------------------
1437  protected void RepaintMovement(int filterPlayerId = -1)
1438  {
1439  int shapeFlags = GetShapeFlags();
1440  float baseSphereScale = m_fGizmosScaleMultiplier * 0.125;
1441  float baseArrowScale = m_fGizmosScaleMultiplier * 0.25;
1442  foreach (int playerId, array<ref SCR_IGameModeRecord> playerRecords : m_aMovementRecords)
1443  {
1444  // Draw elements only for target player
1445  if (filterPlayerId > 0)
1446  {
1447  if (filterPlayerId != playerId)
1448  continue;
1449  }
1450 
1451 
1452  array<ref SCR_IGameModeRecord> spawnRecords= m_aSpawnRecords.Get(playerId);
1453  int spawnId = 0;
1454 
1455  for (int i = 0, count = playerRecords.Count(); i < count - 1; i++)
1456  {
1457  SCR_MovementRecord record = SCR_MovementRecord.Cast(playerRecords[i]);
1458  float timeStamp = record.GetTimestamp();
1459  float alpha = 1.0;
1460 
1461  SCR_MovementRecord nextRecord = SCR_MovementRecord.Cast(playerRecords[i+1]);
1462 
1463  if (m_bUseHistory)
1464  {
1465  // We can skip if record is in the future
1466  if (timeStamp > m_fCurrentTime)
1467  continue;
1468 
1469  // Otherwise blend out
1470  alpha = GetAlpha(timeStamp);
1471  if (alpha <= 0.0 || alpha > 1.0) // If history is used and item is <almost> completely transparent, skip drawing it
1472  continue; // additionally skip items which are further than now
1473  }
1474 
1475 
1476  // Skip teleports
1477  if (vector.DistanceSqXZ(record.m_Position, nextRecord.m_Position) >= TELEPORT_DISTANCE_SQ)
1478  continue;
1479 
1480  vector playerRGB = PLAYER_COLORS_ARRAY[ record.m_PlayerId % PLAYER_COLORS_COUNT ];
1481  int playerColor;
1482  if (m_bFactionColors && spawnRecords)
1483  {
1484  while(spawnId > -1 && spawnRecords[spawnId].GetTimestamp() > timeStamp)
1485  spawnId--;
1486  while(spawnId+1 < spawnRecords.Count() && spawnRecords[spawnId+1].GetTimestamp() < timeStamp)
1487  spawnId++;
1488  playerColor = SCR_SpawnRecord.Cast(spawnRecords[spawnId]).m_FactionColor;
1489  }
1490  else
1491  playerColor = ARGB(255 * alpha, playerRGB[0], playerRGB[1], playerRGB[2]);
1492 
1493  // Try to differentiate players by picking different colors
1494 
1495  // Draw balls on visited position
1496  float scaleAlpha = Math.Lerp(0.7, 1.0, alpha); // 70-100% scale based on age
1497  ref Shape arrowShape = Shape.CreateArrow(record.m_Position, nextRecord.m_Position, baseArrowScale * scaleAlpha, playerColor, shapeFlags);
1498  ref Shape sphereShape = Shape.CreateSphere(playerColor, shapeFlags | ShapeFlags.NOOUTLINE, record.m_Position, baseSphereScale * scaleAlpha);
1499  m_aShapes.Insert(arrowShape);
1500  m_aShapes.Insert(sphereShape);
1501  }
1502  }
1503  }
1504 
1505  //------------------------------------------------------------------------------------------------
1506  protected void FocusCamera(vector mins, vector maxs, float height = 100.0)
1507  {
1508  CameraManager cameraManager = GetGame().GetCameraManager();
1509  if (!cameraManager)
1510  return;
1511 
1512  CameraBase camera = cameraManager.CurrentCamera();
1513  if (!camera)
1514  return;
1515 
1516  vector center = vector.Lerp(mins, maxs, 0.5);
1517  vector position = Vector(center[0], center[1] + height, center[2]);
1518 
1519  vector transform[4];
1520  Math3D.AnglesToMatrix("0 -89.9 0", transform);
1521  transform[3] = position;
1522 
1523  camera.SetWorldTransform(transform);
1524  }
1525 
1526  //------------------------------------------------------------------------------------------------
1527  // constructor
1530  void SCR_StatisticsDrawerEntity(IEntitySource src, IEntity parent)
1531  {
1532  SetEventMask(EntityEvent.INIT | EntityEvent.FRAME);
1533  Activate();
1534  }
1535 
1536  //------------------------------------------------------------------------------------------------
1537  // destructor
1538  void ~SCR_StatisticsDrawerEntity()
1539  {
1540  if (m_aRecords)
1541  Close();
1542  }
1543 }
1544 #endif
m_Position
private vector m_Position
Definition: UnitDisplayComponent.c:17
EntityEditorProps
enum EQueryType EntityEditorProps(category:"GameScripted/Sound", description:"THIS IS THE SCRIPT DESCRIPTION.", color:"0 0 255 255")
Definition: SCR_AmbientSoundsComponent.c:12
Clear
void Clear(GenericEntity entity)
Definition: SCR_GadgetManagerComponent.c:105
GetGame
ArmaReforgerScripted GetGame()
Definition: game.c:1424
EOnFrame
override void EOnFrame(IEntity owner, float timeSlice)
Definition: SCR_PlayerProfileManagerComponent.c:199
GenericEntity
SCR_GenericBoxEntityClass GenericEntity
Instigator
Definition: Instigator.c:6
Spawn
override IEntity Spawn(IEntity owner, Physics parentPhysics, SCR_HitInfo hitInfo, bool snapToTerrain=false)
Definition: SCR_DestructionBaseComponent.c:815
m_vMins
vector m_vMins
Definition: SCR_BuildingSoundComponent.c:15
distance
float distance
Definition: SCR_DestructibleTreeV2.c:29
BaseWeaponComponent
Definition: BaseWeaponComponent.c:12
m_Type
protected EEditableEntityType m_Type
Definition: SCR_EntitiesToolbarEditorUIComponent.c:3
Activate
protected void Activate()
Definition: SCR_BaseHintCondition.c:9
m_vMaxs
vector m_vMaxs
Definition: SCR_BuildingSoundComponent.c:18
Faction
Definition: Faction.c:12
m_fTimestamp
float m_fTimestamp
Definition: SCR_AITargetInfo.c:22
SCR_AIGroup
Definition: SCR_AIGroup.c:68
type
EDamageType type
Definition: SCR_DestructibleTreeV2.c:32
EWeaponType
EWeaponType
Definition: EWeaponType.c:12
EntityUtils
Definition: EntityUtils.c:12
m_Faction
Faction m_Faction
Definition: SCR_AITargetInfo.c:18
position
vector position
Definition: SCR_DestructibleTreeV2.c:30
category
params category
Definition: SCR_VehicleDamageManagerComponent.c:180