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(Client()->State() != IClient::STATE_DEMOPLAYBACK)
229 {
230 m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
231 m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId].m_Team = pMsg7->m_Team;
232 if(m_aClients[pMsg7->m_ClientId].m_Active)
233 m_aClients[pMsg7->m_ClientId].UpdateRenderInfo();
234
235 // if(pMsg7->m_ClientId == m_LocalClientId)
236 // {
237 // m_TeamCooldownTick = pMsg7->m_CooldownTick;
238 // m_TeamChangeTime = Client()->LocalTime();
239 // }
240 }
241
242 if(Conn != g_Config.m_ClDummy)
243 return nullptr;
244
245 if(pMsg7->m_Silent == 0)
246 {
247 DoTeamChangeMessage7(pName: m_aClients[pMsg7->m_ClientId].m_aName, ClientId: pMsg7->m_ClientId, Team: pMsg7->m_Team);
248 }
249
250 // we drop the message and add the new team
251 // info to the playerinfo snap item
252 // using translation context
253 return nullptr;
254 }
255 else if(*pMsgId == protocol7::NETMSGTYPE_SV_WEAPONPICKUP)
256 {
257 protocol7::CNetMsg_Sv_WeaponPickup *pMsg7 = (protocol7::CNetMsg_Sv_WeaponPickup *)pRawMsg;
258 ::CNetMsg_Sv_WeaponPickup *pMsg = (::CNetMsg_Sv_WeaponPickup *)s_aRawMsg;
259
260 pMsg->m_Weapon = pMsg7->m_Weapon;
261
262 return s_aRawMsg;
263 }
264 else if(*pMsgId == protocol7::NETMSGTYPE_SV_SERVERSETTINGS)
265 {
266 // 0.7 only message for ui enrichment like locked teams
267 protocol7::CNetMsg_Sv_ServerSettings *pMsg = (protocol7::CNetMsg_Sv_ServerSettings *)pRawMsg;
268
269 if(!m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && pMsg->m_TeamLock)
270 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Teams were locked");
271 else if(m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock && !pMsg->m_TeamLock)
272 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Teams were unlocked");
273
274 m_pClient->m_TranslationContext.m_ServerSettings.m_KickVote = pMsg->m_KickVote;
275 m_pClient->m_TranslationContext.m_ServerSettings.m_KickMin = pMsg->m_KickMin;
276 m_pClient->m_TranslationContext.m_ServerSettings.m_SpecVote = pMsg->m_SpecVote;
277 m_pClient->m_TranslationContext.m_ServerSettings.m_TeamLock = pMsg->m_TeamLock;
278 m_pClient->m_TranslationContext.m_ServerSettings.m_TeamBalance = pMsg->m_TeamBalance;
279 m_pClient->m_TranslationContext.m_ServerSettings.m_PlayerSlots = pMsg->m_PlayerSlots;
280 return nullptr; // There is no 0.6 equivalent
281 }
282 else if(*pMsgId == protocol7::NETMSGTYPE_SV_RACEFINISH)
283 {
284 *pMsgId = NETMSGTYPE_SV_RACEFINISH;
285 protocol7::CNetMsg_Sv_RaceFinish *pMsg7 = (protocol7::CNetMsg_Sv_RaceFinish *)pRawMsg;
286 ::CNetMsg_Sv_RaceFinish *pMsg = (::CNetMsg_Sv_RaceFinish *)s_aRawMsg;
287
288 pMsg->m_ClientId = pMsg7->m_ClientId;
289 pMsg->m_Time = pMsg7->m_Time;
290 pMsg->m_Diff = pMsg7->m_Diff;
291 pMsg->m_RecordPersonal = pMsg7->m_RecordPersonal;
292 pMsg->m_RecordServer = pMsg7->m_RecordServer;
293
294 return s_aRawMsg;
295 }
296 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHECKPOINT)
297 {
298 *pMsgId = NETMSGTYPE_SV_DDRACETIME;
299 protocol7::CNetMsg_Sv_Checkpoint *pMsg7 = (protocol7::CNetMsg_Sv_Checkpoint *)pRawMsg;
300 ::CNetMsg_Sv_DDRaceTime *pMsg = (::CNetMsg_Sv_DDRaceTime *)s_aRawMsg;
301
302 pMsg->m_Time = 1;
303 pMsg->m_Finish = 0;
304 pMsg->m_Check = pMsg7->m_Diff / 10;
305
306 return s_aRawMsg;
307 }
308 else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFOREMOVE)
309 {
310 *pMsgId = NETMSGTYPE_SV_COMMANDINFOREMOVE;
311 protocol7::CNetMsg_Sv_CommandInfoRemove *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfoRemove *)pRawMsg;
312 ::CNetMsg_Sv_CommandInfoRemove *pMsg = (::CNetMsg_Sv_CommandInfoRemove *)s_aRawMsg;
313
314 pMsg->m_pName = pMsg7->m_pName;
315
316 return s_aRawMsg;
317 }
318 else if(*pMsgId == protocol7::NETMSGTYPE_SV_COMMANDINFO)
319 {
320 *pMsgId = NETMSGTYPE_SV_COMMANDINFO;
321 protocol7::CNetMsg_Sv_CommandInfo *pMsg7 = (protocol7::CNetMsg_Sv_CommandInfo *)pRawMsg;
322 ::CNetMsg_Sv_CommandInfo *pMsg = (::CNetMsg_Sv_CommandInfo *)s_aRawMsg;
323
324 pMsg->m_pName = pMsg7->m_pName;
325 pMsg->m_pArgsFormat = pMsg7->m_pArgsFormat;
326 pMsg->m_pHelpText = pMsg7->m_pHelpText;
327
328 return s_aRawMsg;
329 }
330 else if(*pMsgId == protocol7::NETMSGTYPE_SV_SKINCHANGE)
331 {
332 protocol7::CNetMsg_Sv_SkinChange *pMsg7 = (protocol7::CNetMsg_Sv_SkinChange *)pRawMsg;
333
334 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
335 {
336 dbg_msg(sys: "sixup", fmt: "Sv_SkinChange got invalid ClientId: %d", pMsg7->m_ClientId);
337 return nullptr;
338 }
339
340 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
341 Client.m_Active = true;
342 ApplySkin7InfoFromGameMsg(pMsg: pMsg7, ClientId: pMsg7->m_ClientId, Conn);
343 // skin will be moved to the 0.6 snap by the translation context
344 // and we drop the game message
345 return nullptr;
346 }
347 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTECLEAROPTIONS)
348 {
349 *pMsgId = NETMSGTYPE_SV_VOTECLEAROPTIONS;
350 return s_aRawMsg;
351 }
352 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONADD)
353 {
354 *pMsgId = NETMSGTYPE_SV_VOTEOPTIONADD;
355 protocol7::CNetMsg_Sv_VoteOptionAdd *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionAdd *)pRawMsg;
356 ::CNetMsg_Sv_VoteOptionAdd *pMsg = (::CNetMsg_Sv_VoteOptionAdd *)s_aRawMsg;
357 pMsg->m_pDescription = pMsg7->m_pDescription;
358 return s_aRawMsg;
359 }
360 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONREMOVE)
361 {
362 *pMsgId = NETMSGTYPE_SV_VOTEOPTIONREMOVE;
363 protocol7::CNetMsg_Sv_VoteOptionRemove *pMsg7 = (protocol7::CNetMsg_Sv_VoteOptionRemove *)pRawMsg;
364 ::CNetMsg_Sv_VoteOptionRemove *pMsg = (::CNetMsg_Sv_VoteOptionRemove *)s_aRawMsg;
365 pMsg->m_pDescription = pMsg7->m_pDescription;
366 return s_aRawMsg;
367 }
368 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTEOPTIONLISTADD)
369 {
370 ::CNetMsg_Sv_VoteOptionListAdd *pMsg = (::CNetMsg_Sv_VoteOptionListAdd *)s_aRawMsg;
371 int NumOptions = pUnpacker->GetInt();
372 if(NumOptions > 14)
373 {
374 for(int i = 0; i < NumOptions; i++)
375 {
376 const char *pDescription = pUnpacker->GetString(SanitizeType: CUnpacker::SANITIZE_CC);
377 if(pUnpacker->Error())
378 continue;
379
380 m_Voting.AddOption(pDescription);
381 }
382 // 0.7 can send more vote options than
383 // the 0.6 protocol fit
384 // in that case we do not translate it but just
385 // reimplement what 0.6 would do
386 return nullptr;
387 }
388 pMsg->m_NumOptions = 0;
389 for(int i = 0; i < NumOptions; i++)
390 {
391 const char *pDescription = pUnpacker->GetString(SanitizeType: CUnpacker::SANITIZE_CC);
392 if(pUnpacker->Error())
393 continue;
394
395 pMsg->m_NumOptions++;
396 switch(i)
397 {
398 case 0: (pMsg->m_pDescription0 = pDescription); break;
399 case 1: (pMsg->m_pDescription1 = pDescription); break;
400 case 2: (pMsg->m_pDescription2 = pDescription); break;
401 case 3: (pMsg->m_pDescription3 = pDescription); break;
402 case 4: (pMsg->m_pDescription4 = pDescription); break;
403 case 5: (pMsg->m_pDescription5 = pDescription); break;
404 case 6: (pMsg->m_pDescription6 = pDescription); break;
405 case 7: (pMsg->m_pDescription7 = pDescription); break;
406 case 8: (pMsg->m_pDescription8 = pDescription); break;
407 case 9: (pMsg->m_pDescription9 = pDescription); break;
408 case 10: (pMsg->m_pDescription10 = pDescription); break;
409 case 11: (pMsg->m_pDescription11 = pDescription); break;
410 case 12: (pMsg->m_pDescription12 = pDescription); break;
411 case 13: (pMsg->m_pDescription13 = pDescription); break;
412 case 14: (pMsg->m_pDescription14 = pDescription);
413 }
414 }
415 return s_aRawMsg;
416 }
417 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESET)
418 {
419 *pMsgId = NETMSGTYPE_SV_VOTESET;
420 protocol7::CNetMsg_Sv_VoteSet *pMsg7 = (protocol7::CNetMsg_Sv_VoteSet *)pRawMsg;
421 ::CNetMsg_Sv_VoteSet *pMsg = (::CNetMsg_Sv_VoteSet *)s_aRawMsg;
422
423 pMsg->m_Timeout = pMsg7->m_Timeout;
424 pMsg->m_pDescription = pMsg7->m_pDescription;
425 pMsg->m_pReason = pMsg7->m_pReason;
426
427 char aBuf[128];
428 if(pMsg7->m_Timeout)
429 {
430 if(pMsg7->m_ClientId != -1)
431 {
432 const char *pName = m_aClients[pMsg7->m_ClientId].m_aName;
433 switch(pMsg7->m_Type)
434 {
435 case protocol7::VOTE_START_OP:
436 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);
437 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
438 break;
439 case protocol7::VOTE_START_KICK:
440 {
441 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' called for vote to kick '%s' (%s)", pName, pMsg7->m_pDescription, pMsg7->m_pReason);
442 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
443 break;
444 }
445 case protocol7::VOTE_START_SPEC:
446 {
447 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);
448 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
449 }
450 }
451 }
452 }
453 else
454 {
455 switch(pMsg7->m_Type)
456 {
457 case protocol7::VOTE_START_OP:
458 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Admin forced server option '%s' (%s)", pMsg7->m_pDescription, pMsg7->m_pReason);
459 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
460 break;
461 case protocol7::VOTE_START_SPEC:
462 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Admin moved '%s' to spectator (%s)", pMsg7->m_pDescription, pMsg7->m_pReason);
463 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: aBuf);
464 break;
465 case protocol7::VOTE_END_ABORT:
466 m_Voting.OnReset();
467 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: "Vote aborted");
468 break;
469 case protocol7::VOTE_END_PASS:
470 m_Voting.OnReset();
471 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pMsg7->m_ClientId == -1 ? "Admin forced vote yes" : "Vote passed");
472 break;
473 case protocol7::VOTE_END_FAIL:
474 m_Voting.OnReset();
475 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pMsg7->m_ClientId == -1 ? "Admin forced vote no" : "Vote failed");
476 }
477 }
478
479 return s_aRawMsg;
480 }
481 else if(*pMsgId == protocol7::NETMSGTYPE_SV_VOTESTATUS)
482 {
483 *pMsgId = NETMSGTYPE_SV_VOTESTATUS;
484 protocol7::CNetMsg_Sv_VoteStatus *pMsg7 = (protocol7::CNetMsg_Sv_VoteStatus *)pRawMsg;
485 ::CNetMsg_Sv_VoteStatus *pMsg = (::CNetMsg_Sv_VoteStatus *)s_aRawMsg;
486
487 pMsg->m_Yes = pMsg7->m_Yes;
488 pMsg->m_No = pMsg7->m_No;
489 pMsg->m_Pass = pMsg7->m_Pass;
490 pMsg->m_Total = pMsg7->m_Total;
491
492 return s_aRawMsg;
493 }
494 else if(*pMsgId == protocol7::NETMSGTYPE_SV_READYTOENTER)
495 {
496 *pMsgId = NETMSGTYPE_SV_READYTOENTER;
497 return s_aRawMsg;
498 }
499 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTDROP)
500 {
501 protocol7::CNetMsg_Sv_ClientDrop *pMsg7 = (protocol7::CNetMsg_Sv_ClientDrop *)pRawMsg;
502 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
503 {
504 dbg_msg(sys: "sixup", fmt: "Sv_ClientDrop got invalid ClientId: %d", pMsg7->m_ClientId);
505 return nullptr;
506 }
507 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
508 Client.Reset();
509
510 if(pMsg7->m_Silent)
511 return nullptr;
512
513 if(Conn != g_Config.m_ClDummy)
514 return nullptr;
515
516 static char s_aBuf[128];
517 if(pMsg7->m_pReason[0])
518 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);
519 else
520 str_format(buffer: s_aBuf, buffer_size: sizeof(s_aBuf), format: "'%s' has left the game", m_aClients[pMsg7->m_ClientId].m_aName);
521 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: s_aBuf);
522
523 return nullptr;
524 }
525 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CLIENTINFO)
526 {
527 protocol7::CNetMsg_Sv_ClientInfo *pMsg7 = (protocol7::CNetMsg_Sv_ClientInfo *)pRawMsg;
528 if(pMsg7->m_ClientId < 0 || pMsg7->m_ClientId >= MAX_CLIENTS)
529 {
530 dbg_msg(sys: "sixup", fmt: "Sv_ClientInfo got invalid ClientId: %d", pMsg7->m_ClientId);
531 return nullptr;
532 }
533
534 if(pMsg7->m_Local)
535 {
536 m_pClient->m_TranslationContext.m_aLocalClientId[Conn] = pMsg7->m_ClientId;
537 }
538 CTranslationContext::CClientData &Client = m_pClient->m_TranslationContext.m_aClients[pMsg7->m_ClientId];
539 Client.m_Active = true;
540 Client.m_Team = pMsg7->m_Team;
541 str_copy(dst&: Client.m_aName, src: pMsg7->m_pName);
542 str_copy(dst&: Client.m_aClan, src: pMsg7->m_pClan);
543 Client.m_Country = pMsg7->m_Country;
544 if(!in_range(a: Client.m_Country, lower: CountryCode::MINIMUM, upper: CountryCode::MAXIMUM))
545 {
546 Client.m_Country = CountryCode::DEFAULT;
547 }
548 ApplySkin7InfoFromGameMsg(pMsg: pMsg7, ClientId: pMsg7->m_ClientId, Conn);
549 if(m_pClient->m_TranslationContext.m_aLocalClientId[Conn] == -1)
550 return nullptr;
551 if(pMsg7->m_Silent || pMsg7->m_Local)
552 return nullptr;
553
554 if(Conn != g_Config.m_ClDummy)
555 return nullptr;
556
557 DoTeamChangeMessage7(
558 pName: pMsg7->m_pName,
559 ClientId: pMsg7->m_ClientId,
560 Team: pMsg7->m_Team,
561 pPrefix: "entered and ");
562
563 return nullptr; // we only do side effects and add stuff to the snap here
564 }
565 else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEINFO)
566 {
567 protocol7::CNetMsg_Sv_GameInfo *pMsg7 = (protocol7::CNetMsg_Sv_GameInfo *)pRawMsg;
568 m_pClient->m_TranslationContext.m_GameFlags = pMsg7->m_GameFlags;
569 m_pClient->m_TranslationContext.m_ScoreLimit = pMsg7->m_ScoreLimit;
570 m_pClient->m_TranslationContext.m_TimeLimit = pMsg7->m_TimeLimit;
571 m_pClient->m_TranslationContext.m_MatchNum = pMsg7->m_MatchNum;
572 m_pClient->m_TranslationContext.m_MatchCurrent = pMsg7->m_MatchCurrent;
573 m_pClient->m_TranslationContext.m_ShouldSendGameInfo = true;
574 return nullptr; // Added to snap by translation context
575 }
576 else if(*pMsgId == protocol7::NETMSGTYPE_SV_EMOTICON)
577 {
578 *pMsgId = NETMSGTYPE_SV_EMOTICON;
579 protocol7::CNetMsg_Sv_Emoticon *pMsg7 = (protocol7::CNetMsg_Sv_Emoticon *)pRawMsg;
580 ::CNetMsg_Sv_Emoticon *pMsg = (::CNetMsg_Sv_Emoticon *)s_aRawMsg;
581
582 pMsg->m_ClientId = pMsg7->m_ClientId;
583 pMsg->m_Emoticon = pMsg7->m_Emoticon;
584
585 return s_aRawMsg;
586 }
587 else if(*pMsgId == protocol7::NETMSGTYPE_SV_KILLMSG)
588 {
589 *pMsgId = NETMSGTYPE_SV_KILLMSG;
590
591 protocol7::CNetMsg_Sv_KillMsg *pMsg7 = (protocol7::CNetMsg_Sv_KillMsg *)pRawMsg;
592 ::CNetMsg_Sv_KillMsg *pMsg = (::CNetMsg_Sv_KillMsg *)s_aRawMsg;
593
594 pMsg->m_Killer = pMsg7->m_Killer;
595 pMsg->m_Victim = pMsg7->m_Victim;
596 pMsg->m_Weapon = pMsg7->m_Weapon;
597 pMsg->m_ModeSpecial = pMsg7->m_ModeSpecial;
598
599 return s_aRawMsg;
600 }
601 else if(*pMsgId == protocol7::NETMSGTYPE_SV_CHAT)
602 {
603 *pMsgId = NETMSGTYPE_SV_CHAT;
604
605 protocol7::CNetMsg_Sv_Chat *pMsg7 = (protocol7::CNetMsg_Sv_Chat *)pRawMsg;
606 ::CNetMsg_Sv_Chat *pMsg = (::CNetMsg_Sv_Chat *)s_aRawMsg;
607
608 if(pMsg7->m_Mode == protocol7::CHAT_WHISPER)
609 {
610 bool Receive = pMsg7->m_TargetId == m_pClient->m_TranslationContext.m_aLocalClientId[Conn];
611
612 pMsg->m_Team = Receive ? 3 : 2;
613 pMsg->m_ClientId = Receive ? pMsg7->m_ClientId : pMsg7->m_TargetId;
614 }
615 else
616 {
617 pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM ? 1 : 0;
618 pMsg->m_ClientId = pMsg7->m_ClientId;
619 }
620
621 pMsg->m_pMessage = pMsg7->m_pMessage;
622
623 return s_aRawMsg;
624 }
625 else if(*pMsgId == protocol7::NETMSGTYPE_SV_GAMEMSG)
626 {
627 int GameMsgId = pUnpacker->GetInt();
628
629 /**
630 * Prints chat message only once
631 * even if it is being sent to main tee and dummy
632 */
633 auto SendChat = [Conn, GameMsgId, this](const char *pText) -> void {
634 if(GameMsgId != protocol7::GAMEMSG_TEAM_BALANCE_VICTIM && GameMsgId != protocol7::GAMEMSG_SPEC_INVALIDID)
635 {
636 if(Conn != g_Config.m_ClDummy)
637 return;
638 }
639 m_Chat.AddLine(ClientId: -1, Team: 0, pLine: pText);
640 };
641
642 // check for valid gamemsgid
643 if(GameMsgId < 0 || GameMsgId >= protocol7::NUM_GAMEMSGS)
644 return nullptr;
645
646 int aParaI[3] = {0};
647 int NumParaI = 0;
648
649 // get paras
650 switch(gs_GameMsgList7[GameMsgId].m_ParaType)
651 {
652 case PARA_I: NumParaI = 1; break;
653 case PARA_II: NumParaI = 2; break;
654 case PARA_III: NumParaI = 3; break;
655 }
656 for(int i = 0; i < NumParaI; i++)
657 {
658 aParaI[i] = pUnpacker->GetInt();
659 }
660
661 // check for unpacking errors
662 if(pUnpacker->Error())
663 return nullptr;
664
665 // handle special messages
666 char aBuf[256];
667 bool TeamPlay = m_pClient->m_TranslationContext.m_GameFlags & protocol7::GAMEFLAG_TEAMS;
668 if(gs_GameMsgList7[GameMsgId].m_Action == DO_SPECIAL)
669 {
670 switch(GameMsgId)
671 {
672 case protocol7::GAMEMSG_CTF_DROP:
673 if(Conn == g_Config.m_ClDummy)
674 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_DROP);
675 break;
676 case protocol7::GAMEMSG_CTF_RETURN:
677 if(Conn == g_Config.m_ClDummy)
678 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_RETURN);
679 break;
680 case protocol7::GAMEMSG_TEAM_ALL:
681 {
682 const char *pMsg = "";
683 switch(GetStrTeam7(Team: aParaI[0], Teamplay: TeamPlay))
684 {
685 case STR_TEAM_GAME: pMsg = "All players were moved to the game"; break;
686 case STR_TEAM_RED: pMsg = "All players were moved to the red team"; break;
687 case STR_TEAM_BLUE: pMsg = "All players were moved to the blue team"; break;
688 case STR_TEAM_SPECTATORS: pMsg = "All players were moved to the spectators"; break;
689 }
690 m_Broadcast.DoBroadcast(pText: pMsg); // client side broadcast
691 }
692 break;
693 case protocol7::GAMEMSG_TEAM_BALANCE_VICTIM:
694 {
695 const char *pMsg = "";
696 switch(GetStrTeam7(Team: aParaI[0], Teamplay: TeamPlay))
697 {
698 case STR_TEAM_RED: pMsg = "You were moved to the red team due to team balancing"; break;
699 case STR_TEAM_BLUE: pMsg = "You were moved to the blue team due to team balancing"; break;
700 }
701 m_Broadcast.DoBroadcast(pText: pMsg); // client side broadcast
702 }
703 break;
704 case protocol7::GAMEMSG_CTF_GRAB:
705 if(Conn == g_Config.m_ClDummy)
706 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_GRAB_EN);
707 break;
708 case protocol7::GAMEMSG_GAME_PAUSED:
709 {
710 int ClientId = std::clamp(val: aParaI[0], lo: 0, hi: MAX_CLIENTS - 1);
711 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' initiated a pause", m_aClients[ClientId].m_aName);
712 SendChat(aBuf);
713 }
714 break;
715 case protocol7::GAMEMSG_CTF_CAPTURE:
716 if(Conn == g_Config.m_ClDummy)
717 m_Sounds.Enqueue(Channel: CSounds::CHN_GLOBAL, SetId: SOUND_CTF_CAPTURE);
718 int ClientId = std::clamp(val: aParaI[1], lo: 0, hi: MAX_CLIENTS - 1);
719 m_aStats[ClientId].m_FlagCaptures++;
720
721 float Time = aParaI[2] / (float)Client()->GameTickSpeed();
722 if(Time <= 60)
723 {
724 if(aParaI[0])
725 {
726 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The blue flag was captured by '%s' (%.2f seconds)", m_aClients[ClientId].m_aName, Time);
727 }
728 else
729 {
730 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The red flag was captured by '%s' (%.2f seconds)", m_aClients[ClientId].m_aName, Time);
731 }
732 }
733 else
734 {
735 if(aParaI[0])
736 {
737 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The blue flag was captured by '%s'", m_aClients[ClientId].m_aName);
738 }
739 else
740 {
741 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The red flag was captured by '%s'", m_aClients[ClientId].m_aName);
742 }
743 }
744 SendChat(aBuf);
745 }
746 return nullptr;
747 }
748
749 // build message
750 const char *pText = "";
751 if(NumParaI == 0)
752 {
753 pText = gs_GameMsgList7[GameMsgId].m_pText;
754 }
755
756 // handle message
757 switch(gs_GameMsgList7[GameMsgId].m_Action)
758 {
759 case DO_CHAT:
760 SendChat(pText);
761 break;
762 case DO_BROADCAST:
763 m_Broadcast.DoBroadcast(pText); // client side broadcast
764 break;
765 }
766
767 // no need to handle it in 0.6 since we printed
768 // the message already
769 return nullptr;
770 }
771
772 char aBuf[256];
773 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());
774 Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "client", pStr: aBuf);
775
776 return nullptr;
777}
778