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