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[] = "22";
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
442void CTeeHistorian::RecordPlayerInput(int ClientId, uint32_t UniqueClientId, const CNetObj_PlayerInput *pInput)
443{
444 CTeehistorianPacker Buffer;
445
446 CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientId];
447 CNetObj_PlayerInput DiffInput;
448 if(pPrev->m_UniqueClientId == UniqueClientId)
449 {
450 if(mem_comp(a: &pPrev->m_Input, b: pInput, size: sizeof(pPrev->m_Input)) == 0)
451 {
452 return;
453 }
454 EnsureTickWritten();
455 Buffer.Reset();
456
457 Buffer.AddInt(i: -TEEHISTORIAN_INPUT_DIFF);
458 CSnapshotDelta::DiffItem(pPast: (int *)&pPrev->m_Input, pCurrent: (int *)pInput, pOut: (int *)&DiffInput, Size: sizeof(DiffInput) / sizeof(int32_t));
459 if(m_Debug)
460 {
461 const int *pData = (const int *)&DiffInput;
462 dbg_msg(sys: "teehistorian", fmt: "diff_input cid=%d %d %d %d %d %d %d %d %d %d %d", ClientId,
463 pData[0], pData[1], pData[2], pData[3], pData[4],
464 pData[5], pData[6], pData[7], pData[8], pData[9]);
465 }
466 }
467 else
468 {
469 EnsureTickWritten();
470 Buffer.Reset();
471 Buffer.AddInt(i: -TEEHISTORIAN_INPUT_NEW);
472 DiffInput = *pInput;
473 if(m_Debug)
474 {
475 dbg_msg(sys: "teehistorian", fmt: "new_input cid=%d", ClientId);
476 }
477 }
478 Buffer.AddInt(i: ClientId);
479 for(size_t i = 0; i < sizeof(DiffInput) / sizeof(int32_t); i++)
480 {
481 Buffer.AddInt(i: ((int *)&DiffInput)[i]);
482 }
483 pPrev->m_UniqueClientId = UniqueClientId;
484 pPrev->m_Input = *pInput;
485
486 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
487}
488
489void CTeeHistorian::RecordPlayerMessage(int ClientId, const void *pMsg, int MsgSize)
490{
491 EnsureTickWritten();
492
493 CTeehistorianPacker Buffer;
494 Buffer.Reset();
495 Buffer.AddInt(i: -TEEHISTORIAN_MESSAGE);
496 Buffer.AddInt(i: ClientId);
497 Buffer.AddInt(i: MsgSize);
498 Buffer.AddRaw(pData: pMsg, Size: MsgSize);
499
500 if(m_Debug)
501 {
502 CUnpacker Unpacker;
503 Unpacker.Reset(pData: pMsg, Size: MsgSize);
504 int MsgId = Unpacker.GetInt();
505 int Sys = MsgId & 1;
506 MsgId >>= 1;
507 dbg_msg(sys: "teehistorian", fmt: "msg cid=%d sys=%d msgid=%d", ClientId, Sys, MsgId);
508 }
509
510 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
511}
512
513void CTeeHistorian::RecordPlayerJoin(int ClientId, int Protocol)
514{
515 dbg_assert(Protocol == PROTOCOL_6 || Protocol == PROTOCOL_7, "invalid version");
516 EnsureTickWritten();
517
518 {
519 CTeehistorianPacker Buffer;
520 Buffer.Reset();
521 Buffer.AddInt(i: ClientId);
522 if(m_Debug)
523 {
524 dbg_msg(sys: "teehistorian", fmt: "joinver%d cid=%d", Protocol == PROTOCOL_6 ? 6 : 7, ClientId);
525 }
526 CUuid Uuid = Protocol == PROTOCOL_6 ? UUID_TEEHISTORIAN_JOINVER6 : UUID_TEEHISTORIAN_JOINVER7;
527 WriteExtra(Uuid, pData: Buffer.Data(), DataSize: Buffer.Size());
528 }
529
530 CTeehistorianPacker Buffer;
531 Buffer.Reset();
532 Buffer.AddInt(i: -TEEHISTORIAN_JOIN);
533 Buffer.AddInt(i: ClientId);
534
535 if(m_Debug)
536 {
537 dbg_msg(sys: "teehistorian", fmt: "join cid=%d", ClientId);
538 }
539
540 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
541}
542
543void CTeeHistorian::RecordPlayerRejoin(int ClientId)
544{
545 EnsureTickWritten();
546
547 CTeehistorianPacker Buffer;
548 Buffer.Reset();
549 Buffer.AddInt(i: ClientId);
550
551 if(m_Debug)
552 {
553 dbg_msg(sys: "teehistorian", fmt: "player_rejoin cid=%d", ClientId);
554 }
555
556 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_REJOIN, pData: Buffer.Data(), DataSize: Buffer.Size());
557}
558
559void CTeeHistorian::RecordPlayerReady(int ClientId)
560{
561 EnsureTickWritten();
562
563 CTeehistorianPacker Buffer;
564 Buffer.Reset();
565 Buffer.AddInt(i: ClientId);
566
567 if(m_Debug)
568 {
569 dbg_msg(sys: "teehistorian", fmt: "player_ready cid=%d", ClientId);
570 }
571
572 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_READY, pData: Buffer.Data(), DataSize: Buffer.Size());
573}
574
575void CTeeHistorian::RecordPlayerDrop(int ClientId, const char *pReason)
576{
577 EnsureTickWritten();
578
579 CTeehistorianPacker Buffer;
580 Buffer.Reset();
581 Buffer.AddInt(i: -TEEHISTORIAN_DROP);
582 Buffer.AddInt(i: ClientId);
583 Buffer.AddString(pStr: pReason, Limit: 0);
584
585 if(m_Debug)
586 {
587 dbg_msg(sys: "teehistorian", fmt: "drop cid=%d reason='%s'", ClientId, pReason);
588 }
589
590 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
591}
592
593void CTeeHistorian::RecordPlayerName(int ClientId, const char *pName)
594{
595 EnsureTickWritten();
596
597 CTeehistorianPacker Buffer;
598 Buffer.Reset();
599 Buffer.AddInt(i: ClientId);
600 Buffer.AddString(pStr: pName, Limit: 0);
601
602 if(m_Debug)
603 {
604 dbg_msg(sys: "teehistorian", fmt: "player_name cid=%d name='%s'", ClientId, pName);
605 }
606
607 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_NAME, pData: Buffer.Data(), DataSize: Buffer.Size());
608}
609
610void CTeeHistorian::RecordConsoleCommand(int ClientId, int FlagMask, const char *pCmd, IConsole::IResult *pResult)
611{
612 EnsureTickWritten();
613
614 CTeehistorianPacker Buffer;
615 Buffer.Reset();
616 Buffer.AddInt(i: -TEEHISTORIAN_CONSOLE_COMMAND);
617 Buffer.AddInt(i: ClientId);
618 Buffer.AddInt(i: FlagMask);
619 Buffer.AddString(pStr: pCmd, Limit: 0);
620 Buffer.AddInt(i: pResult->NumArguments());
621 for(int i = 0; i < pResult->NumArguments(); i++)
622 {
623 Buffer.AddString(pStr: pResult->GetString(Index: i), Limit: 0);
624 }
625
626 if(m_Debug)
627 {
628 dbg_msg(sys: "teehistorian", fmt: "ccmd cid=%d cmd='%s'", ClientId, pCmd);
629 }
630
631 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
632}
633
634void CTeeHistorian::RecordTestExtra()
635{
636 if(m_Debug)
637 {
638 dbg_msg(sys: "teehistorian", fmt: "test");
639 }
640
641 WriteExtra(Uuid: UUID_TEEHISTORIAN_TEST, pData: "", DataSize: 0);
642}
643
644void CTeeHistorian::RecordPlayerSwap(int ClientId1, int ClientId2)
645{
646 EnsureTickWritten();
647
648 CTeehistorianPacker Buffer;
649 Buffer.Reset();
650 Buffer.AddInt(i: ClientId1);
651 Buffer.AddInt(i: ClientId2);
652
653 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_SWITCH, pData: Buffer.Data(), DataSize: Buffer.Size());
654}
655
656void CTeeHistorian::RecordTeamSaveSuccess(int Team, CUuid SaveId, const char *pTeamSave)
657{
658 EnsureTickWritten();
659
660 CTeehistorianPacker Buffer;
661 Buffer.Reset();
662 Buffer.AddInt(i: Team);
663 Buffer.AddRaw(pData: &SaveId, Size: sizeof(SaveId));
664 Buffer.AddString(pStr: pTeamSave, Limit: 0);
665
666 if(m_Debug)
667 {
668 char aSaveId[UUID_MAXSTRSIZE];
669 FormatUuid(Uuid: SaveId, pBuffer: aSaveId, BufferLength: sizeof(aSaveId));
670 dbg_msg(sys: "teehistorian", fmt: "save_success team=%d save_id=%s team_save='%s'", Team, aSaveId, pTeamSave);
671 }
672
673 WriteExtra(Uuid: UUID_TEEHISTORIAN_SAVE_SUCCESS, pData: Buffer.Data(), DataSize: Buffer.Size());
674}
675
676void CTeeHistorian::RecordTeamSaveFailure(int Team)
677{
678 EnsureTickWritten();
679
680 CTeehistorianPacker Buffer;
681 Buffer.Reset();
682 Buffer.AddInt(i: Team);
683
684 if(m_Debug)
685 {
686 dbg_msg(sys: "teehistorian", fmt: "save_failure team=%d", Team);
687 }
688
689 WriteExtra(Uuid: UUID_TEEHISTORIAN_SAVE_FAILURE, pData: Buffer.Data(), DataSize: Buffer.Size());
690}
691
692void CTeeHistorian::RecordTeamLoadSuccess(int Team, CUuid SaveId, const char *pTeamSave)
693{
694 EnsureTickWritten();
695
696 CTeehistorianPacker Buffer;
697 Buffer.Reset();
698 Buffer.AddInt(i: Team);
699 Buffer.AddRaw(pData: &SaveId, Size: sizeof(SaveId));
700 Buffer.AddString(pStr: pTeamSave, Limit: 0);
701
702 if(m_Debug)
703 {
704 char aSaveId[UUID_MAXSTRSIZE];
705 FormatUuid(Uuid: SaveId, pBuffer: aSaveId, BufferLength: sizeof(aSaveId));
706 dbg_msg(sys: "teehistorian", fmt: "load_success team=%d save_id=%s team_save='%s'", Team, aSaveId, pTeamSave);
707 }
708
709 WriteExtra(Uuid: UUID_TEEHISTORIAN_LOAD_SUCCESS, pData: Buffer.Data(), DataSize: Buffer.Size());
710}
711
712void CTeeHistorian::RecordTeamLoadFailure(int Team)
713{
714 EnsureTickWritten();
715
716 CTeehistorianPacker Buffer;
717 Buffer.Reset();
718 Buffer.AddInt(i: Team);
719
720 if(m_Debug)
721 {
722 dbg_msg(sys: "teehistorian", fmt: "load_failure team=%d", Team);
723 }
724
725 WriteExtra(Uuid: UUID_TEEHISTORIAN_LOAD_FAILURE, pData: Buffer.Data(), DataSize: Buffer.Size());
726}
727
728void CTeeHistorian::EndInputs()
729{
730 dbg_assert(m_State == STATE_INPUTS, "invalid teehistorian state");
731
732 m_State = STATE_BEFORE_ENDTICK;
733}
734
735void CTeeHistorian::EndTick()
736{
737 dbg_assert(m_State == STATE_BEFORE_ENDTICK, "invalid teehistorian state");
738 m_State = STATE_BEFORE_TICK;
739}
740
741void CTeeHistorian::RecordDDNetVersionOld(int ClientId, int DDNetVersion)
742{
743 CTeehistorianPacker Buffer;
744 Buffer.Reset();
745 Buffer.AddInt(i: ClientId);
746 Buffer.AddInt(i: DDNetVersion);
747
748 if(m_Debug)
749 {
750 dbg_msg(sys: "teehistorian", fmt: "ddnetver_old cid=%d ddnet_version=%d", ClientId, DDNetVersion);
751 }
752
753 WriteExtra(Uuid: UUID_TEEHISTORIAN_DDNETVER_OLD, pData: Buffer.Data(), DataSize: Buffer.Size());
754}
755
756void CTeeHistorian::RecordDDNetVersion(int ClientId, CUuid ConnectionId, int DDNetVersion, const char *pDDNetVersionStr)
757{
758 CTeehistorianPacker Buffer;
759 Buffer.Reset();
760 Buffer.AddInt(i: ClientId);
761 Buffer.AddRaw(pData: &ConnectionId, Size: sizeof(ConnectionId));
762 Buffer.AddInt(i: DDNetVersion);
763 Buffer.AddString(pStr: pDDNetVersionStr, Limit: 0);
764
765 if(m_Debug)
766 {
767 char aConnectionId[UUID_MAXSTRSIZE];
768 FormatUuid(Uuid: ConnectionId, pBuffer: aConnectionId, BufferLength: sizeof(aConnectionId));
769 dbg_msg(sys: "teehistorian", fmt: "ddnetver cid=%d connection_id=%s ddnet_version=%d ddnet_version_str=%s", ClientId, aConnectionId, DDNetVersion, pDDNetVersionStr);
770 }
771
772 WriteExtra(Uuid: UUID_TEEHISTORIAN_DDNETVER, pData: Buffer.Data(), DataSize: Buffer.Size());
773}
774
775void CTeeHistorian::RecordAuthInitial(int ClientId, int Level, const char *pAuthName)
776{
777 CTeehistorianPacker Buffer;
778 Buffer.Reset();
779 Buffer.AddInt(i: ClientId);
780 Buffer.AddInt(i: Level);
781 Buffer.AddString(pStr: pAuthName, Limit: 0);
782
783 if(m_Debug)
784 {
785 dbg_msg(sys: "teehistorian", fmt: "auth_init cid=%d level=%d auth_name=%s", ClientId, Level, pAuthName);
786 }
787
788 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_INIT, pData: Buffer.Data(), DataSize: Buffer.Size());
789}
790
791void CTeeHistorian::RecordAuthLogin(int ClientId, int Level, const char *pAuthName)
792{
793 CTeehistorianPacker Buffer;
794 Buffer.Reset();
795 Buffer.AddInt(i: ClientId);
796 Buffer.AddInt(i: Level);
797 Buffer.AddString(pStr: pAuthName, Limit: 0);
798
799 if(m_Debug)
800 {
801 dbg_msg(sys: "teehistorian", fmt: "auth_login cid=%d level=%d auth_name=%s", ClientId, Level, pAuthName);
802 }
803
804 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_LOGIN, pData: Buffer.Data(), DataSize: Buffer.Size());
805}
806
807void CTeeHistorian::RecordAuthLogout(int ClientId)
808{
809 CTeehistorianPacker Buffer;
810 Buffer.Reset();
811 Buffer.AddInt(i: ClientId);
812
813 if(m_Debug)
814 {
815 dbg_msg(sys: "teehistorian", fmt: "auth_logout cid=%d", ClientId);
816 }
817
818 WriteExtra(Uuid: UUID_TEEHISTORIAN_AUTH_LOGOUT, pData: Buffer.Data(), DataSize: Buffer.Size());
819}
820
821void CTeeHistorian::RecordAntibot(const void *pData, int DataSize)
822{
823 if(m_Debug)
824 {
825 dbg_msg(sys: "teehistorian", fmt: "antibot data_size=%d", DataSize);
826 }
827
828 WriteExtra(Uuid: UUID_TEEHISTORIAN_ANTIBOT, pData, DataSize);
829}
830
831void CTeeHistorian::RecordPlayerFinish(int ClientId, int TimeTicks)
832{
833 CTeehistorianPacker Buffer;
834 Buffer.Reset();
835 Buffer.AddInt(i: ClientId);
836 Buffer.AddInt(i: TimeTicks);
837 if(m_Debug)
838 {
839 dbg_msg(sys: "teehistorian", fmt: "player_finish cid=%d time=%d", ClientId, TimeTicks);
840 }
841
842 WriteExtra(Uuid: UUID_TEEHISTORIAN_PLAYER_FINISH, pData: Buffer.Data(), DataSize: Buffer.Size());
843}
844
845void CTeeHistorian::RecordTeamFinish(int TeamId, int TimeTicks)
846{
847 CTeehistorianPacker Buffer;
848 Buffer.Reset();
849 Buffer.AddInt(i: TeamId);
850 Buffer.AddInt(i: TimeTicks);
851 if(m_Debug)
852 {
853 dbg_msg(sys: "teehistorian", fmt: "team_finish cid=%d time=%d", TeamId, TimeTicks);
854 }
855
856 WriteExtra(Uuid: UUID_TEEHISTORIAN_TEAM_FINISH, pData: Buffer.Data(), DataSize: Buffer.Size());
857}
858
859void CTeeHistorian::Finish()
860{
861 dbg_assert(m_State == STATE_START || m_State == STATE_INPUTS || m_State == STATE_BEFORE_ENDTICK || m_State == STATE_BEFORE_TICK, "invalid teehistorian state");
862
863 if(m_State == STATE_INPUTS)
864 {
865 EndInputs();
866 }
867 if(m_State == STATE_BEFORE_ENDTICK)
868 {
869 EndTick();
870 }
871
872 CTeehistorianPacker Buffer;
873 Buffer.Reset();
874 Buffer.AddInt(i: -TEEHISTORIAN_FINISH);
875
876 if(m_Debug)
877 {
878 dbg_msg(sys: "teehistorian", fmt: "finish");
879 }
880
881 Write(pData: Buffer.Data(), DataSize: Buffer.Size());
882}
883