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