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