1#include <base/dbg.h>
2#include <base/str.h>
3
4#include <engine/shared/protocol7.h>
5
6#include <generated/protocol7.h>
7
8#include <game/client/gameclient.h>
9#include <game/gamecore.h>
10#include <game/localization.h>
11
12enum
13{
14 STR_TEAM_GAME,
15 STR_TEAM_RED,
16 STR_TEAM_BLUE,
17 STR_TEAM_SPECTATORS,
18};
19
20static int GetStrTeam7(int Team, bool Teamplay)
21{
22 if(Teamplay)
23 {
24 if(Team == TEAM_RED)
25 return STR_TEAM_RED;
26 else if(Team == TEAM_BLUE)
27 return STR_TEAM_BLUE;
28 }
29 else if(Team == 0)
30 return STR_TEAM_GAME;
31
32 return STR_TEAM_SPECTATORS;
33}
34
35enum
36{
37 DO_CHAT = 0,
38 DO_BROADCAST,
39 DO_SPECIAL,
40};
41
42enum
43{
44 PARA_NONE = 0,
45 PARA_I,
46 PARA_II,
47 PARA_III,
48};
49
50struct CGameMsg7
51{
52 int m_Action;
53 int m_ParaType;
54 const char *m_pText;
55};
56
57static CGameMsg7 gs_GameMsgList7[protocol7::NUM_GAMEMSGS] = {
58 {/*GAMEMSG_TEAM_SWAP*/ .m_Action: DO_CHAT, .m_ParaType: PARA_NONE, .m_pText: "Teams were swapped"},
59 {/*GAMEMSG_SPEC_INVALIDID*/ .m_Action: DO_CHAT, .m_ParaType: PARA_NONE, .m_pText: "Invalid spectator id used"}, //!
60 {/*GAMEMSG_TEAM_SHUFFLE*/ .m_Action: DO_CHAT, .m_ParaType: PARA_NONE, .m_pText: "Teams were shuffled"},
61 {/*GAMEMSG_TEAM_BALANCE*/ .m_Action: DO_CHAT, .m_ParaType: PARA_NONE, .m_pText: "Teams have been balanced"},
62 {/*GAMEMSG_CTF_DROP*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_NONE, .m_pText: ""}, // special - play ctf drop sound
63 {/*GAMEMSG_CTF_RETURN*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_NONE, .m_pText: ""}, // special - play ctf return sound
64
65 {/*GAMEMSG_TEAM_ALL*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_I, .m_pText: ""}, // special - add team name
66 {/*GAMEMSG_TEAM_BALANCE_VICTIM*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_I, .m_pText: ""}, // special - add team name
67 {/*GAMEMSG_CTF_GRAB*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_I, .m_pText: ""}, // special - play ctf grab sound based on team
68
69 {/*GAMEMSG_CTF_CAPTURE*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_III, .m_pText: ""}, // special - play ctf capture sound + capture chat message
70
71 {/*GAMEMSG_GAME_PAUSED*/ .m_Action: DO_SPECIAL, .m_ParaType: PARA_I, .m_pText: ""}, // special - add player name
72};
73
74void CGameClient::DoTeamChangeMessage7(const char *pName, int ClientId, int Team, const char *pPrefix)
75{
76 char aBuf[128];
77 switch(GetStrTeam7(Team, Teamplay: m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS))
78 {
79 case STR_TEAM_GAME: str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' %sjoined the game", pName, pPrefix); break;
80 case STR_TEAM_RED: str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' %sjoined the red team", pName, pPrefix); break;
81 case STR_TEAM_BLUE: str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' %sjoined the blue team", pName, pPrefix); break;
82 case STR_TEAM_SPECTATORS: str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' %sjoined the spectators", pName, pPrefix); break;
83 }
84 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
85}
86
87template<typename T>
88void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId, int Conn)
89{
90 CClientData::CSixup &SixupData = m_aClients[ClientId].m_aSixup[Conn];
91
92 char *apSkinPartsPtr[protocol7::NUM_SKINPARTS];
93 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
94 {
95 str_utf8_copy_num(SixupData.m_aaSkinPartNames[Part], pMsg->m_apSkinPartNames[Part], sizeof(SixupData.m_aaSkinPartNames[Part]), protocol7::MAX_SKIN_LENGTH);
96 apSkinPartsPtr[Part] = SixupData.m_aaSkinPartNames[Part];
97 SixupData.m_aUseCustomColors[Part] = pMsg->m_aUseCustomColors[Part];
98 SixupData.m_aSkinPartColors[Part] = pMsg->m_aSkinPartColors[Part];
99 }
100 m_Skins7.ValidateSkinParts(apPartNames: apSkinPartsPtr, pUseCustomColors: SixupData.m_aUseCustomColors, pPartColors: SixupData.m_aSkinPartColors, GameFlags: m_pClient->m_TranslationContext.m_GameFlags);
101}
102
103void CGameClient::ApplySkin7InfoFromSnapObj(const protocol7::CNetObj_De_ClientInfo *pObj, int ClientId)
104{
105 char aSkinPartNames[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_ARRAY_SIZE];
106 protocol7::CNetMsg_Sv_SkinChange Msg;
107 Msg.m_ClientId = ClientId;
108 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
109 {
110 IntsToStr(
111 pInts: pObj->m_aaSkinPartNames[Part],
112 NumInts: std::size(pObj->m_aaSkinPartNames[Part]),
113 pStr: aSkinPartNames[Part],
114 StrSize: std::size(aSkinPartNames[Part]));
115 Msg.m_apSkinPartNames[Part] = aSkinPartNames[Part];
116 Msg.m_aUseCustomColors[Part] = pObj->m_aUseCustomColors[Part];
117 Msg.m_aSkinPartColors[Part] = pObj->m_aSkinPartColors[Part];
118 }
119 ApplySkin7InfoFromGameMsg(pMsg: &Msg, ClientId, Conn: 0);
120}
121
122void CGameClient::CClientData::UpdateSkin7HatSprite(int Dummy)
123{
124 const CClientData::CSixup &SixupData = m_aSixup[Dummy];
125 CTeeRenderInfo::CSixup &SixupSkinInfo = m_pSkinInfo->TeeRenderInfo().m_aSixup[Dummy];
126
127 if(SixupSkinInfo.m_HatTexture.IsValid())
128 {
129 if(str_comp(a: SixupData.m_aaSkinPartNames[protocol7::SKINPART_BODY], b: "standard") != 0 ||
130 str_comp(a: SixupData.m_aaSkinPartNames[protocol7::SKINPART_DECORATION], b: "twinbopp") == 0)
131 {
132 SixupSkinInfo.m_HatSpriteIndex = CSkins7::HAT_OFFSET_SIDE + (ClientId() % CSkins7::HAT_NUM);
133 }
134 else
135 {
136 SixupSkinInfo.m_HatSpriteIndex = ClientId() % CSkins7::HAT_NUM;
137 }
138 }
139}
140
141void CGameClient::CClientData::UpdateSkin7BotDecoration(int Dummy)
142{
143 static const ColorRGBA BOT_COLORS[] = {
144 ColorRGBA(0xff0000),
145 ColorRGBA(0xff6600),
146 ColorRGBA(0x4d9f45),
147 ColorRGBA(0xd59e29),
148 ColorRGBA(0x9fd3a9),
149 ColorRGBA(0xbdd85e),
150 ColorRGBA(0xc07f94),
151 ColorRGBA(0xc3a267),
152 ColorRGBA(0xf8a83b),
153 ColorRGBA(0xcce2bf),
154 ColorRGBA(0xe6b498),
155 ColorRGBA(0x74c7a3),
156 };
157
158 CTeeRenderInfo::CSixup &SixupSkinInfo = m_pSkinInfo->TeeRenderInfo().m_aSixup[Dummy];
159
160 if((m_pGameClient->m_pClient->m_TranslationContext.m_aClients[ClientId()].m_PlayerFlags7 & protocol7::PLAYERFLAG_BOT) != 0)
161 {
162 if(!SixupSkinInfo.m_BotColor.a) // bot color has not been set; pick a random color once
163 {
164 SixupSkinInfo.m_BotColor = BOT_COLORS[rand() % std::size(BOT_COLORS)];
165 }
166 }
167 else
168 {
169 SixupSkinInfo.m_BotColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
170 }
171}
172
173void *CGameClient::TranslateGameMsg(int *pMsgId, CUnpacker *pUnpacker, int Conn)
174{
175 if(!m_pClient->IsSixup())
176 {
177 return m_NetObjHandler.SecureUnpackMsg(Type: *pMsgId, pUnpacker);
178 }
179
180 void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(Type: *pMsgId, pUnpacker);
181 if(!pRawMsg)
182 {
183 if(*pMsgId > __NETMSGTYPE_UUID_HELPER && *pMsgId < OFFSET_MAPITEMTYPE_UUID)
184 {
185 void *pDDNetExMsg = m_NetObjHandler.SecureUnpackMsg(Type: *pMsgId, pUnpacker);
186 if(pDDNetExMsg)
187 return pDDNetExMsg;
188 }
189
190 dbg_msg(sys: "sixup", fmt: "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(Type: *pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn());
191 return nullptr;
192 }
193 static char s_aRawMsg[1024];
194
195 if(*pMsgId == protocol7::NETMSGTYPE_SV_MOTD)
196 {
197 protocol7::CNetMsg_Sv_Motd *pMsg7 = (protocol7::CNetMsg_Sv_Motd *)pRawMsg;
198 ::CNetMsg_Sv_Motd *pMsg = (::CNetMsg_Sv_Motd *)s_aRawMsg;
199
200 pMsg->m_pMessage = pMsg7->m_pMessage;
201
202 return s_aRawMsg;
203 }
204 else if(*pMsgId == protocol7::NETMSGTYPE_SV_BROADCAST)
205 {
206 protocol7::CNetMsg_Sv_Broadcast *pMsg7 = (protocol7::CNetMsg_Sv_Broadcast *)pRawMsg;
207 ::CNetMsg_Sv_Broadcast *pMsg = (::CNetMsg_Sv_Broadcast *)s_aRawMsg;
208
209 pMsg->m_pMessage = pMsg7->m_pMessage;
210
211 return s_aRawMsg;
212 }
213 else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETTEAM)
214 {
215 protocol7::CNetMsg_Cl_SetTeam *pMsg7 = (protocol7::CNetMsg_Cl_SetTeam *)pRawMsg;
216 ::CNetMsg_Cl_SetTeam *pMsg = (::CNetMsg_Cl_SetTeam *)s_aRawMsg;
217
218 pMsg->m_Team = pMsg7->m_Team;
219
220 return s_aRawMsg;
221 }
222 else if(*pMsgId == protocol7::NETMSGTYPE_SV_TEAM)
223 {
224 protocol7::CNetMsg_Sv_Team *pMsg7 = (protocol7::CNetMsg_Sv_Team *)pRawMsg;
225
226 if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
227 {
228 m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
229 m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
230 if(m_aClients[pMsg7->m_ClientId].m_Active)
231 m_aClients[pMsg7->m_ClientId].UpdateRenderInfo();
232
233 // if(pMsg7->m_ClientId == m_LocalClientId)
234 // {
235 // m_TeamCooldownTick = pMsg7->m_CooldownTick;
236 // m_TeamChangeTime = Client()->LocalTime();
237 // }
238 }
239
240 if(Conn != g_Config.m_ClDummy)
241 return nullptr;
242
243 if(pMsg7->m_Silent == 0)
244 {
245 DoTeamChangeMessage7(pName: m_aClients[pMsg7->m_ClientId].m_aName, ClientId: pMsg7->m_ClientId, Team: pMsg7->m_Team);
246 }
247
248 // we drop the message and add the new team
249 // info to the playerinfo snap item
250 // using translation context
251 return nullptr;
252 }
253 else if(*pMsgId == protocol7::NETMSGTYPE_SV_WEAPONPICKUP)
254 {
255 protocol7::CNetMsg_Sv_WeaponPickup *pMsg7 = (protocol7::CNetMsg_Sv_WeaponPickup *)pRawMsg;
256 ::CNetMsg_Sv_WeaponPickup *pMsg = (::CNetMsg_Sv_WeaponPickup *)s_aRawMsg;
257
258 pMsg->m_Weapon = pMsg7->m_Weapon;
259
260 return s_aRawMsg;
261 }
262 else if(*pMsgId == protocol7::NETMSGTYPE_SV_SERVERSETTINGS)
263 {
264 // 0.7 only message for ui enrichment like locked teams
265 protocol7::CNetMsg_Sv_ServerSettings *pMsg = (protocol7::CNetMsg_Sv_ServerSettings *)pRawMsg;
266
267 if(!m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && pMsg->m_TeamLock)
268 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Teams were locked");
269 else if(m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && !pMsg->m_TeamLock)
270 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Teams were unlocked");
271
272 m_pClient->m_TranslationContext.m_ServerSettings.m_KickVote = pMsg->m_KickVote;
273 m_pClient->m_TranslationContext.m_ServerSettings.m_KickMin = pMsg->m_KickMin;
274 m_pClient->m_TranslationContext.m_ServerSettings.m_SpecVote = pMsg->m_SpecVote;
275 m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock = pMsg->m_TeamLock;
276 m_pClient->m_TranslationContext.m_ServerSettings.m_TeamBalance = pMsg->m_TeamBalance;
277 m_pClient->m_TranslationContext.m_ServerSettings.m_PlayerSlots = pMsg->m_PlayerSlots;
278 return nullptr; // There is no 0.6 equivalent
279 }
280 else if(*pMsgId == protocol7::NETMSGTYPE_SV_RACEFINISH)
281 {
282 *pMsgId = NETMSGTYPE_SV_RACEFINISH;
283 protocol7::CNetMsg_Sv_RaceFinish *pMsg7 = (protocol7::CNetMsg_Sv_RaceFinish *)pRawMsg;
284 ::CNetMsg_Sv_RaceFinish *pMsg = (::CNetMsg_Sv_RaceFinish *)s_aRawMsg;
285
286 pMsg->m_ClientId = pMsg7->m_ClientId;
287 pMsg->m_Time = pMsg7->m_Time;
288 pMsg->m_Diff = pMsg7->m_Diff;
289 pMsg->m_RecordPersonal = pMsg7->m_RecordPersonal;
290 pMsg->m_RecordServer = pMsg7->m_RecordServer;
291
292 return s_aRawMsg;
293 }
294 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHECKPOINT)
295 {
296 *pMsgId = NETMSGTYPE_SV_DDRACETIME;
297 protocol7::CNetMsg_Sv_Checkpoint *pMsg7 = (protocol7::CNetMsg_Sv_Checkpoint *)pRawMsg;
298 ::CNetMsg_Sv_DDRaceTime *pMsg = (::CNetMsg_Sv_DDRaceTime *)s_aRawMsg;
299
300 pMsg->m_Time = 1;
301 pMsg->m_Finish = 0;
302 pMsg->m_Check = pMsg7->m_Diff / 10;
303
304 return s_aRawMsg;
305 }
306 else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFOREMOVE)
307 {
308 *pMsgId = NETMSGTYPE_SV_COMMANDINFOREMOVE;
309 protocol7::CNetMsg_Sv_CommandInfoRemove *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfoRemove *)pRawMsg;
310 ::CNetMsg_Sv_CommandInfoRemove *pMsg = (::CNetMsg_Sv_CommandInfoRemove *)s_aRawMsg;
311
312 pMsg->m_pName = pMsg7->m_pName;
313
314 return s_aRawMsg;
315 }
316 else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFO)
317 {
318 *pMsgId = NETMSGTYPE_SV_COMMANDINFO;
319 protocol7::CNetMsg_Sv_CommandInfo *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfo *)pRawMsg;
320 ::CNetMsg_Sv_CommandInfo *pMsg = (::CNetMsg_Sv_CommandInfo *)s_aRawMsg;
321
322 pMsg->m_pName = pMsg7->m_pName;
323 pMsg->m_pArgsFormat = pMsg7->m_pArgsFormat;
324 pMsg->m_pHelpText = pMsg7->m_pHelpText;
325
326 return s_aRawMsg;
327 }
328 else if(*pMsgId == protocol7::NETMSGTYPE_SV_SKINCHANGE)
329 {
330 protocol7::CNetMsg_Sv_SkinChange *pMsg7 = (protocol7::CNetMsg_Sv_SkinChange *)pRawMsg;
331
332 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
333 {
334 dbg_msg(sys: "sixup", fmt: "Sv_SkinChange got invalid ClientId: %d", pMsg7->m_ClientId);
335 return nullptr;
336 }
337
338 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
339 Client.m_Active = true;
340 ApplySkin7InfoFromGameMsg(pMsg: pMsg7, ClientId: pMsg7->m_ClientId, Conn);
341 // skin will be moved to the 0.6 snap by the translation context
342 // and we drop the game message
343 return nullptr;
344 }
345 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTECLEAROPTIONS)
346 {
347 *pMsgId = NETMSGTYPE_SV_VOTECLEAROPTIONS;
348 return s_aRawMsg;
349 }
350 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONADD)
351 {
352 *pMsgId = NETMSGTYPE_SV_VOTEOPTIONADD;
353 protocol7::CNetMsg_Sv_VoteOptionAdd *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionAdd *)pRawMsg;
354 ::CNetMsg_Sv_VoteOptionAdd *pMsg = (::CNetMsg_Sv_VoteOptionAdd *)s_aRawMsg;
355 pMsg->m_pDescription = pMsg7->m_pDescription;
356 return s_aRawMsg;
357 }
358 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONREMOVE)
359 {
360 *pMsgId = NETMSGTYPE_SV_VOTEOPTIONREMOVE;
361 protocol7::CNetMsg_Sv_VoteOptionRemove *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionRemove *)pRawMsg;
362 ::CNetMsg_Sv_VoteOptionRemove *pMsg = (::CNetMsg_Sv_VoteOptionRemove *)s_aRawMsg;
363 pMsg->m_pDescription = pMsg7->m_pDescription;
364 return s_aRawMsg;
365 }
366 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONLISTADD)
367 {
368 ::CNetMsg_Sv_VoteOptionListAdd *pMsg = (::CNetMsg_Sv_VoteOptionListAdd *)s_aRawMsg;
369 int NumOptions = pUnpacker->GetInt();
370 if(NumOptions > 14)
371 {
372 for(int i = 0; i < NumOptions; i++)
373 {
374 const char *pDescription = pUnpacker->GetString(SanitizeType: CUnpacker::SANITIZE_CC);
375 if(pUnpacker->Error())
376 continue;
377
378 m_Voting.AddOption(pDescription);
379 }
380 // 0.7 can send more vote options than
381 // the 0.6 protocol fit
382 // in that case we do not translate it but just
383 // reimplement what 0.6 would do
384 return nullptr;
385 }
386 pMsg->m_NumOptions = 0;
387 for(int i = 0; i < NumOptions; i++)
388 {
389 const char *pDescription = pUnpacker->GetString(SanitizeType: CUnpacker::SANITIZE_CC);
390 if(pUnpacker->Error())
391 continue;
392
393 pMsg->m_NumOptions++;
394 switch(i)
395 {
396 case 0: (pMsg->m_pDescription0 = pDescription); break;
397 case 1: (pMsg->m_pDescription1 = pDescription); break;
398 case 2: (pMsg->m_pDescription2 = pDescription); break;
399 case 3: (pMsg->m_pDescription3 = pDescription); break;
400 case 4: (pMsg->m_pDescription4 = pDescription); break;
401 case 5: (pMsg->m_pDescription5 = pDescription); break;
402 case 6: (pMsg->m_pDescription6 = pDescription); break;
403 case 7: (pMsg->m_pDescription7 = pDescription); break;
404 case 8: (pMsg->m_pDescription8 = pDescription); break;
405 case 9: (pMsg->m_pDescription9 = pDescription); break;
406 case 10: (pMsg->m_pDescription10 = pDescription); break;
407 case 11: (pMsg->m_pDescription11 = pDescription); break;
408 case 12: (pMsg->m_pDescription12 = pDescription); break;
409 case 13: (pMsg->m_pDescription13 = pDescription); break;
410 case 14: (pMsg->m_pDescription14 = pDescription);
411 }
412 }
413 return s_aRawMsg;
414 }
415 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESET)
416 {
417 *pMsgId = NETMSGTYPE_SV_VOTESET;
418 protocol7::CNetMsg_Sv_VoteSet *pMsg7 = (protocol7::CNetMsg_Sv_VoteSet *)pRawMsg;
419 ::CNetMsg_Sv_VoteSet *pMsg = (::CNetMsg_Sv_VoteSet *)s_aRawMsg;
420
421 pMsg->m_Timeout = pMsg7->m_Timeout;
422 pMsg->m_pDescription = pMsg7->m_pDescription;
423 pMsg->m_pReason = pMsg7->m_pReason;
424
425 char aBuf[128];
426 if(pMsg7->m_Timeout)
427 {
428 if(pMsg7->m_ClientId != -1)
429 {
430 const char *pName = m_aClients[pMsg7->m_ClientId].m_aName;
431 switch(pMsg7->m_Type)
432 {
433 case protocol7::VOTE_START_OP:
434 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' called vote to change server option '%s' (%s)", pName, pMsg7->m_pDescription, pMsg7->m_pReason);
435 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
436 break;
437 case protocol7::VOTE_START_KICK:
438 {
439 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' called for vote to kick '%s' (%s)", pName, pMsg7->m_pDescription, pMsg7->m_pReason);
440 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
441 break;
442 }
443 case protocol7::VOTE_START_SPEC:
444 {
445 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' called for vote to move '%s' to spectators (%s)", pName, pMsg7->m_pDescription, pMsg7->m_pReason);
446 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
447 }
448 }
449 }
450 }
451 else
452 {
453 switch(pMsg7->m_Type)
454 {
455 case protocol7::VOTE_START_OP:
456 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Admin forced server option '%s' (%s)", pMsg7->m_pDescription, pMsg7->m_pReason);
457 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
458 break;
459 case protocol7::VOTE_START_SPEC:
460 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Admin moved '%s' to spectator (%s)", pMsg7->m_pDescription, pMsg7->m_pReason);
461 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
462 break;
463 case protocol7::VOTE_END_ABORT:
464 m_Voting.OnReset();
465 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Vote aborted");
466 break;
467 case protocol7::VOTE_END_PASS:
468 m_Voting.OnReset();
469 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pMsg7->m_ClientId == -1 ? "Admin forced vote yes" : "Vote passed");
470 break;
471 case protocol7::VOTE_END_FAIL:
472 m_Voting.OnReset();
473 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pMsg7->m_ClientId == -1 ? "Admin forced vote no" : "Vote failed");
474 }
475 }
476
477 return s_aRawMsg;
478 }
479 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESTATUS)
480 {
481 *pMsgId = NETMSGTYPE_SV_VOTESTATUS;
482 protocol7::CNetMsg_Sv_VoteStatus *pMsg7 = (protocol7::CNetMsg_Sv_VoteStatus *)pRawMsg;
483 ::CNetMsg_Sv_VoteStatus *pMsg = (::CNetMsg_Sv_VoteStatus *)s_aRawMsg;
484
485 pMsg->m_Yes = pMsg7->m_Yes;
486 pMsg->m_No = pMsg7->m_No;
487 pMsg->m_Pass = pMsg7->m_Pass;
488 pMsg->m_Total = pMsg7->m_Total;
489
490 return s_aRawMsg;
491 }
492 else if(*pMsgId == protocol7::NETMSGTYPE_SV_READYTOENTER)
493 {
494 *pMsgId = NETMSGTYPE_SV_READYTOENTER;
495 return s_aRawMsg;
496 }
497 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTDROP)
498 {
499 protocol7::CNetMsg_Sv_ClientDrop *pMsg7 = (protocol7::CNetMsg_Sv_ClientDrop *)pRawMsg;
500 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
501 {
502 dbg_msg(sys: "sixup", fmt: "Sv_ClientDrop got invalid ClientId: %d", pMsg7->m_ClientId);
503 return nullptr;
504 }
505 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
506 Client.Reset();
507
508 if(pMsg7->m_Silent)
509 return nullptr;
510
511 if(Conn != g_Config.m_ClDummy)
512 return nullptr;
513
514 static char s_aBuf[128];
515 if(pMsg7->m_pReason[0])
516 str_format(buffer: s_aBuf, buffer_size: sizeof(s_aBuf), format: "'%s' has left the game (%s)", m_aClients[pMsg7->m_ClientId].m_aName, pMsg7->m_pReason);
517 else
518 str_format(buffer: s_aBuf, buffer_size: sizeof(s_aBuf), format: "'%s' has left the game", m_aClients[pMsg7->m_ClientId].m_aName);
519 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: s_aBuf);
520
521 return nullptr;
522 }
523 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTINFO)
524 {
525 protocol7::CNetMsg_Sv_ClientInfo *pMsg7 = (protocol7::CNetMsg_Sv_ClientInfo *)pRawMsg;
526 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
527 {
528 dbg_msg(sys: "sixup", fmt: "Sv_ClientInfo got invalid ClientId: %d", pMsg7->m_ClientId);
529 return nullptr;
530 }
531
532 if(pMsg7->m_Local)
533 {
534 m_pClient->m_TranslationContext.m_aLocalClientId[Conn] = pMsg7->m_ClientId;
535 }
536 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
537 Client.m_Active = true;
538 Client.m_Team = pMsg7->m_Team;
539 str_copy(dst&: Client.m_aName, src: pMsg7->m_pName);
540 str_copy(dst&: Client.m_aClan, src: pMsg7->m_pClan);
541 Client.m_Country = pMsg7->m_Country;
542 ApplySkin7InfoFromGameMsg(pMsg: pMsg7, ClientId: pMsg7->m_ClientId, Conn);
543 if(m_pClient->m_TranslationContext.m_aLocalClientId[Conn] == -1)
544 return nullptr;
545 if(pMsg7->m_Silent || pMsg7->m_Local)
546 return nullptr;
547
548 if(Conn != g_Config.m_ClDummy)
549 return nullptr;
550
551 DoTeamChangeMessage7(
552 pName: pMsg7->m_pName,
553 ClientId: pMsg7->m_ClientId,
554 Team: pMsg7->m_Team,
555 pPrefix: "entered and ");
556
557 return nullptr; // we only do side effects and add stuff to the snap here
558 }
559 else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEINFO)
560 {
561 protocol7::CNetMsg_Sv_GameInfo *pMsg7 = (protocol7::CNetMsg_Sv_GameInfo *)pRawMsg;
562 m_pClient->m_TranslationContext.m_GameFlags = pMsg7->m_GameFlags;
563 m_pClient->m_TranslationContext.m_ScoreLimit = pMsg7->m_ScoreLimit;
564 m_pClient->m_TranslationContext.m_TimeLimit = pMsg7->m_TimeLimit;
565 m_pClient->m_TranslationContext.m_MatchNum = pMsg7->m_MatchNum;
566 m_pClient->m_TranslationContext.m_MatchCurrent = pMsg7->m_MatchCurrent;
567 m_pClient->m_TranslationContext.m_ShouldSendGameInfo = true;
568 return nullptr; // Added to snap by translation context
569 }
570 else if(*pMsgId == protocol7::NETMSGTYPE_SV_EMOTICON)
571 {
572 *pMsgId = NETMSGTYPE_SV_EMOTICON;
573 protocol7::CNetMsg_Sv_Emoticon *pMsg7 = (protocol7::CNetMsg_Sv_Emoticon *)pRawMsg;
574 ::CNetMsg_Sv_Emoticon *pMsg = (::CNetMsg_Sv_Emoticon *)s_aRawMsg;
575
576 pMsg->m_ClientId = pMsg7->m_ClientId;
577 pMsg->m_Emoticon = pMsg7->m_Emoticon;
578
579 return s_aRawMsg;
580 }
581 else if(*pMsgId == protocol7::NETMSGTYPE_SV_KILLMSG)
582 {
583 *pMsgId = NETMSGTYPE_SV_KILLMSG;
584
585 protocol7::CNetMsg_Sv_KillMsg *pMsg7 = (protocol7::CNetMsg_Sv_KillMsg *)pRawMsg;
586 ::CNetMsg_Sv_KillMsg *pMsg = (::CNetMsg_Sv_KillMsg *)s_aRawMsg;
587
588 pMsg->m_Killer = pMsg7->m_Killer;
589 pMsg->m_Victim = pMsg7->m_Victim;
590 pMsg->m_Weapon = pMsg7->m_Weapon;
591 pMsg->m_ModeSpecial = pMsg7->m_ModeSpecial;
592
593 return s_aRawMsg;
594 }
595 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHAT)
596 {
597 *pMsgId = NETMSGTYPE_SV_CHAT;
598
599 protocol7::CNetMsg_Sv_Chat *pMsg7 = (protocol7::CNetMsg_Sv_Chat *)pRawMsg;
600 ::CNetMsg_Sv_Chat *pMsg = (::CNetMsg_Sv_Chat *)s_aRawMsg;
601
602 if(pMsg7->m_Mode == protocol7::CHAT_WHISPER)
603 {
604 bool Receive = pMsg7->m_TargetId == m_pClient->m_TranslationContext.m_aLocalClientId[Conn];
605
606 pMsg->m_Team = Receive ? 3 : 2;
607 pMsg->m_ClientId = Receive ? pMsg7->m_ClientId : pMsg7->m_TargetId;
608 }
609 else
610 {
611 pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM ? 1 : 0;
612 pMsg->m_ClientId = pMsg7->m_ClientId;
613 }
614
615 pMsg->m_pMessage = pMsg7->m_pMessage;
616
617 return s_aRawMsg;
618 }
619 else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEMSG)
620 {
621 int GameMsgId = pUnpacker->GetInt();
622
623 /**
624 * Prints chat message only once
625 * even if it is being sent to main tee and dummy
626 */
627 auto SendChat = [Conn, GameMsgId, this](const char *pText) -> void {
628 if(GameMsgId != protocol7::GAMEMSG_TEAM_BALANCE_VICTIM && GameMsgId != protocol7::GAMEMSG_SPEC_INVALIDID)
629 {
630 if(Conn != g_Config.m_ClDummy)
631 return;
632 }
633 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pText);
634 };
635
636 // check for valid gamemsgid
637 if(GameMsgId < 0 || GameMsgId >= protocol7::NUM_GAMEMSGS)
638 return nullptr;
639
640 int aParaI[3] = {0};
641 int NumParaI = 0;
642
643 // get paras
644 switch(gs_GameMsgList7[GameMsgId].m_ParaType)
645 {
646 case PARA_I: NumParaI = 1; break;
647 case PARA_II: NumParaI = 2; break;
648 case PARA_III: NumParaI = 3; break;
649 }
650 for(int i = 0; i < NumParaI; i++)
651 {
652 aParaI[i] = pUnpacker->GetInt();
653 }
654
655 // check for unpacking errors
656 if(pUnpacker->Error())
657 return nullptr;
658
659 // handle special messages
660 char aBuf[256];
661 bool TeamPlay = m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS;
662 if(gs_GameMsgList7[GameMsgId].m_Action == DO_SPECIAL)
663 {
664 switch(GameMsgId)
665 {
666 case protocol7::GAMEMSG_CTF_DROP:
667 if(Conn == g_Config.m_ClDummy)
668 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_DROP);
669 break;
670 case protocol7::GAMEMSG_CTF_RETURN:
671 if(Conn == g_Config.m_ClDummy)
672 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_RETURN);
673 break;
674 case protocol7::GAMEMSG_TEAM_ALL:
675 {
676 const char *pMsg = "";
677 switch(GetStrTeam7(Team: aParaI[0], Teamplay: TeamPlay))
678 {
679 case STR_TEAM_GAME: pMsg = "All players were moved to the game"; break;
680 case STR_TEAM_RED: pMsg = "All players were moved to the red team"; break;
681 case STR_TEAM_BLUE: pMsg = "All players were moved to the blue team"; break;
682 case STR_TEAM_SPECTATORS: pMsg = "All players were moved to the spectators"; break;
683 }
684 m_Broadcast.DoBroadcast(pText: pMsg); // client side broadcast
685 }
686 break;
687 case protocol7::GAMEMSG_TEAM_BALANCE_VICTIM:
688 {
689 const char *pMsg = "";
690 switch(GetStrTeam7(Team: aParaI[0], Teamplay: TeamPlay))
691 {
692 case STR_TEAM_RED: pMsg = "You were moved to the red team due to team balancing"; break;
693 case STR_TEAM_BLUE: pMsg = "You were moved to the blue team due to team balancing"; break;
694 }
695 m_Broadcast.DoBroadcast(pText: pMsg); // client side broadcast
696 }
697 break;
698 case protocol7::GAMEMSG_CTF_GRAB:
699 if(Conn == g_Config.m_ClDummy)
700 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_GRAB_EN);
701 break;
702 case protocol7::GAMEMSG_GAME_PAUSED:
703 {
704 int ClientId = std::clamp(val: aParaI[0], lo: 0, hi: MAX_CLIENTS - 1);
705 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' initiated a pause", m_aClients[ClientId].m_aName);
706 SendChat(aBuf);
707 }
708 break;
709 case protocol7::GAMEMSG_CTF_CAPTURE:
710 if(Conn == g_Config.m_ClDummy)
711 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_CAPTURE);
712 int ClientId = std::clamp(val: aParaI[1], lo: 0, hi: MAX_CLIENTS - 1);
713 m_aStats[ClientId].m_FlagCaptures++;
714
715 float Time = aParaI[2] / (float)Client()->GameTickSpeed();
716 if(Time <= 60)
717 {
718 if(aParaI[0])
719 {
720 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The blue flag was captured by '%s' (%.2f seconds)", m_aClients[ClientId].m_aName, Time);
721 }
722 else
723 {
724 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The red flag was captured by '%s' (%.2f seconds)", m_aClients[ClientId].m_aName, Time);
725 }
726 }
727 else
728 {
729 if(aParaI[0])
730 {
731 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The blue flag was captured by '%s'", m_aClients[ClientId].m_aName);
732 }
733 else
734 {
735 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The red flag was captured by '%s'", m_aClients[ClientId].m_aName);
736 }
737 }
738 SendChat(aBuf);
739 }
740 return nullptr;
741 }
742
743 // build message
744 const char *pText = "";
745 if(NumParaI == 0)
746 {
747 pText = gs_GameMsgList7[GameMsgId].m_pText;
748 }
749
750 // handle message
751 switch(gs_GameMsgList7[GameMsgId].m_Action)
752 {
753 case DO_CHAT:
754 SendChat(pText);
755 break;
756 case DO_BROADCAST:
757 m_Broadcast.DoBroadcast(pText); // client side broadcast
758 break;
759 }
760
761 // no need to handle it in 0.6 since we printed
762 // the message already
763 return nullptr;
764 }
765
766 char aBuf[256];
767 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler7.GetMsgName(Type: *pMsgId), *pMsgId, m_NetObjHandler7.FailedMsgOn());
768 Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "client", pStr: aBuf);
769
770 return nullptr;
771}
772