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