1#include "teehistorian.h"
2
3#include <base/dbg.h>
4#include <base/mem.h>
5#include <base/str.h>
6#include <base/time.h>
7
8#include <engine/shared/config.h>
9#include <engine/shared/json.h>
10#include <engine/shared/packer.h>
11#include <engine/shared/snapshot.h>
12
13#include <game/gamecore.h>
14
15class CTeehistorianPacker : public CAbstractPacker
16{
17public:
18 CTeehistorianPacker() :
19 CAbstractPacker(m_aBuffer, sizeof(m_aBuffer))
20 {
21 }
22
23private:
24 unsigned char m_aBuffer[1024 * 64];
25};
26
27static const char TEEHISTORIAN_NAME[] = "teehistorian@ddnet.tw";
28static const CUuid TEEHISTORIAN_UUID = CalculateUuid(pName: TEEHISTORIAN_NAME);
29static const char TEEHISTORIAN_VERSION[] = "2";
30static const char TEEHISTORIAN_VERSION_MINOR[] = "20";
31
32#define UUID(id, name) static const CUuid UUID_##id = CalculateUuid(name);
33#include <engine/shared/teehistorian_ex_chunks.h>
34#undef UUID
35
36enum
37{
38 TEEHISTORIAN_NONE,
39 TEEHISTORIAN_FINISH,
40 TEEHISTORIAN_TICK_SKIP,
41 TEEHISTORIAN_PLAYER_NEW,
42 TEEHISTORIAN_PLAYER_OLD,
43 TEEHISTORIAN_INPUT_DIFF,
44 TEEHISTORIAN_INPUT_NEW,
45 TEEHISTORIAN_MESSAGE,
46 TEEHISTORIAN_JOIN,
47 TEEHISTORIAN_DROP,
48 TEEHISTORIAN_CONSOLE_COMMAND,
49 TEEHISTORIAN_EX,
50};
51
52CTeeHistorian::CTeeHistorian()
53{
54 m_State = STATE_START;
55 m_pfnWriteCallback = nullptr;
56 m_pWriteCallbackUserdata = nullptr;
57}
58
59void CTeeHistorian::Reset(const CGameInfo *pGameInfo, WRITE_CALLBACK pfnWriteCallback, void *pUser)
60{
61 dbg_assert(m_State == STATE_START || m_State == STATE_BEFORE_TICK, "invalid teehistorian state");
62
63 m_Debug = 0;
64
65 m_Tick = 0;
66 m_LastWrittenTick = 0;
67 // Tick 0 is implicit at the start, game starts as tick 1.
68 m_TickWritten = true;
69 m_MaxClientId = MAX_CLIENTS;
70
71 // `m_PrevMaxClientId` is initialized in `BeginPlayers`
72 for(auto &PrevPlayer : m_aPrevPlayers)
73 {
74 PrevPlayer.m_Alive = false;
75 // zero means no id
76 PrevPlayer.m_UniqueClientId = 0;
77 PrevPlayer.m_Team = 0;
78 }
79 for(auto &PrevTeam : m_aPrevTeams)
80 {
81 PrevTeam.m_Practice = false;
82 }
83 m_pfnWriteCallback = pfnWriteCallback;
84 m_pWriteCallbackUserdata = pUser;
85
86 WriteHeader(pGameInfo);
87
88 m_State = STATE_START;
89}
90
91void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
92{
93 Write(pData: &TEEHISTORIAN_UUID, DataSize: sizeof(TEEHISTORIAN_UUID));
94
95 char aGameUuid[UUID_MAXSTRSIZE];
96 char aStartTime[128];
97 char aMapSha256[SHA256_MAXSTRSIZE];
98
99 FormatUuid(Uuid: pGameInfo->m_GameUuid, pBuffer: aGameUuid, BufferLength: sizeof(aGameUuid));
100 str_timestamp_ex(time: pGameInfo->m_StartTime, buffer: aStartTime, buffer_size: sizeof(aStartTime), format: "%Y-%m-%dT%H:%M:%S%z");
101 sha256_str(digest: pGameInfo->m_MapSha256, str: aMapSha256, max_len: sizeof(aMapSha256));
102
103 char aPrevGameUuid[UUID_MAXSTRSIZE];
104 char aPrevGameUuidJson[64];
105 if(pGameInfo->m_HavePrevGameUuid)
106 {
107 FormatUuid(Uuid: pGameInfo->m_PrevGameUuid, pBuffer: aPrevGameUuid, BufferLength: sizeof(aPrevGameUuid));
108 str_format(buffer: aPrevGameUuidJson, buffer_size: sizeof(aPrevGameUuidJson), format: "\"prev_game_uuid\":\"%s\",", aPrevGameUuid);
109 }
110 else
111 {
112 aPrevGameUuidJson[0] = 0;
113 }
114
115 char aCommentBuffer[128];
116 char aServerVersionBuffer[128];
117 char aStartTimeBuffer[128];
118 char aServerNameBuffer[128];
119 char aGameTypeBuffer[128];
120 char aMapNameBuffer[128];
121 char aMapSha256Buffer[256];
122 char aPrngDescription[128];
123
124 char aJson[2048];
125
126#define E(buf, str) EscapeJson(buf, sizeof(buf), str)
127
128 str_format(buffer: aJson, buffer_size: sizeof(aJson),
129 format: "{"
130 "\"comment\":\"%s\","
131 "\"version\":\"%s\","
132 "\"version_minor\":\"%s\","
133 "\"game_uuid\":\"%s\","
134 "%s"
135 "\"server_version\":\"%s\","
136 "\"start_time\":\"%s\","
137 "\"server_name\":\"%s\","
138 "\"server_port\":\"%d\","
139 "\"game_type\":\"%s\","
140 "\"map_name\":\"%s\","
141 "\"map_size\":\"%d\","
142 "\"map_sha256\":\"%s\","
143 "\"map_crc\":\"%08x\","
144 "\"prng_description\":\"%s\","
145 "\"config\":{",
146 E(aCommentBuffer, TEEHISTORIAN_NAME),
147 TEEHISTORIAN_VERSION,
148 TEEHISTORIAN_VERSION_MINOR,
149 aGameUuid,
150 aPrevGameUuidJson,
151 E(aServerVersionBuffer, pGameInfo->m_pServerVersion),
152 E(aStartTimeBuffer, aStartTime),
153 E(aServerNameBuffer, pGameInfo->m_pServerName),
154 pGameInfo->m_ServerPort,
155 E(aGameTypeBuffer, pGameInfo->m_pGameType),
156 E(aMapNameBuffer, pGameInfo->m_pMapName),
157 pGameInfo->m_MapSize,
158 E(aMapSha256Buffer, aMapSha256),
159 pGameInfo->m_MapCrc,
160 E(aPrngDescription, pGameInfo->m_pPrngDescription));
161 Write(pData: aJson, DataSize: str_length(str: aJson));
162
163 char aBuffer1[1024];
164 char aBuffer2[1024];
165 bool First = true;
166
167#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Flags, Desc) \
168 if((Flags) & CFGFLAG_SERVER && !((Flags) & CFGFLAG_NONTEEHISTORIC) && pGameInfo->m_pConfig->m_##Name != (Def)) \
169 { \
170 str_format(aJson, sizeof(aJson), "%s\"%s\":\"%d\"", \
171 First ? "" : ",", \
172 E(aBuffer1, #ScriptName), \
173 pGameInfo->m_pConfig->m_##Name); \
174 Write(aJson, str_length(aJson)); \
175 First = false; \
176 }
177
178#define MACRO_CONFIG_COL(Name, ScriptName, Def, Flags, Desc) MACRO_CONFIG_INT(Name, ScriptName, Def, 0, 0, Flags, Desc)
179
180#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Flags, Desc) \
181 if((Flags) & CFGFLAG_SERVER && !((Flags) & CFGFLAG_NONTEEHISTORIC) && str_comp(pGameInfo->m_pConfig->m_##Name, (Def)) != 0) \
182 { \
183 str_format(aJson, sizeof(aJson), "%s\"%s\":\"%s\"", \
184 First ? "" : ",", \
185 E(aBuffer1, #ScriptName), \
186 E(aBuffer2, pGameInfo->m_pConfig->m_##Name)); \
187 Write(aJson, str_length(aJson)); \
188 First = false; \
189 }
190
191#include <engine/shared/config_variables.h>
192
193#undef MACRO_CONFIG_INT
194#undef MACRO_CONFIG_COL
195#undef MACRO_CONFIG_STR
196
197 str_copy(dst&: aJson, src: "},\"tuning\":{");
198 Write(pData: aJson, DataSize: str_length(str: aJson));
199
200 First = true;
201
202#define MACRO_TUNING_PARAM(Name, ScriptName, Value, Description) \
203 if(pGameInfo->m_pTuning->m_##Name.Get() != (int)((Value) * 100)) \
204 { \
205 str_format(aJson, sizeof(aJson), "%s\"%s\":\"%d\"", \
206 First ? "" : ",", \
207 E(aBuffer1, #ScriptName), \
208 pGameInfo->m_pTuning->m_##Name.Get()); \
209 Write(aJson, str_length(aJson)); \
210 First = false; \
211 }
212#include <game/tuning.h>
213#undef MACRO_TUNING_PARAM
214
215 str_copy(dst&: aJson, src: "},\"uuids\":[");
216 Write(pData: aJson, DataSize: str_length(str: aJson));
217
218 for(int i = 0; i < pGameInfo->m_pUuids->NumUuids(); i++)
219 {
220 str_format(buffer: aJson, buffer_size: sizeof(aJson), format: "%s\"%s\"",
221 i == 0 ? "" : ",",
222 E(aBuffer1, pGameInfo->m_pUuids->GetName(OFFSET_UUID + i)));
223 Write(pData: aJson, DataSize: str_length(str: aJson));
224 }
225
226 str_copy(dst&: aJson, src: "]}");
227 Write(pData: aJson, DataSize: str_length(str: aJson));
228 Write(pData: "", DataSize: 1); // Null termination.
229}
230
231void CTeeHistorian::WriteExtra(CUuid Uuid, const void *pData, int DataSize)
232{
233 EnsureTickWritten();
234
235 CTeehistorianPacker Ex;
236 Ex.Reset();
237 Ex.AddInt(i: -TEEHISTORIAN_EX);
238 Ex.AddRaw(pData: &Uuid, Size: sizeof(Uuid));
239 Ex.AddInt(i: DataSize);
240 Write(pData: Ex.Data(), DataSize: Ex.Size());
241 Write(pData, DataSize);
242}
243
244void CTeeHistorian::BeginTick(int Tick)
245{
246 dbg_assert(m_State == STATE_START || m_State == STATE_BEFORE_TICK, "invalid teehistorian state");
247
248 m_Tick = Tick;
249 m_TickWritten = false;
250
251 if(m_Debug > 1)
252 {
253 dbg_msg(sys: "teehistorian", fmt: "tick %d", Tick);
254 }
255
256 m_State = STATE_BEFORE_PLAYERS;
257}
258
259void CTeeHistorian::BeginPlayers()
260{
261 dbg_assert(m_State == STATE_BEFORE_PLAYERS, "invalid teehistorian state");
262
263 m_PrevMaxClientId = m_MaxClientId;
264 // ensure that PLAYER_{DIFF, NEW, OLD} don't cause an implicit tick after a TICK_SKIP
265 // by not overwriting m_MaxClientId during RecordPlayer
266 m_MaxClientId = -1;
267
268 m_State = STATE_PLAYERS;
269}
270
271void CTeeHistorian::EnsureTickWrittenPlayerData(int ClientId)
272{
273 dbg_assert(ClientId > m_MaxClientId, "invalid player data order");
274 m_MaxClientId = ClientId;
275
276 if(!m_TickWritten && (ClientId > m_PrevMaxClientId || m_LastWrittenTick + 1 != m_Tick))
277 {
278 WriteTick();
279 }
280 else
281 {
282 // Tick is implicit.
283 m_LastWrittenTick = m_Tick;
284 m_TickWritten = true;
285 }
286}
287
288void CTeeHistorian::RecordPlayer(int ClientId, const CNetObj_CharacterCore *pChar)
289{
290 dbg_assert(m_State == STATE_PLAYERS, "invalid teehistorian state");
291
292 CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientId];
293 if(!pPrev->m_Alive || pPrev->m_X != pChar->m_X || pPrev->m_Y != pChar->m_Y)
294 {
295 EnsureTickWrittenPlayerData(ClientId);
296
297 CTeehistorianPacker Buffer;
298 Buffer.Reset();
299 if(pPrev->m_Alive)
300 {
301 int dx = pChar->m_X - pPrev->m_X;
302 int dy = pChar->m_Y - pPrev->m_Y;
303 Buffer.AddInt(i: ClientId);
304 Buffer.AddInt(i: dx);
305 Buffer.AddInt(i: dy);
306 if(m_Debug)
307 {
308 dbg_msg(sys: "teehistorian", fmt: "diff cid=%d dx=%d dy=%d", ClientId, dx, dy);
309 }
310 }
311 else
312 {
313 int x = pChar->m_X;
314 int y = pChar->m_Y;
315 Buffer.AddInt(i: -TEEHISTORIAN_PLAYER_NEW);
316 Buffer.AddInt(i: ClientId);
317 Buffer.AddInt(i: x);
318 Buffer.AddInt(i: y);
319 if(m_Debug)
320 {
321 dbg_msg(sys: "teehistorian", fmt: "new cid=%d x=%d y=%d", ClientId, x, y);
322 }
323 }
324 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
325 }
326 pPrev->m_X = pChar->m_X;
327 pPrev->m_Y = pChar->m_Y;
328 pPrev->m_Alive = true;
329}
330
331void CTeeHistorian::RecordDeadPlayer(int ClientId)
332{
333 dbg_assert(m_State == STATE_PLAYERS, "invalid teehistorian state");
334
335 CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientId];
336 if(pPrev->m_Alive)
337 {
338 EnsureTickWrittenPlayerData(ClientId);
339
340 CTeehistorianPacker Buffer;
341 Buffer.Reset();
342 Buffer.AddInt(i: -TEEHISTORIAN_PLAYER_OLD);
343 Buffer.AddInt(i: ClientId);
344 if(m_Debug)
345 {
346 dbg_msg(sys: "teehistorian", fmt: "old cid=%d", ClientId);
347 }
348 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
349 }
350 pPrev->m_Alive = false;
351}
352
353void CTeeHistorian::RecordPlayerTeam(int ClientId, int Team)
354{
355 if(m_aPrevPlayers[ClientId].m_Team != Team)
356 {
357 m_aPrevPlayers[ClientId].m_Team = Team;
358
359 EnsureTickWritten();
360
361 CTeehistorianPacker Buffer;
362 Buffer.Reset();
363 Buffer.AddInt(i: ClientId);
364 Buffer.AddInt(i: Team);
365
366 if(m_Debug)
367 {
368 dbg_msg(sys: "teehistorian", fmt: "player_team cid=%d team=%d", ClientId, Team);
369 }
370
371 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_TEAM, pData: Buffer.Data(), DataSize: Buffer.Size());
372 }
373}
374
375void CTeeHistorian::RecordTeamPractice(int Team, bool Practice)
376{
377 if(m_aPrevTeams[Team].m_Practice != Practice)
378 {
379 m_aPrevTeams[Team].m_Practice = Practice;
380
381 EnsureTickWritten();
382
383 CTeehistorianPacker Buffer;
384 Buffer.Reset();
385 Buffer.AddInt(i: Team);
386 Buffer.AddInt(i: Practice);
387
388 if(m_Debug)
389 {
390 dbg_msg(sys: "teehistorian", fmt: "team_practice team=%d practice=%d", Team, Practice);
391 }
392
393 WriteExtra(Uuid: UUID_TEEHISTORIAN_TEAM_PRACTICE, pData: Buffer.Data(), DataSize: Buffer.Size());
394 }
395}
396
397void CTeeHistorian::Write(const void *pData, int DataSize)
398{
399 m_pfnWriteCallback(pData, DataSize, m_pWriteCallbackUserdata);
400}
401
402void CTeeHistorian::EnsureTickWritten()
403{
404 if(!m_TickWritten)
405 {
406 WriteTick();
407 }
408}
409
410void CTeeHistorian::WriteTick()
411{
412 CTeehistorianPacker TickPacker;
413 TickPacker.Reset();
414
415 const int TickDelta = m_Tick - m_LastWrittenTick - 1;
416 TickPacker.AddInt(i: -TEEHISTORIAN_TICK_SKIP);
417 TickPacker.AddInt(i: TickDelta);
418 if(m_Debug)
419 {
420 dbg_msg(sys: "teehistorian", fmt: "skip_ticks tick_delta=%d", TickDelta);
421 }
422 Write(pData: TickPacker.Data(), DataSize: TickPacker.Size());
423
424 m_TickWritten = true;
425 m_LastWrittenTick = m_Tick;
426}
427
428void CTeeHistorian::EndPlayers()
429{
430 dbg_assert(m_State == STATE_PLAYERS, "invalid teehistorian state");
431
432 m_State = STATE_BEFORE_INPUTS;
433}
434
435void CTeeHistorian::BeginInputs()
436{
437 dbg_assert(m_State == STATE_BEFORE_INPUTS, "invalid teehistorian state");
438
439 m_State = STATE_INPUTS;
440}
441
442template<class T>
443static rust::Slice<const int32_t> AsSlice(const T *pObject)
444{
445 return rust::Slice((const int32_t *)pObject, sizeof(*pObject) / sizeof(int32_t));
446}
447
448template<class T>
449static rust::Slice<int32_t> AsMutSlice(T *pObject)
450{
451 return rust::Slice((int32_t *)pObject, sizeof(*pObject) / sizeof(int32_t));
452}
453
454void CTeeHistorian::RecordPlayerInput(int ClientId, uint32_t UniqueClientId, const CNetObj_PlayerInput *pInput)
455{
456 CTeehistorianPacker Buffer;
457
458 CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientId];
459 CNetObj_PlayerInput DiffInput;
460 if(pPrev->m_UniqueClientId == UniqueClientId)
461 {
462 if(mem_comp(a: &pPrev->m_Input, b: pInput, size: sizeof(pPrev->m_Input)) == 0)
463 {
464 return;
465 }
466 EnsureTickWritten();
467 Buffer.Reset();
468
469 Buffer.AddInt(i: -TEEHISTORIAN_INPUT_DIFF);
470 CSnapshotDelta_DiffItem(past: AsSlice(pObject: &pPrev->m_Input), current: AsSlice(pObject: pInput), out: AsMutSlice(pObject: &DiffInput));
471 if(m_Debug)
472 {
473 const int *pData = (const int *)&DiffInput;
474 dbg_msg(sys: "teehistorian", fmt: "diff_input cid=%d %d %d %d %d %d %d %d %d %d %d", ClientId,
475 pData[0], pData[1], pData[2], pData[3], pData[4],
476 pData[5], pData[6], pData[7], pData[8], pData[9]);
477 }
478 }
479 else
480 {
481 EnsureTickWritten();
482 Buffer.Reset();
483 Buffer.AddInt(i: -TEEHISTORIAN_INPUT_NEW);
484 DiffInput = *pInput;
485 if(m_Debug)
486 {
487 dbg_msg(sys: "teehistorian", fmt: "new_input cid=%d", ClientId);
488 }
489 }
490 Buffer.AddInt(i: ClientId);
491 for(size_t i = 0; i < sizeof(DiffInput) / sizeof(int32_t); i++)
492 {
493 Buffer.AddInt(i: ((int *)&DiffInput)[i]);
494 }
495 pPrev->m_UniqueClientId = UniqueClientId;
496 pPrev->m_Input = *pInput;
497
498 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
499}
500
501void CTeeHistorian::RecordPlayerMessage(int ClientId, const void *pMsg, int MsgSize)
502{
503 EnsureTickWritten();
504
505 CTeehistorianPacker Buffer;
506 Buffer.Reset();
507 Buffer.AddInt(i: -TEEHISTORIAN_MESSAGE);
508 Buffer.AddInt(i: ClientId);
509 Buffer.AddInt(i: MsgSize);
510 Buffer.AddRaw(pData: pMsg, Size: MsgSize);
511
512 if(m_Debug)
513 {
514 CUnpacker Unpacker;
515 Unpacker.Reset(pData: pMsg, Size: MsgSize);
516 int MsgId = Unpacker.GetInt();
517 int Sys = MsgId & 1;
518 MsgId >>= 1;
519 dbg_msg(sys: "teehistorian", fmt: "msg cid=%d sys=%d msgid=%d", ClientId, Sys, MsgId);
520 }
521
522 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
523}
524
525void CTeeHistorian::RecordPlayerJoin(int ClientId, int Protocol)
526{
527 dbg_assert(Protocol == PROTOCOL_6 || Protocol == PROTOCOL_7, "invalid version");
528 EnsureTickWritten();
529
530 {
531 CTeehistorianPacker Buffer;
532 Buffer.Reset();
533 Buffer.AddInt(i: ClientId);
534 if(m_Debug)
535 {
536 dbg_msg(sys: "teehistorian", fmt: "joinver%d cid=%d", Protocol == PROTOCOL_6 ? 6 : 7, ClientId);
537 }
538 CUuid Uuid = Protocol == PROTOCOL_6 ? UUID_TEEHISTORIAN_JOINVER6 : UUID_TEEHISTORIAN_JOINVER7;
539 WriteExtra(Uuid, pData: Buffer.Data(), DataSize: Buffer.Size());
540 }
541
542 CTeehistorianPacker Buffer;
543 Buffer.Reset();
544 Buffer.AddInt(i: -TEEHISTORIAN_JOIN);
545 Buffer.AddInt(i: ClientId);
546
547 if(m_Debug)
548 {
549 dbg_msg(sys: "teehistorian", fmt: "join cid=%d", ClientId);
550 }
551
552 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
553}
554
555void CTeeHistorian::RecordPlayerRejoin(int ClientId)
556{
557 EnsureTickWritten();
558
559 CTeehistorianPacker Buffer;
560 Buffer.Reset();
561 Buffer.AddInt(i: ClientId);
562
563 if(m_Debug)
564 {
565 dbg_msg(sys: "teehistorian", fmt: "player_rejoin cid=%d", ClientId);
566 }
567
568 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_REJOIN, pData: Buffer.Data(), DataSize: Buffer.Size());
569}
570
571void CTeeHistorian::RecordPlayerReady(int ClientId)
572{
573 EnsureTickWritten();
574
575 CTeehistorianPacker Buffer;
576 Buffer.Reset();
577 Buffer.AddInt(i: ClientId);
578
579 if(m_Debug)
580 {
581 dbg_msg(sys: "teehistorian", fmt: "player_ready cid=%d", ClientId);
582 }
583
584 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_READY, pData: Buffer.Data(), DataSize: Buffer.Size());
585}
586
587void CTeeHistorian::RecordPlayerDrop(int ClientId, const char *pReason)
588{
589 EnsureTickWritten();
590
591 CTeehistorianPacker Buffer;
592 Buffer.Reset();
593 Buffer.AddInt(i: -TEEHISTORIAN_DROP);
594 Buffer.AddInt(i: ClientId);
595 Buffer.AddString(pStr: pReason, Limit: 0);
596
597 if(m_Debug)
598 {
599 dbg_msg(sys: "teehistorian", fmt: "drop cid=%d reason='%s'", ClientId, pReason);
600 }
601
602 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
603}
604
605void CTeeHistorian::RecordPlayerName(int ClientId, const char *pName)
606{
607 EnsureTickWritten();
608
609 CTeehistorianPacker Buffer;
610 Buffer.Reset();
611 Buffer.AddInt(i: ClientId);
612 Buffer.AddString(pStr: pName, Limit: 0);
613
614 if(m_Debug)
615 {
616 dbg_msg(sys: "teehistorian", fmt: "player_name cid=%d name='%s'", ClientId, pName);
617 }
618
619 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_NAME, pData: Buffer.Data(), DataSize: Buffer.Size());
620}
621
622void CTeeHistorian::RecordConsoleCommand(int ClientId, int FlagMask, const char *pCmd, IConsole::IResult *pResult)
623{
624 EnsureTickWritten();
625
626 CTeehistorianPacker Buffer;
627 Buffer.Reset();
628 Buffer.AddInt(i: -TEEHISTORIAN_CONSOLE_COMMAND);
629 Buffer.AddInt(i: ClientId);
630 Buffer.AddInt(i: FlagMask);
631 Buffer.AddString(pStr: pCmd, Limit: 0);
632 Buffer.AddInt(i: pResult->NumArguments());
633 for(int i = 0; i < pResult->NumArguments(); i++)
634 {
635 Buffer.AddString(pStr: pResult->GetString(Index: i), Limit: 0);
636 }
637
638 if(m_Debug)
639 {
640 dbg_msg(sys: "teehistorian", fmt: "ccmd cid=%d cmd='%s'", ClientId, pCmd);
641 }
642
643 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
644}
645
646void CTeeHistorian::RecordTestExtra()
647{
648 if(m_Debug)
649 {
650 dbg_msg(sys: "teehistorian", fmt: "test");
651 }
652
653 WriteExtra(Uuid: UUID_TEEHISTORIAN_TEST, pData: "", DataSize: 0);
654}
655
656void CTeeHistorian::RecordPlayerSwap(int ClientId1, int ClientId2)
657{
658 EnsureTickWritten();
659
660 CTeehistorianPacker Buffer;
661 Buffer.Reset();
662 Buffer.AddInt(i: ClientId1);
663 Buffer.AddInt(i: ClientId2);
664
665 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_SWITCH, pData: Buffer.Data(), DataSize: Buffer.Size());
666}
667
668void CTeeHistorian::RecordTeamSaveSuccess(int Team, CUuid SaveId, const char *pTeamSave)
669{
670 EnsureTickWritten();
671
672 CTeehistorianPacker Buffer;
673 Buffer.Reset();
674 Buffer.AddInt(i: Team);
675 Buffer.AddRaw(pData: &SaveId, Size: sizeof(SaveId));
676 Buffer.AddString(pStr: pTeamSave, Limit: 0);
677
678 if(m_Debug)
679 {
680 char aSaveId[UUID_MAXSTRSIZE];
681 FormatUuid(Uuid: SaveId, pBuffer: aSaveId, BufferLength: sizeof(aSaveId));
682 dbg_msg(sys: "teehistorian", fmt: "save_success team=%d save_id=%s team_save='%s'", Team, aSaveId, pTeamSave);
683 }
684
685 WriteExtra(Uuid: UUID_TEEHISTORIAN_SAVE_SUCCESS, pData: Buffer.Data(), DataSize: Buffer.Size());
686}
687
688void CTeeHistorian::RecordTeamSaveFailure(int Team)
689{
690 EnsureTickWritten();
691
692 CTeehistorianPacker Buffer;
693 Buffer.Reset();
694 Buffer.AddInt(i: Team);
695
696 if(m_Debug)
697 {
698 dbg_msg(sys: "teehistorian", fmt: "save_failure team=%d", Team);
699 }
700
701 WriteExtra(Uuid: UUID_TEEHISTORIAN_SAVE_FAILURE, pData: Buffer.Data(), DataSize: Buffer.Size());
702}
703
704void CTeeHistorian::RecordTeamLoadSuccess(int Team, CUuid SaveId, const char *pTeamSave)
705{
706 EnsureTickWritten();
707
708 CTeehistorianPacker Buffer;
709 Buffer.Reset();
710 Buffer.AddInt(i: Team);
711 Buffer.AddRaw(pData: &SaveId, Size: sizeof(SaveId));
712 Buffer.AddString(pStr: pTeamSave, Limit: 0);
713
714 if(m_Debug)
715 {
716 char aSaveId[UUID_MAXSTRSIZE];
717 FormatUuid(Uuid: SaveId, pBuffer: aSaveId, BufferLength: sizeof(aSaveId));
718 dbg_msg(sys: "teehistorian", fmt: "load_success team=%d save_id=%s team_save='%s'", Team, aSaveId, pTeamSave);
719 }
720
721 WriteExtra(Uuid: UUID_TEEHISTORIAN_LOAD_SUCCESS, pData: Buffer.Data(), DataSize: Buffer.Size());
722}
723
724void CTeeHistorian::RecordTeamLoadFailure(int Team)
725{
726 EnsureTickWritten();
727
728 CTeehistorianPacker Buffer;
729 Buffer.Reset();
730 Buffer.AddInt(i: Team);
731
732 if(m_Debug)
733 {
734 dbg_msg(sys: "teehistorian", fmt: "load_failure team=%d", Team);
735 }
736
737 WriteExtra(Uuid: UUID_TEEHISTORIAN_LOAD_FAILURE, pData: Buffer.Data(), DataSize: Buffer.Size());
738}
739
740void CTeeHistorian::EndInputs()
741{
742 dbg_assert(m_State == STATE_INPUTS, "invalid teehistorian state");
743
744 m_State = STATE_BEFORE_ENDTICK;
745}
746
747void CTeeHistorian::EndTick()
748{
749 dbg_assert(m_State == STATE_BEFORE_ENDTICK, "invalid teehistorian state");
750 m_State = STATE_BEFORE_TICK;
751}
752
753void CTeeHistorian::RecordDDNetVersionOld(int ClientId, int DDNetVersion)
754{
755 CTeehistorianPacker Buffer;
756 Buffer.Reset();
757 Buffer.AddInt(i: ClientId);
758 Buffer.AddInt(i: DDNetVersion);
759
760 if(m_Debug)
761 {
762 dbg_msg(sys: "teehistorian", fmt: "ddnetver_old cid=%d ddnet_version=%d", ClientId, DDNetVersion);
763 }
764
765 WriteExtra(Uuid: UUID_TEEHISTORIAN_DDNETVER_OLD, pData: Buffer.Data(), DataSize: Buffer.Size());
766}
767
768void CTeeHistorian::RecordDDNetVersion(int ClientId, CUuid ConnectionId, int DDNetVersion, const char *pDDNetVersionStr)
769{
770 CTeehistorianPacker Buffer;
771 Buffer.Reset();
772 Buffer.AddInt(i: ClientId);
773 Buffer.AddRaw(pData: &ConnectionId, Size: sizeof(ConnectionId));
774 Buffer.AddInt(i: DDNetVersion);
775 Buffer.AddString(pStr: pDDNetVersionStr, Limit: 0);
776
777 if(m_Debug)
778 {
779 char aConnectionId[UUID_MAXSTRSIZE];
780 FormatUuid(Uuid: ConnectionId, pBuffer: aConnectionId, BufferLength: sizeof(aConnectionId));
781 dbg_msg(sys: "teehistorian", fmt: "ddnetver cid=%d connection_id=%s ddnet_version=%d ddnet_version_str=%s", ClientId, aConnectionId, DDNetVersion, pDDNetVersionStr);
782 }
783
784 WriteExtra(Uuid: UUID_TEEHISTORIAN_DDNETVER, pData: Buffer.Data(), DataSize: Buffer.Size());
785}
786
787void CTeeHistorian::RecordAuthInitial(int ClientId, int Level, const char *pAuthName)
788{
789 CTeehistorianPacker Buffer;
790 Buffer.Reset();
791 Buffer.AddInt(i: ClientId);
792 Buffer.AddInt(i: Level);
793 Buffer.AddString(pStr: pAuthName, Limit: 0);
794
795 if(m_Debug)
796 {
797 dbg_msg(sys: "teehistorian", fmt: "auth_init cid=%d level=%d auth_name=%s", ClientId, Level, pAuthName);
798 }
799
800 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_INIT, pData: Buffer.Data(), DataSize: Buffer.Size());
801}
802
803void CTeeHistorian::RecordAuthLogin(int ClientId, int Level, const char *pAuthName)
804{
805 CTeehistorianPacker Buffer;
806 Buffer.Reset();
807 Buffer.AddInt(i: ClientId);
808 Buffer.AddInt(i: Level);
809 Buffer.AddString(pStr: pAuthName, Limit: 0);
810
811 if(m_Debug)
812 {
813 dbg_msg(sys: "teehistorian", fmt: "auth_login cid=%d level=%d auth_name=%s", ClientId, Level, pAuthName);
814 }
815
816 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_LOGIN, pData: Buffer.Data(), DataSize: Buffer.Size());
817}
818
819void CTeeHistorian::RecordAuthLogout(int ClientId)
820{
821 CTeehistorianPacker Buffer;
822 Buffer.Reset();
823 Buffer.AddInt(i: ClientId);
824
825 if(m_Debug)
826 {
827 dbg_msg(sys: "teehistorian", fmt: "auth_logout cid=%d", ClientId);
828 }
829
830 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_LOGOUT, pData: Buffer.Data(), DataSize: Buffer.Size());
831}
832
833void CTeeHistorian::RecordAntibot(const void *pData, int DataSize)
834{
835 if(m_Debug)
836 {
837 dbg_msg(sys: "teehistorian", fmt: "antibot data_size=%d", DataSize);
838 }
839
840 WriteExtra(Uuid: UUID_TEEHISTORIAN_ANTIBOT, pData, DataSize);
841}
842
843void CTeeHistorian::RecordPlayerFinish(int ClientId, int TimeTicks)
844{
845 CTeehistorianPacker Buffer;
846 Buffer.Reset();
847 Buffer.AddInt(i: ClientId);
848 Buffer.AddInt(i: TimeTicks);
849 if(m_Debug)
850 {
851 dbg_msg(sys: "teehistorian", fmt: "player_finish cid=%d time=%d", ClientId, TimeTicks);
852 }
853
854 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_FINISH, pData: Buffer.Data(), DataSize: Buffer.Size());
855}
856
857void CTeeHistorian::RecordTeamFinish(int TeamId, int TimeTicks)
858{
859 CTeehistorianPacker Buffer;
860 Buffer.Reset();
861 Buffer.AddInt(i: TeamId);
862 Buffer.AddInt(i: TimeTicks);
863 if(m_Debug)
864 {
865 dbg_msg(sys: "teehistorian", fmt: "team_finish cid=%d time=%d", TeamId, TimeTicks);
866 }
867
868 WriteExtra(Uuid: UUID_TEEHISTORIAN_TEAM_FINISH, pData: Buffer.Data(), DataSize: Buffer.Size());
869}
870
871void CTeeHistorian::Finish()
872{
873 dbg_assert(m_State == STATE_START || m_State == STATE_INPUTS || m_State == STATE_BEFORE_ENDTICK || m_State == STATE_BEFORE_TICK, "invalid teehistorian state");
874
875 if(m_State == STATE_INPUTS)
876 {
877 EndInputs();
878 }
879 if(m_State == STATE_BEFORE_ENDTICK)
880 {
881 EndTick();
882 }
883
884 CTeehistorianPacker Buffer;
885 Buffer.Reset();
886 Buffer.AddInt(i: -TEEHISTORIAN_FINISH);
887
888 if(m_Debug)
889 {
890 dbg_msg(sys: "teehistorian", fmt: "finish");
891 }
892
893 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
894}
895