Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
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
10enum SCR_EGameModeRecordType
11{
12 Death,
13 Movement,
14 Connection,
15 Spawn,
16 AILOD,
17}
18
19enum ETypeOfAgent // TODO: SCR_ETypeOfAgent / SCR_EAgentType
20{
21 Group,
22 Agent,
23 Bird,
24 Player
25}
26
28class 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
128sealed class SCR_DeathRecord : SCR_IGameModeRecord
129{
130 vector m_Position;
131 vector m_InstigatorPosition;
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
224sealed 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
267sealed 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
306sealed 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
347sealed 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
423class 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: "")]
672class SCR_StatisticsDrawerEntityClass : GenericEntityClass
673{
674}
675
676class 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
SCR_EAIThreatSectorFlags flags
string FormatTimestamp()
string FactionKey
Faction unique identifier type.
Definition FactionKey.c:2
ArmaReforgerScripted GetGame()
Definition game.c:1398
void SCR_AIGroup(IEntitySource src, IEntity parent)
float m_fTimestamp
Faction m_Faction
IEntity Spawn(IEntity owner, Physics parentPhysics, SCR_HitInfo hitInfo, bool snapToTerrain=false)
Spawns the object.
enum SCR_ECompassType EntityEditorProps(category:"GameScripted/Gadgets", description:"Compass", color:"0 0 255 255")
Prefab data class for compass component.
float distance
EDamageType type
vector position
EEditableEntityType m_Type
Get all prefabs that have the spawner the given labels and are valid in the editor mode param catalogType Type to catalog to get prefabs from param editorMode Editor mode to get valid entries from param faction Faction(Optional)
override bool Save(notnull ScriptBitWriter writer)
ref array< ref Shape > m_aShapes
proto native void Close()
enum EVehicleType IEntity
proto external void Activate()
proto external EntityEvent SetEventMask(EntityEvent e)
proto external Managed FindComponent(typename typeName)
proto external vector GetOrigin()
void EOnFrame(IEntity owner, float timeSlice)
proto external BaseWorld GetWorld()
void Load()
Definition gameLib.c:220
ShapeType
Definition ShapeType.c:13
DebugTextFlags
ShapeFlags
Definition ShapeFlags.c:13
EntityEvent
Various entity events.
Definition EntityEvent.c:14
FileMode
Mode for opening file. See FileSystem::Open.
Definition FileMode.c:14
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
proto native vector Vector(float x, float y, float z)
proto native void Clear()
Remove all calls from list.
proto native bool IsEmpty()
void Debug()
Definition Types.c:327
EWeaponType
Definition EWeaponType.c:13
vector m_Position
Only in XY, Z is ignored.
Definition EnWidgets.c:81
proto int ARGB(int a, int r, int g, int b)