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