1#include <base/dbg.h>
2#include <base/mem.h>
3
4#include <engine/shared/protocolglue.h>
5#include <engine/shared/snapshot.h>
6#include <engine/shared/translation_context.h>
7
8#include <generated/protocol7.h>
9
10#include <game/client/gameclient.h>
11
12int CGameClient::TranslateSnap(CSnapshotBuffer *pSnapDstSix, CSnapshot *pSnapSrcSeven, int Conn, bool Dummy)
13{
14 CSnapshotBuilder Builder;
15 Builder.Init();
16
17 float LocalTime = Client()->LocalTime();
18 int GameTick = Client()->GameTick(Conn: g_Config.m_ClDummy);
19 CTranslationContext &TranslationContext = Client()->m_TranslationContext;
20
21 for(auto &PlayerInfosRace : TranslationContext.m_apPlayerInfosRace)
22 PlayerInfosRace = nullptr;
23
24 int SpectatorId = -3;
25
26 for(int i = 0; i < pSnapSrcSeven->NumItems(); i++)
27 {
28 const CSnapshotItem *pItem7 = (CSnapshotItem *)pSnapSrcSeven->GetItem(Index: i);
29 const int Size = pSnapSrcSeven->GetItemSize(Index: i);
30 const int ItemType = pSnapSrcSeven->GetItemType(Index: i);
31
32 if(ItemType <= 0)
33 {
34 // Don't add extended item type descriptions, they get
35 // added implicitly (== 0).
36 //
37 // Don't add items of unknown item types either (< 0).
38 continue;
39 }
40
41 // ddnet ex snap items
42 if(ItemType >= OFFSET_UUID)
43 {
44 CUnpacker Unpacker;
45 Unpacker.Reset(pData: pItem7->Data(), Size);
46
47 void *pRawObj = GetNetObjHandler()->SecureUnpackObj(Type: ItemType, pUnpacker: &Unpacker);
48 if(!pRawObj)
49 {
50 if(ItemType != UUID_UNKNOWN)
51 dbg_msg(sys: "sixup", fmt: "dropped weird ddnet ex object '%s' (%d), failed on '%s'", GetNetObjHandler()->GetObjName(Type: ItemType), ItemType, GetNetObjHandler()->FailedObjOn());
52 continue;
53 }
54 const int ItemSize = GetNetObjHandler()->GetUnpackedObjSize(Type: ItemType);
55
56 Builder.NewItem(Type: ItemType, Id: pItem7->Id(), pData: pRawObj, Size: ItemSize);
57 continue;
58 }
59
60 if(GetNetObjHandler7()->ValidateObj(Type: ItemType, pData: pItem7->Data(), Size) != 0)
61 {
62 dbg_msg(
63 sys: "sixup",
64 fmt: "invalidated index=%d type=%d (%s) size=%d id=%d",
65 i,
66 ItemType,
67 GetNetObjHandler7()->GetObjName(Type: ItemType),
68 Size,
69 pItem7->Id());
70 pSnapSrcSeven->InvalidateItem(Index: i);
71 continue;
72 }
73
74 if(ItemType == protocol7::NETOBJTYPE_PLAYERINFORACE)
75 {
76 const protocol7::CNetObj_PlayerInfoRace *pInfo = (const protocol7::CNetObj_PlayerInfoRace *)pItem7->Data();
77 int ClientId = pItem7->Id();
78 if(ClientId < MAX_CLIENTS && TranslationContext.m_aClients[ClientId].m_Active)
79 {
80 TranslationContext.m_apPlayerInfosRace[ClientId] = pInfo;
81 }
82 }
83 else if(ItemType == protocol7::NETOBJTYPE_SPECTATORINFO)
84 {
85 const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data();
86 SpectatorId = pSpec7->m_SpectatorId;
87 if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW)
88 SpectatorId = SPEC_FREEVIEW;
89 }
90 }
91
92 // hack to put game info in the snap
93 // even though in 0.7 we get it as a game message
94 // this message has to be on the top
95 // the client looks at the items in order and needs the gameinfo at the top
96 // otherwise it will not render skins with team colors
97 if(TranslationContext.m_ShouldSendGameInfo)
98 {
99 int GameStateFlagsSix = 0;
100 if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_GAMEOVER)
101 GameStateFlagsSix |= GAMESTATEFLAG_GAMEOVER;
102 if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_SUDDENDEATH)
103 GameStateFlagsSix |= GAMESTATEFLAG_SUDDENDEATH;
104 if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_PAUSED)
105 GameStateFlagsSix |= GAMESTATEFLAG_PAUSED;
106
107 /*
108 This is a 0.7 only flag that we just ignore for now
109
110 GAMESTATEFLAG_ROUNDOVER
111 */
112
113 CNetObj_GameInfo Info6 = {};
114 Info6.m_GameFlags = TranslationContext.m_GameFlags;
115 Info6.m_GameStateFlags = GameStateFlagsSix;
116 Info6.m_RoundStartTick = TranslationContext.m_GameStartTick7;
117 Info6.m_WarmupTimer = 0; // removed in 0.7
118 if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_WARMUP)
119 Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick;
120 if(TranslationContext.m_GameStateFlags7 & protocol7::GAMESTATEFLAG_STARTCOUNTDOWN)
121 Info6.m_WarmupTimer = TranslationContext.m_GameStateEndTick7 - GameTick;
122
123 // hack to port 0.7 race timer to ddnet warmup gametimer hack
124 int TimerClientId = TranslationContext.m_aLocalClientId[Conn];
125 if(SpectatorId >= 0)
126 TimerClientId = SpectatorId;
127 const protocol7::CNetObj_PlayerInfoRace *pRaceInfo = TimerClientId == -1 ? nullptr : TranslationContext.m_apPlayerInfosRace[TimerClientId];
128 if(pRaceInfo)
129 {
130 Info6.m_WarmupTimer = -pRaceInfo->m_RaceStartTick;
131 Info6.m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
132 }
133
134 Info6.m_ScoreLimit = TranslationContext.m_ScoreLimit;
135 Info6.m_TimeLimit = TranslationContext.m_TimeLimit;
136 Info6.m_RoundNum = TranslationContext.m_MatchNum;
137 Info6.m_RoundCurrent = TranslationContext.m_MatchCurrent;
138
139 Builder.NewItem(Type: NETOBJTYPE_GAMEINFO, Id: 0, pData: &Info6, Size: sizeof(Info6));
140 }
141
142 for(int i = 0; i < MAX_CLIENTS; i++)
143 {
144 const CTranslationContext::CClientData &Client = TranslationContext.m_aClients[i];
145 if(!Client.m_Active)
146 continue;
147
148 CNetObj_ClientInfo Info6 = {};
149 StrToInts(pInts: Info6.m_aName, NumInts: std::size(Info6.m_aName), pStr: Client.m_aName);
150 StrToInts(pInts: Info6.m_aClan, NumInts: std::size(Info6.m_aClan), pStr: Client.m_aClan);
151 Info6.m_Country = Client.m_Country;
152 StrToInts(pInts: Info6.m_aSkin, NumInts: std::size(Info6.m_aSkin), pStr: "default");
153 Info6.m_UseCustomColor = 0;
154 Info6.m_ColorBody = 0;
155 Info6.m_ColorFeet = 0;
156 Builder.NewItem(Type: NETOBJTYPE_CLIENTINFO, Id: i, pData: &Info6, Size: sizeof(Info6));
157 }
158
159 bool NewGameData = false;
160
161 for(int i = 0; i < pSnapSrcSeven->NumItems(); i++)
162 {
163 const CSnapshotItem *pItem7 = pSnapSrcSeven->GetItem(Index: i);
164 const int ItemType = pSnapSrcSeven->GetItemType(Index: i);
165 const int Size = pSnapSrcSeven->GetItemSize(Index: i);
166 // the first few items are a full match
167 // no translation needed
168 if(ItemType == protocol7::NETOBJTYPE_PROJECTILE ||
169 ItemType == protocol7::NETOBJTYPE_LASER ||
170 ItemType == protocol7::NETOBJTYPE_FLAG)
171 {
172 Builder.NewItem(Type: ItemType, Id: pItem7->Id(), pData: pItem7->Data(), Size);
173 }
174 else if(ItemType == protocol7::NETOBJTYPE_PICKUP)
175 {
176 const protocol7::CNetObj_Pickup *pPickup7 = (const protocol7::CNetObj_Pickup *)pItem7->Data();
177 CNetObj_Pickup Pickup6 = {};
178 Pickup6.m_X = pPickup7->m_X;
179 Pickup6.m_Y = pPickup7->m_Y;
180 PickupType_SevenToSix(Type7: pPickup7->m_Type, Type6&: Pickup6.m_Type, SubType6&: Pickup6.m_Subtype);
181 Builder.NewItem(Type: NETOBJTYPE_PICKUP, Id: pItem7->Id(), pData: &Pickup6, Size: sizeof(Pickup6));
182 }
183 else if(ItemType == protocol7::NETOBJTYPE_GAMEDATA)
184 {
185 const protocol7::CNetObj_GameData *pGameData = (const protocol7::CNetObj_GameData *)pItem7->Data();
186 TranslationContext.m_GameStateFlags7 = pGameData->m_GameStateFlags;
187 TranslationContext.m_GameStartTick7 = pGameData->m_GameStartTick;
188 TranslationContext.m_GameStateEndTick7 = pGameData->m_GameStateEndTick;
189 }
190 else if(ItemType == protocol7::NETOBJTYPE_GAMEDATATEAM)
191 {
192 // 0.7 added GameDataTeam and GameDataFlag
193 // both items merged together have all fields of the 0.6 GameData
194 // so if we get either one of them we store the details in the translation context
195 // and build one GameData snap item after this loop
196 const protocol7::CNetObj_GameDataTeam *pTeam7 = (const protocol7::CNetObj_GameDataTeam *)pItem7->Data();
197
198 TranslationContext.m_TeamscoreRed = pTeam7->m_TeamscoreRed;
199 TranslationContext.m_TeamscoreBlue = pTeam7->m_TeamscoreBlue;
200 NewGameData = true;
201 }
202 else if(ItemType == protocol7::NETOBJTYPE_GAMEDATAFLAG)
203 {
204 const protocol7::CNetObj_GameDataFlag *pFlag7 = (const protocol7::CNetObj_GameDataFlag *)pItem7->Data();
205
206 TranslationContext.m_FlagCarrierRed = pFlag7->m_FlagCarrierRed;
207 TranslationContext.m_FlagCarrierBlue = pFlag7->m_FlagCarrierBlue;
208 NewGameData = true;
209
210 // used for blinking the flags in the hud
211 // but that already works the 0.6 way
212 // and is covered by the NETOBJTYPE_GAMEDATA translation
213 // pFlag7->m_FlagDropTickRed;
214 // pFlag7->m_FlagDropTickBlue;
215 }
216 else if(ItemType == protocol7::NETOBJTYPE_CHARACTER)
217 {
218 const protocol7::CNetObj_Character *pChar7 = (const protocol7::CNetObj_Character *)pItem7->Data();
219
220 CNetObj_Character Char6 = {};
221 // character core is unchanged
222 mem_copy(dest: &Char6, source: pItem7->Data(), size: sizeof(CNetObj_CharacterCore));
223
224 Char6.m_PlayerFlags = 0;
225 if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS)
226 Char6.m_PlayerFlags = PlayerFlags_SevenToSix(Flags: TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7);
227 Char6.m_Health = pChar7->m_Health;
228 Char6.m_Armor = pChar7->m_Armor;
229 Char6.m_AmmoCount = pChar7->m_AmmoCount;
230 Char6.m_Weapon = pChar7->m_Weapon;
231 Char6.m_Emote = pChar7->m_Emote;
232 Char6.m_AttackTick = pChar7->m_AttackTick;
233 Builder.NewItem(Type: NETOBJTYPE_CHARACTER, Id: pItem7->Id(), pData: &Char6, Size: sizeof(Char6));
234
235 if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_PLAYER)
236 {
237 CNetEvent_SoundWorld Sound = {};
238 Sound.m_X = pChar7->m_X;
239 Sound.m_Y = pChar7->m_Y;
240 Sound.m_SoundId = SOUND_HOOK_ATTACH_PLAYER;
241 Builder.NewItem(Type: NETEVENTTYPE_SOUNDWORLD, Id: pItem7->Id(), pData: &Sound, Size: sizeof(Sound));
242 }
243
244 if(TranslationContext.m_aLocalClientId[Conn] != pItem7->Id())
245 {
246 if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_GROUND_JUMP)
247 {
248 CNetEvent_SoundWorld Sound = {};
249 Sound.m_X = pChar7->m_X;
250 Sound.m_Y = pChar7->m_Y;
251 Sound.m_SoundId = SOUND_PLAYER_JUMP;
252 Builder.NewItem(Type: NETEVENTTYPE_SOUNDWORLD, Id: pItem7->Id(), pData: &Sound, Size: sizeof(Sound));
253 }
254 if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_ATTACH_GROUND)
255 {
256 CNetEvent_SoundWorld Sound = {};
257 Sound.m_X = pChar7->m_X;
258 Sound.m_Y = pChar7->m_Y;
259 Sound.m_SoundId = SOUND_HOOK_ATTACH_GROUND;
260 Builder.NewItem(Type: NETEVENTTYPE_SOUNDWORLD, Id: pItem7->Id(), pData: &Sound, Size: sizeof(Sound));
261 }
262 if(pChar7->m_TriggeredEvents & protocol7::COREEVENTFLAG_HOOK_HIT_NOHOOK)
263 {
264 CNetEvent_SoundWorld Sound = {};
265 Sound.m_X = pChar7->m_X;
266 Sound.m_Y = pChar7->m_Y;
267 Sound.m_SoundId = SOUND_HOOK_NOATTACH;
268 Builder.NewItem(Type: NETEVENTTYPE_SOUNDWORLD, Id: pItem7->Id(), pData: &Sound, Size: sizeof(Sound));
269 }
270 }
271 }
272 else if(ItemType == protocol7::NETOBJTYPE_PLAYERINFO)
273 {
274 const protocol7::CNetObj_PlayerInfo *pInfo7 = (const protocol7::CNetObj_PlayerInfo *)pItem7->Data();
275 CNetObj_PlayerInfo Info6 = {};
276 Info6.m_Local = TranslationContext.m_aLocalClientId[Conn] == pItem7->Id();
277 Info6.m_ClientId = pItem7->Id();
278 Info6.m_Team = 0;
279 if(pItem7->Id() >= 0 && pItem7->Id() < MAX_CLIENTS)
280 {
281 Info6.m_Team = TranslationContext.m_aClients[pItem7->Id()].m_Team;
282 TranslationContext.m_aClients[pItem7->Id()].m_PlayerFlags7 = pInfo7->m_PlayerFlags;
283 }
284 Info6.m_Score = pInfo7->m_Score;
285 Info6.m_Latency = pInfo7->m_Latency;
286 Builder.NewItem(Type: NETOBJTYPE_PLAYERINFO, Id: pItem7->Id(), pData: &Info6, Size: sizeof(Info6));
287 }
288 else if(ItemType == protocol7::NETOBJTYPE_SPECTATORINFO)
289 {
290 const protocol7::CNetObj_SpectatorInfo *pSpec7 = (const protocol7::CNetObj_SpectatorInfo *)pItem7->Data();
291 CNetObj_SpectatorInfo Spec6 = {};
292 Spec6.m_SpectatorId = pSpec7->m_SpectatorId;
293 if(pSpec7->m_SpecMode == protocol7::SPEC_FREEVIEW)
294 Spec6.m_SpectatorId = SPEC_FREEVIEW;
295 Spec6.m_X = pSpec7->m_X;
296 Spec6.m_Y = pSpec7->m_Y;
297 Builder.NewItem(Type: NETOBJTYPE_SPECTATORINFO, Id: pItem7->Id(), pData: &Spec6, Size: sizeof(Spec6));
298 }
299 else if(ItemType == protocol7::NETEVENTTYPE_EXPLOSION)
300 {
301 const protocol7::CNetEvent_Explosion *pExplosion7 = (const protocol7::CNetEvent_Explosion *)pItem7->Data();
302 CNetEvent_Explosion Explosion6 = {};
303 Explosion6.m_X = pExplosion7->m_X;
304 Explosion6.m_Y = pExplosion7->m_Y;
305 Builder.NewItem(Type: NETEVENTTYPE_EXPLOSION, Id: pItem7->Id(), pData: &Explosion6, Size: sizeof(Explosion6));
306 }
307 else if(ItemType == protocol7::NETEVENTTYPE_SPAWN)
308 {
309 const protocol7::CNetEvent_Spawn *pSpawn7 = (const protocol7::CNetEvent_Spawn *)pItem7->Data();
310 CNetEvent_Spawn Spawn6 = {};
311 Spawn6.m_X = pSpawn7->m_X;
312 Spawn6.m_Y = pSpawn7->m_Y;
313 Builder.NewItem(Type: NETEVENTTYPE_SPAWN, Id: pItem7->Id(), pData: &Spawn6, Size: sizeof(Spawn6));
314 }
315 else if(ItemType == protocol7::NETEVENTTYPE_HAMMERHIT)
316 {
317 const protocol7::CNetEvent_HammerHit *pHammerHit7 = (const protocol7::CNetEvent_HammerHit *)pItem7->Data();
318 CNetEvent_HammerHit HammerHit6 = {};
319 HammerHit6.m_X = pHammerHit7->m_X;
320 HammerHit6.m_Y = pHammerHit7->m_Y;
321 Builder.NewItem(Type: NETEVENTTYPE_HAMMERHIT, Id: pItem7->Id(), pData: &HammerHit6, Size: sizeof(HammerHit6));
322 }
323 else if(ItemType == protocol7::NETEVENTTYPE_DEATH)
324 {
325 const protocol7::CNetEvent_Death *pDeath7 = (const protocol7::CNetEvent_Death *)pItem7->Data();
326 CNetEvent_Death Death6 = {};
327 Death6.m_X = pDeath7->m_X;
328 Death6.m_Y = pDeath7->m_Y;
329 Death6.m_ClientId = pDeath7->m_ClientId;
330 Builder.NewItem(Type: NETEVENTTYPE_DEATH, Id: pItem7->Id(), pData: &Death6, Size: sizeof(Death6));
331 }
332 else if(ItemType == protocol7::NETEVENTTYPE_SOUNDWORLD)
333 {
334 const protocol7::CNetEvent_SoundWorld *pSoundWorld7 = (const protocol7::CNetEvent_SoundWorld *)pItem7->Data();
335 CNetEvent_SoundWorld SoundWorld6 = {};
336 SoundWorld6.m_X = pSoundWorld7->m_X;
337 SoundWorld6.m_Y = pSoundWorld7->m_Y;
338 SoundWorld6.m_SoundId = pSoundWorld7->m_SoundId;
339 Builder.NewItem(Type: NETEVENTTYPE_SOUNDWORLD, Id: pItem7->Id(), pData: &SoundWorld6, Size: sizeof(SoundWorld6));
340 }
341 else if(ItemType == protocol7::NETEVENTTYPE_DAMAGE)
342 {
343 // 0.7 introduced amount for damage indicators
344 // so for one 0.7 item we might create multiple 0.6 ones
345 const protocol7::CNetEvent_Damage *pDmg7 = (const protocol7::CNetEvent_Damage *)pItem7->Data();
346
347 int Amount = pDmg7->m_HealthAmount + pDmg7->m_ArmorAmount;
348 if(Amount < 1)
349 continue;
350
351 int ClientId = pDmg7->m_ClientId;
352 TranslationContext.m_aDamageTaken[ClientId]++;
353
354 float Angle;
355 // create healthmod indicator
356 if(LocalTime < TranslationContext.m_aDamageTakenTick[ClientId] + 0.5f)
357 {
358 // make sure that the damage indicators don't group together
359 Angle = TranslationContext.m_aDamageTaken[ClientId] * 0.25f;
360 }
361 else
362 {
363 TranslationContext.m_aDamageTaken[ClientId] = 0;
364 Angle = 0;
365 }
366
367 TranslationContext.m_aDamageTakenTick[ClientId] = LocalTime;
368
369 float a = 3 * pi / 2 + Angle;
370 float s = a - pi / 3;
371 float e = a + pi / 3;
372 for(int k = 0; k < Amount; k++)
373 {
374 CNetEvent_DamageInd Dmg6 = {};
375 Dmg6.m_X = pDmg7->m_X;
376 Dmg6.m_Y = pDmg7->m_Y;
377 float f = mix(a: s, b: e, amount: float(k + 1) / float(Amount + 2));
378 Dmg6.m_Angle = (int)(f * 256.0f);
379 // pItem7->Id() is reused that is technically wrong
380 // but the client implementation does not look at the ids
381 // and renders the damage indicators just fine
382 Builder.NewItem(Type: NETEVENTTYPE_DAMAGEIND, Id: pItem7->Id(), pData: &Dmg6, Size: sizeof(Dmg6));
383 }
384 }
385 else if(ItemType == protocol7::NETOBJTYPE_DE_CLIENTINFO)
386 {
387 const protocol7::CNetObj_De_ClientInfo *pInfo = (const protocol7::CNetObj_De_ClientInfo *)pItem7->Data();
388
389 const int ClientId = pItem7->Id();
390
391 if(ClientId < 0 || ClientId >= MAX_CLIENTS)
392 {
393 dbg_msg(sys: "sixup", fmt: "De_ClientInfo got invalid ClientId: %d", ClientId);
394 return -17;
395 }
396
397 if(pInfo->m_Local)
398 {
399 TranslationContext.m_aLocalClientId[Conn] = ClientId;
400 }
401 CTranslationContext::CClientData &Client = TranslationContext.m_aClients[ClientId];
402 Client.m_Active = true;
403 Client.m_Team = pInfo->m_Team;
404 IntsToStr(pInts: pInfo->m_aName, NumInts: std::size(pInfo->m_aName), pStr: Client.m_aName, StrSize: std::size(Client.m_aName));
405 IntsToStr(pInts: pInfo->m_aClan, NumInts: std::size(pInfo->m_aClan), pStr: Client.m_aClan, StrSize: std::size(Client.m_aClan));
406 Client.m_Country = pInfo->m_Country;
407 if(!in_range(a: Client.m_Country, lower: CountryCode::MINIMUM, upper: CountryCode::MAXIMUM))
408 {
409 Client.m_Country = CountryCode::DEFAULT;
410 }
411
412 ApplySkin7InfoFromSnapObj(pObj: pInfo, ClientId);
413 }
414 else if(ItemType == protocol7::NETOBJTYPE_DE_GAMEINFO)
415 {
416 const protocol7::CNetObj_De_GameInfo *pInfo = (const protocol7::CNetObj_De_GameInfo *)pItem7->Data();
417
418 TranslationContext.m_GameFlags = pInfo->m_GameFlags;
419 TranslationContext.m_ScoreLimit = pInfo->m_ScoreLimit;
420 TranslationContext.m_TimeLimit = pInfo->m_TimeLimit;
421 TranslationContext.m_MatchNum = pInfo->m_MatchNum;
422 TranslationContext.m_MatchCurrent = pInfo->m_MatchCurrent;
423 TranslationContext.m_ShouldSendGameInfo = true;
424 }
425 }
426
427 if(NewGameData)
428 {
429 CNetObj_GameData GameData = {};
430 GameData.m_TeamscoreRed = TranslationContext.m_TeamscoreRed;
431 GameData.m_TeamscoreBlue = TranslationContext.m_TeamscoreBlue;
432 GameData.m_FlagCarrierRed = TranslationContext.m_FlagCarrierRed;
433 GameData.m_FlagCarrierBlue = TranslationContext.m_FlagCarrierBlue;
434 Builder.NewItem(Type: NETOBJTYPE_GAMEDATA, Id: 0, pData: &GameData, Size: sizeof(GameData));
435 }
436
437 return Builder.FinishIfNoDroppedItems(pSnapData: pSnapDstSix);
438}
439
440int CGameClient::OnDemoRecSnap7(CSnapshot *pFrom, CSnapshotBuffer *pTo, int Conn)
441{
442 CSnapshotBuilder Builder;
443 Builder.Init7(pSnapshot: pFrom);
444
445 // add client info
446 for(int i = 0; i < MAX_CLIENTS; i++)
447 {
448 if(!m_aClients[i].m_Active)
449 continue;
450
451 CTranslationContext::CClientData &ClientData = Client()->m_TranslationContext.m_aClients[i];
452
453 protocol7::CNetObj_De_ClientInfo ClientInfoObj;
454 ClientInfoObj.m_Local = i == Client()->m_TranslationContext.m_aLocalClientId[Conn];
455 ClientInfoObj.m_Team = ClientData.m_Team;
456 StrToInts(pInts: ClientInfoObj.m_aName, NumInts: std::size(ClientInfoObj.m_aName), pStr: m_aClients[i].m_aName);
457 StrToInts(pInts: ClientInfoObj.m_aClan, NumInts: std::size(ClientInfoObj.m_aClan), pStr: m_aClients[i].m_aClan);
458 ClientInfoObj.m_Country = ClientData.m_Country;
459
460 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
461 {
462 StrToInts(
463 pInts: ClientInfoObj.m_aaSkinPartNames[Part],
464 NumInts: std::size(ClientInfoObj.m_aaSkinPartNames[Part]),
465 pStr: m_aClients[i].m_aSixup[Conn].m_aaSkinPartNames[Part]);
466 ClientInfoObj.m_aUseCustomColors[Part] = m_aClients[i].m_aSixup[Conn].m_aUseCustomColors[Part];
467 ClientInfoObj.m_aSkinPartColors[Part] = m_aClients[i].m_aSixup[Conn].m_aSkinPartColors[Part];
468 }
469
470 Builder.NewItem(Type: protocol7::NETOBJTYPE_DE_CLIENTINFO, Id: i, pData: &ClientInfoObj, Size: sizeof(ClientInfoObj));
471 }
472
473 // add tuning
474 if(mem_comp(a: &CTuningParams::DEFAULT, b: &m_aTuning[Conn], size: sizeof(CTuningParams)) != 0)
475 {
476 protocol7::CNetObj_De_TuneParams TuneParams;
477 mem_copy(dest: &TuneParams.m_aTuneParams, source: &m_aTuning[Conn], size: sizeof(TuneParams.m_aTuneParams));
478 Builder.NewItem(Type: protocol7::NETOBJTYPE_DE_TUNEPARAMS, Id: 0, pData: &TuneParams, Size: sizeof(TuneParams));
479 }
480
481 // add game info
482 protocol7::CNetObj_De_GameInfo GameInfo;
483 GameInfo.m_GameFlags = Client()->m_TranslationContext.m_GameFlags;
484 GameInfo.m_ScoreLimit = Client()->m_TranslationContext.m_ScoreLimit;
485 GameInfo.m_TimeLimit = Client()->m_TranslationContext.m_TimeLimit;
486 GameInfo.m_MatchNum = Client()->m_TranslationContext.m_MatchNum;
487 GameInfo.m_MatchCurrent = Client()->m_TranslationContext.m_MatchCurrent;
488 Builder.NewItem(Type: protocol7::NETOBJTYPE_DE_GAMEINFO, Id: 0, pData: &GameInfo, Size: sizeof(GameInfo));
489
490 return Builder.FinishIfNoDroppedItems(pSnapData: pTo);
491}
492