1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | #include "gamecontext.h" |
4 | |
5 | #include <vector> |
6 | |
7 | #include "teeinfo.h" |
8 | #include <antibot/antibot_data.h> |
9 | #include <base/logger.h> |
10 | #include <base/math.h> |
11 | #include <base/system.h> |
12 | #include <engine/console.h> |
13 | #include <engine/engine.h> |
14 | #include <engine/map.h> |
15 | #include <engine/server/server.h> |
16 | #include <engine/shared/config.h> |
17 | #include <engine/shared/datafile.h> |
18 | #include <engine/shared/json.h> |
19 | #include <engine/shared/linereader.h> |
20 | #include <engine/shared/memheap.h> |
21 | #include <engine/storage.h> |
22 | |
23 | #include <game/collision.h> |
24 | #include <game/gamecore.h> |
25 | #include <game/mapitems.h> |
26 | #include <game/version.h> |
27 | |
28 | #include <game/generated/protocol7.h> |
29 | #include <game/generated/protocolglue.h> |
30 | |
31 | #include "entities/character.h" |
32 | #include "gamemodes/DDRace.h" |
33 | #include "gamemodes/mod.h" |
34 | #include "player.h" |
35 | #include "score.h" |
36 | |
37 | // Not thread-safe! |
38 | class CClientChatLogger : public ILogger |
39 | { |
40 | CGameContext *m_pGameServer; |
41 | int m_ClientId; |
42 | ILogger *m_pOuterLogger; |
43 | |
44 | public: |
45 | CClientChatLogger(CGameContext *pGameServer, int ClientId, ILogger *pOuterLogger) : |
46 | m_pGameServer(pGameServer), |
47 | m_ClientId(ClientId), |
48 | m_pOuterLogger(pOuterLogger) |
49 | { |
50 | } |
51 | void Log(const CLogMessage *pMessage) override; |
52 | }; |
53 | |
54 | void CClientChatLogger::Log(const CLogMessage *pMessage) |
55 | { |
56 | if(str_comp(a: pMessage->m_aSystem, b: "chatresp" ) == 0) |
57 | { |
58 | if(m_Filter.Filters(pMessage)) |
59 | { |
60 | return; |
61 | } |
62 | m_pGameServer->SendChatTarget(To: m_ClientId, pText: pMessage->Message()); |
63 | } |
64 | else |
65 | { |
66 | m_pOuterLogger->Log(pMessage); |
67 | } |
68 | } |
69 | |
70 | enum |
71 | { |
72 | RESET, |
73 | NO_RESET |
74 | }; |
75 | |
76 | void CGameContext::Construct(int Resetting) |
77 | { |
78 | m_Resetting = false; |
79 | m_pServer = 0; |
80 | |
81 | for(auto &pPlayer : m_apPlayers) |
82 | pPlayer = 0; |
83 | |
84 | mem_zero(block: &m_aLastPlayerInput, size: sizeof(m_aLastPlayerInput)); |
85 | mem_zero(block: &m_aPlayerHasInput, size: sizeof(m_aPlayerHasInput)); |
86 | |
87 | m_pController = 0; |
88 | m_aVoteCommand[0] = 0; |
89 | m_VoteType = VOTE_TYPE_UNKNOWN; |
90 | m_VoteCloseTime = 0; |
91 | m_pVoteOptionFirst = 0; |
92 | m_pVoteOptionLast = 0; |
93 | m_NumVoteOptions = 0; |
94 | m_LastMapVote = 0; |
95 | |
96 | m_SqlRandomMapResult = nullptr; |
97 | |
98 | m_pScore = nullptr; |
99 | m_NumMutes = 0; |
100 | m_NumVoteMutes = 0; |
101 | |
102 | m_LatestLog = 0; |
103 | mem_zero(block: &m_aLogs, size: sizeof(m_aLogs)); |
104 | |
105 | if(Resetting == NO_RESET) |
106 | { |
107 | m_NonEmptySince = 0; |
108 | m_pVoteOptionHeap = new CHeap(); |
109 | } |
110 | |
111 | m_aDeleteTempfile[0] = 0; |
112 | m_TeeHistorianActive = false; |
113 | } |
114 | |
115 | void CGameContext::Destruct(int Resetting) |
116 | { |
117 | for(auto &pPlayer : m_apPlayers) |
118 | delete pPlayer; |
119 | |
120 | if(Resetting == NO_RESET) |
121 | delete m_pVoteOptionHeap; |
122 | |
123 | if(m_pScore) |
124 | { |
125 | delete m_pScore; |
126 | m_pScore = nullptr; |
127 | } |
128 | } |
129 | |
130 | CGameContext::CGameContext() |
131 | { |
132 | Construct(Resetting: NO_RESET); |
133 | } |
134 | |
135 | CGameContext::CGameContext(int Reset) |
136 | { |
137 | Construct(Resetting: Reset); |
138 | } |
139 | |
140 | CGameContext::~CGameContext() |
141 | { |
142 | Destruct(Resetting: m_Resetting ? RESET : NO_RESET); |
143 | } |
144 | |
145 | void CGameContext::Clear() |
146 | { |
147 | CHeap *pVoteOptionHeap = m_pVoteOptionHeap; |
148 | CVoteOptionServer *pVoteOptionFirst = m_pVoteOptionFirst; |
149 | CVoteOptionServer *pVoteOptionLast = m_pVoteOptionLast; |
150 | int NumVoteOptions = m_NumVoteOptions; |
151 | CTuningParams Tuning = m_Tuning; |
152 | |
153 | m_Resetting = true; |
154 | this->~CGameContext(); |
155 | new(this) CGameContext(RESET); |
156 | |
157 | m_pVoteOptionHeap = pVoteOptionHeap; |
158 | m_pVoteOptionFirst = pVoteOptionFirst; |
159 | m_pVoteOptionLast = pVoteOptionLast; |
160 | m_NumVoteOptions = NumVoteOptions; |
161 | m_Tuning = Tuning; |
162 | } |
163 | |
164 | void CGameContext::TeeHistorianWrite(const void *pData, int DataSize, void *pUser) |
165 | { |
166 | CGameContext *pSelf = (CGameContext *)pUser; |
167 | aio_write(aio: pSelf->m_pTeeHistorianFile, buffer: pData, size: DataSize); |
168 | } |
169 | |
170 | void CGameContext::CommandCallback(int ClientId, int FlagMask, const char *pCmd, IConsole::IResult *pResult, void *pUser) |
171 | { |
172 | CGameContext *pSelf = (CGameContext *)pUser; |
173 | if(pSelf->m_TeeHistorianActive) |
174 | { |
175 | pSelf->m_TeeHistorian.RecordConsoleCommand(ClientId, FlagMask, pCmd, pResult); |
176 | } |
177 | } |
178 | |
179 | CNetObj_PlayerInput CGameContext::GetLastPlayerInput(int ClientId) const |
180 | { |
181 | dbg_assert(0 <= ClientId && ClientId < MAX_CLIENTS, "invalid ClientId" ); |
182 | return m_aLastPlayerInput[ClientId]; |
183 | } |
184 | |
185 | class CCharacter *CGameContext::GetPlayerChar(int ClientId) |
186 | { |
187 | if(ClientId < 0 || ClientId >= MAX_CLIENTS || !m_apPlayers[ClientId]) |
188 | return 0; |
189 | return m_apPlayers[ClientId]->GetCharacter(); |
190 | } |
191 | |
192 | bool CGameContext::EmulateBug(int Bug) |
193 | { |
194 | return m_MapBugs.Contains(Bug); |
195 | } |
196 | |
197 | void CGameContext::FillAntibot(CAntibotRoundData *pData) |
198 | { |
199 | if(!pData->m_Map.m_pTiles) |
200 | { |
201 | Collision()->FillAntibot(pMapData: &pData->m_Map); |
202 | } |
203 | pData->m_Tick = Server()->Tick(); |
204 | mem_zero(block: pData->m_aCharacters, size: sizeof(pData->m_aCharacters)); |
205 | for(int i = 0; i < MAX_CLIENTS; i++) |
206 | { |
207 | CAntibotCharacterData *pChar = &pData->m_aCharacters[i]; |
208 | for(auto &LatestInput : pChar->m_aLatestInputs) |
209 | { |
210 | LatestInput.m_TargetX = -1; |
211 | LatestInput.m_TargetY = -1; |
212 | } |
213 | pChar->m_Alive = false; |
214 | pChar->m_Pause = false; |
215 | pChar->m_Team = -1; |
216 | |
217 | pChar->m_Pos = vec2(-1, -1); |
218 | pChar->m_Vel = vec2(0, 0); |
219 | pChar->m_Angle = -1; |
220 | pChar->m_HookedPlayer = -1; |
221 | pChar->m_SpawnTick = -1; |
222 | pChar->m_WeaponChangeTick = -1; |
223 | |
224 | if(m_apPlayers[i]) |
225 | { |
226 | str_copy(dst: pChar->m_aName, src: Server()->ClientName(ClientId: i), dst_size: sizeof(pChar->m_aName)); |
227 | CCharacter *pGameChar = m_apPlayers[i]->GetCharacter(); |
228 | pChar->m_Alive = (bool)pGameChar; |
229 | pChar->m_Pause = m_apPlayers[i]->IsPaused(); |
230 | pChar->m_Team = m_apPlayers[i]->GetTeam(); |
231 | if(pGameChar) |
232 | { |
233 | pGameChar->FillAntibot(pData: pChar); |
234 | } |
235 | } |
236 | } |
237 | } |
238 | |
239 | void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount, CClientMask Mask) |
240 | { |
241 | float a = 3 * pi / 2 + Angle; |
242 | //float a = get_angle(dir); |
243 | float s = a - pi / 3; |
244 | float e = a + pi / 3; |
245 | for(int i = 0; i < Amount; i++) |
246 | { |
247 | float f = mix(a: s, b: e, amount: (i + 1) / (float)(Amount + 2)); |
248 | CNetEvent_DamageInd *pEvent = m_Events.Create<CNetEvent_DamageInd>(Mask); |
249 | if(pEvent) |
250 | { |
251 | pEvent->m_X = (int)Pos.x; |
252 | pEvent->m_Y = (int)Pos.y; |
253 | pEvent->m_Angle = (int)(f * 256.0f); |
254 | } |
255 | } |
256 | } |
257 | |
258 | void CGameContext::CreateHammerHit(vec2 Pos, CClientMask Mask) |
259 | { |
260 | // create the event |
261 | CNetEvent_HammerHit *pEvent = m_Events.Create<CNetEvent_HammerHit>(Mask); |
262 | if(pEvent) |
263 | { |
264 | pEvent->m_X = (int)Pos.x; |
265 | pEvent->m_Y = (int)Pos.y; |
266 | } |
267 | } |
268 | |
269 | void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage, int ActivatedTeam, CClientMask Mask) |
270 | { |
271 | // create the event |
272 | CNetEvent_Explosion *pEvent = m_Events.Create<CNetEvent_Explosion>(Mask); |
273 | if(pEvent) |
274 | { |
275 | pEvent->m_X = (int)Pos.x; |
276 | pEvent->m_Y = (int)Pos.y; |
277 | } |
278 | |
279 | // deal damage |
280 | CEntity *apEnts[MAX_CLIENTS]; |
281 | float Radius = 135.0f; |
282 | float InnerRadius = 48.0f; |
283 | int Num = m_World.FindEntities(Pos, Radius, ppEnts: apEnts, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER); |
284 | CClientMask TeamMask = CClientMask().set(); |
285 | for(int i = 0; i < Num; i++) |
286 | { |
287 | auto *pChr = static_cast<CCharacter *>(apEnts[i]); |
288 | vec2 Diff = pChr->m_Pos - Pos; |
289 | vec2 ForceDir(0, 1); |
290 | float l = length(a: Diff); |
291 | if(l) |
292 | ForceDir = normalize(v: Diff); |
293 | l = 1 - clamp(val: (l - InnerRadius) / (Radius - InnerRadius), lo: 0.0f, hi: 1.0f); |
294 | float Strength; |
295 | if(Owner == -1 || !m_apPlayers[Owner] || !m_apPlayers[Owner]->m_TuneZone) |
296 | Strength = Tuning()->m_ExplosionStrength; |
297 | else |
298 | Strength = TuningList()[m_apPlayers[Owner]->m_TuneZone].m_ExplosionStrength; |
299 | |
300 | float Dmg = Strength * l; |
301 | if(!(int)Dmg) |
302 | continue; |
303 | |
304 | if((GetPlayerChar(ClientId: Owner) ? !GetPlayerChar(ClientId: Owner)->GrenadeHitDisabled() : g_Config.m_SvHit) || NoDamage || Owner == pChr->GetPlayer()->GetCid()) |
305 | { |
306 | if(Owner != -1 && pChr->IsAlive() && !pChr->CanCollide(ClientId: Owner)) |
307 | continue; |
308 | if(Owner == -1 && ActivatedTeam != -1 && pChr->IsAlive() && pChr->Team() != ActivatedTeam) |
309 | continue; |
310 | |
311 | // Explode at most once per team |
312 | int PlayerTeam = pChr->Team(); |
313 | if((GetPlayerChar(ClientId: Owner) ? GetPlayerChar(ClientId: Owner)->GrenadeHitDisabled() : !g_Config.m_SvHit) || NoDamage) |
314 | { |
315 | if(PlayerTeam == TEAM_SUPER) |
316 | continue; |
317 | if(!TeamMask.test(position: PlayerTeam)) |
318 | continue; |
319 | TeamMask.reset(position: PlayerTeam); |
320 | } |
321 | |
322 | pChr->TakeDamage(Force: ForceDir * Dmg * 2, Dmg: (int)Dmg, From: Owner, Weapon); |
323 | } |
324 | } |
325 | } |
326 | |
327 | void CGameContext::CreatePlayerSpawn(vec2 Pos, CClientMask Mask) |
328 | { |
329 | // create the event |
330 | CNetEvent_Spawn *pEvent = m_Events.Create<CNetEvent_Spawn>(Mask); |
331 | if(pEvent) |
332 | { |
333 | pEvent->m_X = (int)Pos.x; |
334 | pEvent->m_Y = (int)Pos.y; |
335 | } |
336 | } |
337 | |
338 | void CGameContext::CreateDeath(vec2 Pos, int ClientId, CClientMask Mask) |
339 | { |
340 | // create the event |
341 | CNetEvent_Death *pEvent = m_Events.Create<CNetEvent_Death>(Mask); |
342 | if(pEvent) |
343 | { |
344 | pEvent->m_X = (int)Pos.x; |
345 | pEvent->m_Y = (int)Pos.y; |
346 | pEvent->m_ClientId = ClientId; |
347 | } |
348 | } |
349 | |
350 | void CGameContext::CreateSound(vec2 Pos, int Sound, CClientMask Mask) |
351 | { |
352 | if(Sound < 0) |
353 | return; |
354 | |
355 | // create a sound |
356 | CNetEvent_SoundWorld *pEvent = m_Events.Create<CNetEvent_SoundWorld>(Mask); |
357 | if(pEvent) |
358 | { |
359 | pEvent->m_X = (int)Pos.x; |
360 | pEvent->m_Y = (int)Pos.y; |
361 | pEvent->m_SoundId = Sound; |
362 | } |
363 | } |
364 | |
365 | void CGameContext::CreateSoundGlobal(int Sound, int Target) const |
366 | { |
367 | if(Sound < 0) |
368 | return; |
369 | |
370 | CNetMsg_Sv_SoundGlobal Msg; |
371 | Msg.m_SoundId = Sound; |
372 | if(Target == -2) |
373 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_NOSEND, ClientId: -1); |
374 | else |
375 | { |
376 | int Flag = MSGFLAG_VITAL; |
377 | if(Target != -1) |
378 | Flag |= MSGFLAG_NORECORD; |
379 | Server()->SendPackMsg(pMsg: &Msg, Flags: Flag, ClientId: Target); |
380 | } |
381 | } |
382 | |
383 | void CGameContext::SnapSwitchers(int SnappingClient) |
384 | { |
385 | if(Switchers().empty()) |
386 | return; |
387 | |
388 | CPlayer *pPlayer = SnappingClient != SERVER_DEMO_CLIENT ? m_apPlayers[SnappingClient] : 0; |
389 | int Team = pPlayer && pPlayer->GetCharacter() ? pPlayer->GetCharacter()->Team() : 0; |
390 | |
391 | if(pPlayer && (pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorId != SPEC_FREEVIEW && m_apPlayers[pPlayer->m_SpectatorId] && m_apPlayers[pPlayer->m_SpectatorId]->GetCharacter()) |
392 | Team = m_apPlayers[pPlayer->m_SpectatorId]->GetCharacter()->Team(); |
393 | |
394 | if(Team == TEAM_SUPER) |
395 | return; |
396 | |
397 | int SentTeam = Team; |
398 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) |
399 | SentTeam = 0; |
400 | |
401 | CNetObj_SwitchState *pSwitchState = Server()->SnapNewItem<CNetObj_SwitchState>(Id: SentTeam); |
402 | if(!pSwitchState) |
403 | return; |
404 | |
405 | pSwitchState->m_HighestSwitchNumber = clamp(val: (int)Switchers().size() - 1, lo: 0, hi: 255); |
406 | mem_zero(block: pSwitchState->m_aStatus, size: sizeof(pSwitchState->m_aStatus)); |
407 | |
408 | std::vector<std::pair<int, int>> vEndTicks; // <EndTick, SwitchNumber> |
409 | |
410 | for(int i = 0; i <= pSwitchState->m_HighestSwitchNumber; i++) |
411 | { |
412 | int Status = (int)Switchers()[i].m_aStatus[Team]; |
413 | pSwitchState->m_aStatus[i / 32] |= (Status << (i % 32)); |
414 | |
415 | int EndTick = Switchers()[i].m_aEndTick[Team]; |
416 | if(EndTick > 0 && EndTick < Server()->Tick() + 3 * Server()->TickSpeed() && Switchers()[i].m_aLastUpdateTick[Team] < Server()->Tick()) |
417 | { |
418 | // only keep track of EndTicks that have less than three second left and are not currently being updated by a player being present on a switch tile, to limit how often these are sent |
419 | vEndTicks.emplace_back(args&: Switchers()[i].m_aEndTick[Team], args&: i); |
420 | } |
421 | } |
422 | |
423 | // send the endtick of switchers that are about to toggle back (up to four, prioritizing those with the earliest endticks) |
424 | mem_zero(block: pSwitchState->m_aSwitchNumbers, size: sizeof(pSwitchState->m_aSwitchNumbers)); |
425 | mem_zero(block: pSwitchState->m_aEndTicks, size: sizeof(pSwitchState->m_aEndTicks)); |
426 | |
427 | std::sort(first: vEndTicks.begin(), last: vEndTicks.end()); |
428 | const int NumTimedSwitchers = minimum(a: (int)vEndTicks.size(), b: (int)std::size(pSwitchState->m_aEndTicks)); |
429 | |
430 | for(int i = 0; i < NumTimedSwitchers; i++) |
431 | { |
432 | pSwitchState->m_aSwitchNumbers[i] = vEndTicks[i].second; |
433 | pSwitchState->m_aEndTicks[i] = vEndTicks[i].first; |
434 | } |
435 | } |
436 | |
437 | bool CGameContext::SnapLaserObject(const CSnapContext &Context, int SnapId, const vec2 &To, const vec2 &From, int StartTick, int Owner, int LaserType, int Subtype, int SwitchNumber) const |
438 | { |
439 | if(Context.GetClientVersion() >= VERSION_DDNET_MULTI_LASER) |
440 | { |
441 | CNetObj_DDNetLaser *pObj = Server()->SnapNewItem<CNetObj_DDNetLaser>(Id: SnapId); |
442 | if(!pObj) |
443 | return false; |
444 | |
445 | pObj->m_ToX = (int)To.x; |
446 | pObj->m_ToY = (int)To.y; |
447 | pObj->m_FromX = (int)From.x; |
448 | pObj->m_FromY = (int)From.y; |
449 | pObj->m_StartTick = StartTick; |
450 | pObj->m_Owner = Owner; |
451 | pObj->m_Type = LaserType; |
452 | pObj->m_Subtype = Subtype; |
453 | pObj->m_SwitchNumber = SwitchNumber; |
454 | pObj->m_Flags = 0; |
455 | } |
456 | else |
457 | { |
458 | CNetObj_Laser *pObj = Server()->SnapNewItem<CNetObj_Laser>(Id: SnapId); |
459 | if(!pObj) |
460 | return false; |
461 | |
462 | pObj->m_X = (int)To.x; |
463 | pObj->m_Y = (int)To.y; |
464 | pObj->m_FromX = (int)From.x; |
465 | pObj->m_FromY = (int)From.y; |
466 | pObj->m_StartTick = StartTick; |
467 | } |
468 | |
469 | return true; |
470 | } |
471 | |
472 | bool CGameContext::SnapPickup(const CSnapContext &Context, int SnapId, const vec2 &Pos, int Type, int SubType, int SwitchNumber) const |
473 | { |
474 | if(Context.IsSixup()) |
475 | { |
476 | protocol7::CNetObj_Pickup *pPickup = Server()->SnapNewItem<protocol7::CNetObj_Pickup>(Id: SnapId); |
477 | if(!pPickup) |
478 | return false; |
479 | |
480 | pPickup->m_X = (int)Pos.x; |
481 | pPickup->m_Y = (int)Pos.y; |
482 | |
483 | if(Type == POWERUP_WEAPON) |
484 | pPickup->m_Type = SubType == WEAPON_SHOTGUN ? protocol7::PICKUP_SHOTGUN : SubType == WEAPON_GRENADE ? protocol7::PICKUP_GRENADE : protocol7::PICKUP_LASER; |
485 | else if(Type == POWERUP_NINJA) |
486 | pPickup->m_Type = protocol7::PICKUP_NINJA; |
487 | else if(Type == POWERUP_ARMOR) |
488 | pPickup->m_Type = protocol7::PICKUP_ARMOR; |
489 | } |
490 | else if(Context.GetClientVersion() >= VERSION_DDNET_ENTITY_NETOBJS) |
491 | { |
492 | CNetObj_DDNetPickup *pPickup = Server()->SnapNewItem<CNetObj_DDNetPickup>(Id: SnapId); |
493 | if(!pPickup) |
494 | return false; |
495 | |
496 | pPickup->m_X = (int)Pos.x; |
497 | pPickup->m_Y = (int)Pos.y; |
498 | pPickup->m_Type = Type; |
499 | pPickup->m_Subtype = SubType; |
500 | pPickup->m_SwitchNumber = SwitchNumber; |
501 | } |
502 | else |
503 | { |
504 | CNetObj_Pickup *pPickup = Server()->SnapNewItem<CNetObj_Pickup>(Id: SnapId); |
505 | if(!pPickup) |
506 | return false; |
507 | |
508 | pPickup->m_X = (int)Pos.x; |
509 | pPickup->m_Y = (int)Pos.y; |
510 | |
511 | pPickup->m_Type = Type; |
512 | if(Context.GetClientVersion() < VERSION_DDNET_WEAPON_SHIELDS) |
513 | { |
514 | if(Type >= POWERUP_ARMOR_SHOTGUN && Type <= POWERUP_ARMOR_LASER) |
515 | { |
516 | pPickup->m_Type = POWERUP_ARMOR; |
517 | } |
518 | } |
519 | pPickup->m_Subtype = SubType; |
520 | } |
521 | |
522 | return true; |
523 | } |
524 | |
525 | void CGameContext::CallVote(int ClientId, const char *pDesc, const char *pCmd, const char *pReason, const char *pChatmsg, const char *pSixupDesc) |
526 | { |
527 | // check if a vote is already running |
528 | if(m_VoteCloseTime) |
529 | return; |
530 | |
531 | int64_t Now = Server()->Tick(); |
532 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
533 | |
534 | if(!pPlayer) |
535 | return; |
536 | |
537 | SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: pChatmsg, SpamProtectionClientId: -1, Flags: CHAT_SIX); |
538 | if(!pSixupDesc) |
539 | pSixupDesc = pDesc; |
540 | |
541 | m_VoteCreator = ClientId; |
542 | StartVote(pDesc, pCommand: pCmd, pReason, pSixupDesc); |
543 | pPlayer->m_Vote = 1; |
544 | pPlayer->m_VotePos = m_VotePos = 1; |
545 | pPlayer->m_LastVoteCall = Now; |
546 | } |
547 | |
548 | void CGameContext::SendChatTarget(int To, const char *pText, int Flags) const |
549 | { |
550 | CNetMsg_Sv_Chat Msg; |
551 | Msg.m_Team = 0; |
552 | Msg.m_ClientId = -1; |
553 | Msg.m_pMessage = pText; |
554 | |
555 | if(g_Config.m_SvDemoChat) |
556 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NOSEND, ClientId: SERVER_DEMO_CLIENT); |
557 | |
558 | if(To == -1) |
559 | { |
560 | for(int i = 0; i < Server()->MaxClients(); i++) |
561 | { |
562 | if(!((Server()->IsSixup(ClientId: i) && (Flags & CHAT_SIXUP)) || |
563 | (!Server()->IsSixup(ClientId: i) && (Flags & CHAT_SIX)))) |
564 | continue; |
565 | |
566 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
567 | } |
568 | } |
569 | else |
570 | { |
571 | if(!((Server()->IsSixup(ClientId: To) && (Flags & CHAT_SIXUP)) || |
572 | (!Server()->IsSixup(ClientId: To) && (Flags & CHAT_SIX)))) |
573 | return; |
574 | |
575 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: To); |
576 | } |
577 | } |
578 | |
579 | void CGameContext::SendChatTeam(int Team, const char *pText) const |
580 | { |
581 | for(int i = 0; i < MAX_CLIENTS; i++) |
582 | if(m_apPlayers[i] != nullptr && GetDDRaceTeam(ClientId: i) == Team) |
583 | SendChatTarget(To: i, pText); |
584 | } |
585 | |
586 | void CGameContext::SendChat(int ChatterClientId, int Team, const char *pText, int SpamProtectionClientId, int Flags) |
587 | { |
588 | if(SpamProtectionClientId >= 0 && SpamProtectionClientId < MAX_CLIENTS) |
589 | if(ProcessSpamProtection(ClientId: SpamProtectionClientId)) |
590 | return; |
591 | |
592 | char aBuf[256], aText[256]; |
593 | str_copy(dst: aText, src: pText, dst_size: sizeof(aText)); |
594 | if(ChatterClientId >= 0 && ChatterClientId < MAX_CLIENTS) |
595 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d:%d:%s: %s" , ChatterClientId, Team, Server()->ClientName(ClientId: ChatterClientId), aText); |
596 | else if(ChatterClientId == -2) |
597 | { |
598 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "### %s" , aText); |
599 | str_copy(dst: aText, src: aBuf, dst_size: sizeof(aText)); |
600 | ChatterClientId = -1; |
601 | } |
602 | else |
603 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "*** %s" , aText); |
604 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: Team != CHAT_ALL ? "teamchat" : "chat" , pStr: aBuf); |
605 | |
606 | if(Team == CHAT_ALL) |
607 | { |
608 | CNetMsg_Sv_Chat Msg; |
609 | Msg.m_Team = 0; |
610 | Msg.m_ClientId = ChatterClientId; |
611 | Msg.m_pMessage = aText; |
612 | |
613 | // pack one for the recording only |
614 | if(g_Config.m_SvDemoChat) |
615 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NOSEND, ClientId: SERVER_DEMO_CLIENT); |
616 | |
617 | // send to the clients |
618 | for(int i = 0; i < Server()->MaxClients(); i++) |
619 | { |
620 | if(!m_apPlayers[i]) |
621 | continue; |
622 | bool Send = (Server()->IsSixup(ClientId: i) && (Flags & CHAT_SIXUP)) || |
623 | (!Server()->IsSixup(ClientId: i) && (Flags & CHAT_SIX)); |
624 | |
625 | if(!m_apPlayers[i]->m_DND && Send) |
626 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
627 | } |
628 | |
629 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Chat: %s" , aText); |
630 | LogEvent(Description: aBuf, ClientId: ChatterClientId); |
631 | } |
632 | else |
633 | { |
634 | CTeamsCore *pTeams = &m_pController->Teams().m_Core; |
635 | CNetMsg_Sv_Chat Msg; |
636 | Msg.m_Team = 1; |
637 | Msg.m_ClientId = ChatterClientId; |
638 | Msg.m_pMessage = aText; |
639 | |
640 | // pack one for the recording only |
641 | if(g_Config.m_SvDemoChat) |
642 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NOSEND, ClientId: SERVER_DEMO_CLIENT); |
643 | |
644 | // send to the clients |
645 | for(int i = 0; i < Server()->MaxClients(); i++) |
646 | { |
647 | if(m_apPlayers[i] != 0) |
648 | { |
649 | if(Team == CHAT_SPEC) |
650 | { |
651 | if(m_apPlayers[i]->GetTeam() == CHAT_SPEC) |
652 | { |
653 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
654 | } |
655 | } |
656 | else |
657 | { |
658 | if(pTeams->Team(ClientId: i) == Team && m_apPlayers[i]->GetTeam() != CHAT_SPEC) |
659 | { |
660 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
661 | } |
662 | } |
663 | } |
664 | } |
665 | } |
666 | } |
667 | |
668 | void CGameContext::SendStartWarning(int ClientId, const char *pMessage) |
669 | { |
670 | CCharacter *pChr = GetPlayerChar(ClientId); |
671 | if(pChr && pChr->m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) |
672 | { |
673 | SendChatTarget(To: ClientId, pText: pMessage); |
674 | pChr->m_LastStartWarning = Server()->Tick(); |
675 | } |
676 | } |
677 | |
678 | void CGameContext::SendEmoticon(int ClientId, int Emoticon, int TargetClientId) const |
679 | { |
680 | CNetMsg_Sv_Emoticon Msg; |
681 | Msg.m_ClientId = ClientId; |
682 | Msg.m_Emoticon = Emoticon; |
683 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId: TargetClientId); |
684 | } |
685 | |
686 | void CGameContext::SendWeaponPickup(int ClientId, int Weapon) const |
687 | { |
688 | CNetMsg_Sv_WeaponPickup Msg; |
689 | Msg.m_Weapon = Weapon; |
690 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
691 | } |
692 | |
693 | void CGameContext::SendMotd(int ClientId) const |
694 | { |
695 | CNetMsg_Sv_Motd Msg; |
696 | Msg.m_pMessage = g_Config.m_SvMotd; |
697 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
698 | } |
699 | |
700 | void CGameContext::SendSettings(int ClientId) const |
701 | { |
702 | protocol7::CNetMsg_Sv_ServerSettings Msg; |
703 | Msg.m_KickVote = g_Config.m_SvVoteKick; |
704 | Msg.m_KickMin = g_Config.m_SvVoteKickMin; |
705 | Msg.m_SpecVote = g_Config.m_SvVoteSpectate; |
706 | Msg.m_TeamLock = 0; |
707 | Msg.m_TeamBalance = 0; |
708 | Msg.m_PlayerSlots = g_Config.m_SvMaxClients - g_Config.m_SvSpectatorSlots; |
709 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
710 | } |
711 | |
712 | void CGameContext::SendBroadcast(const char *pText, int ClientId, bool IsImportant) |
713 | { |
714 | CNetMsg_Sv_Broadcast Msg; |
715 | Msg.m_pMessage = pText; |
716 | |
717 | if(ClientId == -1) |
718 | { |
719 | dbg_assert(IsImportant, "broadcast messages to all players must be important" ); |
720 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
721 | |
722 | for(auto &pPlayer : m_apPlayers) |
723 | { |
724 | if(pPlayer) |
725 | { |
726 | pPlayer->m_LastBroadcastImportance = true; |
727 | pPlayer->m_LastBroadcast = Server()->Tick(); |
728 | } |
729 | } |
730 | return; |
731 | } |
732 | |
733 | if(!m_apPlayers[ClientId]) |
734 | return; |
735 | |
736 | if(!IsImportant && m_apPlayers[ClientId]->m_LastBroadcastImportance && m_apPlayers[ClientId]->m_LastBroadcast > Server()->Tick() - Server()->TickSpeed() * 10) |
737 | return; |
738 | |
739 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
740 | m_apPlayers[ClientId]->m_LastBroadcast = Server()->Tick(); |
741 | m_apPlayers[ClientId]->m_LastBroadcastImportance = IsImportant; |
742 | } |
743 | |
744 | void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char *pReason, const char *pSixupDesc) |
745 | { |
746 | // reset votes |
747 | m_VoteEnforce = VOTE_ENFORCE_UNKNOWN; |
748 | m_VoteEnforcer = -1; |
749 | for(auto &pPlayer : m_apPlayers) |
750 | { |
751 | if(pPlayer) |
752 | { |
753 | pPlayer->m_Vote = 0; |
754 | pPlayer->m_VotePos = 0; |
755 | } |
756 | } |
757 | |
758 | // start vote |
759 | m_VoteCloseTime = time_get() + time_freq() * g_Config.m_SvVoteTime; |
760 | str_copy(dst: m_aVoteDescription, src: pDesc, dst_size: sizeof(m_aVoteDescription)); |
761 | str_copy(dst: m_aSixupVoteDescription, src: pSixupDesc, dst_size: sizeof(m_aSixupVoteDescription)); |
762 | str_copy(dst: m_aVoteCommand, src: pCommand, dst_size: sizeof(m_aVoteCommand)); |
763 | str_copy(dst: m_aVoteReason, src: pReason, dst_size: sizeof(m_aVoteReason)); |
764 | SendVoteSet(ClientId: -1); |
765 | m_VoteUpdate = true; |
766 | } |
767 | |
768 | void CGameContext::EndVote() |
769 | { |
770 | m_VoteCloseTime = 0; |
771 | SendVoteSet(ClientId: -1); |
772 | } |
773 | |
774 | void CGameContext::SendVoteSet(int ClientId) |
775 | { |
776 | ::CNetMsg_Sv_VoteSet Msg6; |
777 | protocol7::CNetMsg_Sv_VoteSet Msg7; |
778 | |
779 | Msg7.m_ClientId = m_VoteCreator; |
780 | if(m_VoteCloseTime) |
781 | { |
782 | Msg6.m_Timeout = Msg7.m_Timeout = (m_VoteCloseTime - time_get()) / time_freq(); |
783 | Msg6.m_pDescription = m_aVoteDescription; |
784 | Msg7.m_pDescription = m_aSixupVoteDescription; |
785 | Msg6.m_pReason = Msg7.m_pReason = m_aVoteReason; |
786 | |
787 | int &Type = (Msg7.m_Type = protocol7::VOTE_UNKNOWN); |
788 | if(IsKickVote()) |
789 | Type = protocol7::VOTE_START_KICK; |
790 | else if(IsSpecVote()) |
791 | Type = protocol7::VOTE_START_SPEC; |
792 | else if(IsOptionVote()) |
793 | Type = protocol7::VOTE_START_OP; |
794 | } |
795 | else |
796 | { |
797 | Msg6.m_Timeout = Msg7.m_Timeout = 0; |
798 | Msg6.m_pDescription = Msg7.m_pDescription = "" ; |
799 | Msg6.m_pReason = Msg7.m_pReason = "" ; |
800 | |
801 | int &Type = (Msg7.m_Type = protocol7::VOTE_UNKNOWN); |
802 | if(m_VoteEnforce == VOTE_ENFORCE_NO || m_VoteEnforce == VOTE_ENFORCE_NO_ADMIN) |
803 | Type = protocol7::VOTE_END_FAIL; |
804 | else if(m_VoteEnforce == VOTE_ENFORCE_YES || m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN) |
805 | Type = protocol7::VOTE_END_PASS; |
806 | else if(m_VoteEnforce == VOTE_ENFORCE_ABORT || m_VoteEnforce == VOTE_ENFORCE_CANCEL) |
807 | Type = protocol7::VOTE_END_ABORT; |
808 | |
809 | if(m_VoteEnforce == VOTE_ENFORCE_NO_ADMIN || m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN) |
810 | Msg7.m_ClientId = -1; |
811 | } |
812 | |
813 | if(ClientId == -1) |
814 | { |
815 | for(int i = 0; i < Server()->MaxClients(); i++) |
816 | { |
817 | if(!m_apPlayers[i]) |
818 | continue; |
819 | if(!Server()->IsSixup(ClientId: i)) |
820 | Server()->SendPackMsg(pMsg: &Msg6, Flags: MSGFLAG_VITAL, ClientId: i); |
821 | else |
822 | Server()->SendPackMsg(pMsg: &Msg7, Flags: MSGFLAG_VITAL, ClientId: i); |
823 | } |
824 | } |
825 | else |
826 | { |
827 | if(!Server()->IsSixup(ClientId)) |
828 | Server()->SendPackMsg(pMsg: &Msg6, Flags: MSGFLAG_VITAL, ClientId); |
829 | else |
830 | Server()->SendPackMsg(pMsg: &Msg7, Flags: MSGFLAG_VITAL, ClientId); |
831 | } |
832 | } |
833 | |
834 | void CGameContext::SendVoteStatus(int ClientId, int Total, int Yes, int No) |
835 | { |
836 | if(ClientId == -1) |
837 | { |
838 | for(int i = 0; i < MAX_CLIENTS; ++i) |
839 | if(Server()->ClientIngame(ClientId: i)) |
840 | SendVoteStatus(ClientId: i, Total, Yes, No); |
841 | return; |
842 | } |
843 | |
844 | if(Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientId] && m_apPlayers[ClientId]->GetClientVersion() <= VERSION_DDRACE) |
845 | { |
846 | Yes = (Yes * VANILLA_MAX_CLIENTS) / (float)Total; |
847 | No = (No * VANILLA_MAX_CLIENTS) / (float)Total; |
848 | Total = VANILLA_MAX_CLIENTS; |
849 | } |
850 | |
851 | CNetMsg_Sv_VoteStatus Msg = {.m_Yes: 0}; |
852 | Msg.m_Total = Total; |
853 | Msg.m_Yes = Yes; |
854 | Msg.m_No = No; |
855 | Msg.m_Pass = Total - (Yes + No); |
856 | |
857 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
858 | } |
859 | |
860 | void CGameContext::AbortVoteKickOnDisconnect(int ClientId) |
861 | { |
862 | if(m_VoteCloseTime && ((str_startswith(str: m_aVoteCommand, prefix: "kick " ) && str_toint(str: &m_aVoteCommand[5]) == ClientId) || |
863 | (str_startswith(str: m_aVoteCommand, prefix: "set_team " ) && str_toint(str: &m_aVoteCommand[9]) == ClientId))) |
864 | m_VoteEnforce = VOTE_ENFORCE_ABORT; |
865 | } |
866 | |
867 | void CGameContext::CheckPureTuning() |
868 | { |
869 | // might not be created yet during start up |
870 | if(!m_pController) |
871 | return; |
872 | |
873 | if(str_comp(a: m_pController->m_pGameType, b: "DM" ) == 0 || |
874 | str_comp(a: m_pController->m_pGameType, b: "TDM" ) == 0 || |
875 | str_comp(a: m_pController->m_pGameType, b: "CTF" ) == 0) |
876 | { |
877 | CTuningParams p; |
878 | if(mem_comp(a: &p, b: &m_Tuning, size: sizeof(p)) != 0) |
879 | { |
880 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: "resetting tuning due to pure server" ); |
881 | m_Tuning = p; |
882 | } |
883 | } |
884 | } |
885 | |
886 | void CGameContext::SendTuningParams(int ClientId, int Zone) |
887 | { |
888 | if(ClientId == -1) |
889 | { |
890 | for(int i = 0; i < MAX_CLIENTS; ++i) |
891 | { |
892 | if(m_apPlayers[i]) |
893 | { |
894 | if(m_apPlayers[i]->GetCharacter()) |
895 | { |
896 | if(m_apPlayers[i]->GetCharacter()->m_TuneZone == Zone) |
897 | SendTuningParams(ClientId: i, Zone); |
898 | } |
899 | else if(m_apPlayers[i]->m_TuneZone == Zone) |
900 | { |
901 | SendTuningParams(ClientId: i, Zone); |
902 | } |
903 | } |
904 | } |
905 | return; |
906 | } |
907 | |
908 | CheckPureTuning(); |
909 | |
910 | CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS); |
911 | int *pParams = 0; |
912 | if(Zone == 0) |
913 | pParams = (int *)&m_Tuning; |
914 | else |
915 | pParams = (int *)&(m_aTuningList[Zone]); |
916 | |
917 | for(unsigned i = 0; i < sizeof(m_Tuning) / sizeof(int); i++) |
918 | { |
919 | if(m_apPlayers[ClientId] && m_apPlayers[ClientId]->GetCharacter()) |
920 | { |
921 | if((i == 30) // laser_damage is removed from 0.7 |
922 | && (Server()->IsSixup(ClientId))) |
923 | { |
924 | continue; |
925 | } |
926 | else if((i == 31) // collision |
927 | && (m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_SOLO || m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_NOCOLL)) |
928 | { |
929 | Msg.AddInt(i: 0); |
930 | } |
931 | else if((i == 32) // hooking |
932 | && (m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_SOLO || m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_NOHOOK)) |
933 | { |
934 | Msg.AddInt(i: 0); |
935 | } |
936 | else if((i == 3) // ground jump impulse |
937 | && m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_NOJUMP) |
938 | { |
939 | Msg.AddInt(i: 0); |
940 | } |
941 | else if((i == 33) // jetpack |
942 | && !(m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_JETPACK)) |
943 | { |
944 | Msg.AddInt(i: 0); |
945 | } |
946 | else if((i == 36) // hammer hit |
947 | && m_apPlayers[ClientId]->GetCharacter()->NeededFaketuning() & FAKETUNE_NOHAMMER) |
948 | { |
949 | Msg.AddInt(i: 0); |
950 | } |
951 | else |
952 | { |
953 | Msg.AddInt(i: pParams[i]); |
954 | } |
955 | } |
956 | else |
957 | Msg.AddInt(i: pParams[i]); // if everything is normal just send true tunings |
958 | } |
959 | Server()->SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
960 | } |
961 | |
962 | void CGameContext::OnPreTickTeehistorian() |
963 | { |
964 | if(!m_TeeHistorianActive) |
965 | return; |
966 | |
967 | for(int i = 0; i < MAX_CLIENTS; i++) |
968 | { |
969 | if(m_apPlayers[i] != nullptr) |
970 | m_TeeHistorian.RecordPlayerTeam(ClientId: i, Team: GetDDRaceTeam(ClientId: i)); |
971 | else |
972 | m_TeeHistorian.RecordPlayerTeam(ClientId: i, Team: 0); |
973 | } |
974 | for(int i = 0; i < MAX_CLIENTS; i++) |
975 | { |
976 | m_TeeHistorian.RecordTeamPractice(Team: i, Practice: m_pController->Teams().IsPractice(Team: i)); |
977 | } |
978 | } |
979 | |
980 | void CGameContext::OnTick() |
981 | { |
982 | // check tuning |
983 | CheckPureTuning(); |
984 | |
985 | if(m_TeeHistorianActive) |
986 | { |
987 | int Error = aio_error(aio: m_pTeeHistorianFile); |
988 | if(Error) |
989 | { |
990 | dbg_msg(sys: "teehistorian" , fmt: "error writing to file, err=%d" , Error); |
991 | Server()->SetErrorShutdown("teehistorian io error" ); |
992 | } |
993 | |
994 | if(!m_TeeHistorian.Starting()) |
995 | { |
996 | m_TeeHistorian.EndInputs(); |
997 | m_TeeHistorian.EndTick(); |
998 | } |
999 | m_TeeHistorian.BeginTick(Tick: Server()->Tick()); |
1000 | m_TeeHistorian.BeginPlayers(); |
1001 | } |
1002 | |
1003 | // copy tuning |
1004 | m_World.m_Core.m_aTuning[0] = m_Tuning; |
1005 | m_World.Tick(); |
1006 | |
1007 | UpdatePlayerMaps(); |
1008 | |
1009 | //if(world.paused) // make sure that the game object always updates |
1010 | m_pController->Tick(); |
1011 | |
1012 | for(int i = 0; i < MAX_CLIENTS; i++) |
1013 | { |
1014 | if(m_apPlayers[i]) |
1015 | { |
1016 | // send vote options |
1017 | ProgressVoteOptions(ClientId: i); |
1018 | |
1019 | m_apPlayers[i]->Tick(); |
1020 | m_apPlayers[i]->PostTick(); |
1021 | } |
1022 | } |
1023 | |
1024 | for(auto &pPlayer : m_apPlayers) |
1025 | { |
1026 | if(pPlayer) |
1027 | pPlayer->PostPostTick(); |
1028 | } |
1029 | |
1030 | // update voting |
1031 | if(m_VoteCloseTime) |
1032 | { |
1033 | // abort the kick-vote on player-leave |
1034 | if(m_VoteEnforce == VOTE_ENFORCE_ABORT) |
1035 | { |
1036 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote aborted" ); |
1037 | EndVote(); |
1038 | } |
1039 | else if(m_VoteEnforce == VOTE_ENFORCE_CANCEL) |
1040 | { |
1041 | char aBuf[64]; |
1042 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' canceled their vote" , Server()->ClientName(ClientId: m_VoteCreator)); |
1043 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf); |
1044 | EndVote(); |
1045 | } |
1046 | else |
1047 | { |
1048 | int Total = 0, Yes = 0, No = 0; |
1049 | bool Veto = false, VetoStop = false; |
1050 | if(m_VoteUpdate) |
1051 | { |
1052 | // count votes |
1053 | char aaBuf[MAX_CLIENTS][NETADDR_MAXSTRSIZE] = {{0}}, *pIp = NULL; |
1054 | bool SinglePlayer = true; |
1055 | for(int i = 0; i < MAX_CLIENTS; i++) |
1056 | { |
1057 | if(m_apPlayers[i]) |
1058 | { |
1059 | Server()->GetClientAddr(ClientId: i, pAddrStr: aaBuf[i], Size: NETADDR_MAXSTRSIZE); |
1060 | if(!pIp) |
1061 | pIp = aaBuf[i]; |
1062 | else if(SinglePlayer && str_comp(a: pIp, b: aaBuf[i])) |
1063 | SinglePlayer = false; |
1064 | } |
1065 | } |
1066 | |
1067 | // remember checked players, only the first player with a specific ip will be handled |
1068 | bool aVoteChecked[MAX_CLIENTS] = {false}; |
1069 | int64_t Now = Server()->Tick(); |
1070 | for(int i = 0; i < MAX_CLIENTS; i++) |
1071 | { |
1072 | if(!m_apPlayers[i] || aVoteChecked[i]) |
1073 | continue; |
1074 | |
1075 | if((IsKickVote() || IsSpecVote()) && (m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS || |
1076 | (GetPlayerChar(ClientId: m_VoteCreator) && GetPlayerChar(ClientId: i) && |
1077 | GetPlayerChar(ClientId: m_VoteCreator)->Team() != GetPlayerChar(ClientId: i)->Team()))) |
1078 | continue; |
1079 | |
1080 | if(m_apPlayers[i]->IsAfk() && i != m_VoteCreator) |
1081 | continue; |
1082 | |
1083 | // can't vote in kick and spec votes in the beginning after joining |
1084 | if((IsKickVote() || IsSpecVote()) && Now < m_apPlayers[i]->m_FirstVoteTick) |
1085 | continue; |
1086 | |
1087 | // connecting clients with spoofed ips can clog slots without being ingame |
1088 | if(!Server()->ClientIngame(ClientId: i)) |
1089 | continue; |
1090 | |
1091 | // don't count votes by blacklisted clients |
1092 | if(g_Config.m_SvDnsblVote && !m_pServer->DnsblWhite(ClientId: i) && !SinglePlayer) |
1093 | continue; |
1094 | |
1095 | int CurVote = m_apPlayers[i]->m_Vote; |
1096 | int CurVotePos = m_apPlayers[i]->m_VotePos; |
1097 | |
1098 | // only allow IPs to vote once, but keep veto ability |
1099 | // check for more players with the same ip (only use the vote of the one who voted first) |
1100 | for(int j = i + 1; j < MAX_CLIENTS; j++) |
1101 | { |
1102 | if(!m_apPlayers[j] || aVoteChecked[j] || str_comp(a: aaBuf[j], b: aaBuf[i]) != 0) |
1103 | continue; |
1104 | |
1105 | // count the latest vote by this ip |
1106 | if(CurVotePos < m_apPlayers[j]->m_VotePos) |
1107 | { |
1108 | CurVote = m_apPlayers[j]->m_Vote; |
1109 | CurVotePos = m_apPlayers[j]->m_VotePos; |
1110 | } |
1111 | |
1112 | aVoteChecked[j] = true; |
1113 | } |
1114 | |
1115 | Total++; |
1116 | if(CurVote > 0) |
1117 | Yes++; |
1118 | else if(CurVote < 0) |
1119 | No++; |
1120 | |
1121 | // veto right for players who have been active on server for long and who're not afk |
1122 | if(!IsKickVote() && !IsSpecVote() && g_Config.m_SvVoteVetoTime) |
1123 | { |
1124 | // look through all players with same IP again, including the current player |
1125 | for(int j = i; j < MAX_CLIENTS; j++) |
1126 | { |
1127 | // no need to check ip address of current player |
1128 | if(i != j && (!m_apPlayers[j] || str_comp(a: aaBuf[j], b: aaBuf[i]) != 0)) |
1129 | continue; |
1130 | |
1131 | if(m_apPlayers[j] && !m_apPlayers[j]->IsAfk() && m_apPlayers[j]->GetTeam() != TEAM_SPECTATORS && |
1132 | ((Server()->Tick() - m_apPlayers[j]->m_JoinTick) / (Server()->TickSpeed() * 60) > g_Config.m_SvVoteVetoTime || |
1133 | (m_apPlayers[j]->GetCharacter() && m_apPlayers[j]->GetCharacter()->m_DDRaceState == DDRACE_STARTED && |
1134 | (Server()->Tick() - m_apPlayers[j]->GetCharacter()->m_StartTime) / (Server()->TickSpeed() * 60) > g_Config.m_SvVoteVetoTime))) |
1135 | { |
1136 | if(CurVote == 0) |
1137 | Veto = true; |
1138 | else if(CurVote < 0) |
1139 | VetoStop = true; |
1140 | break; |
1141 | } |
1142 | } |
1143 | } |
1144 | } |
1145 | |
1146 | if(g_Config.m_SvVoteMaxTotal && Total > g_Config.m_SvVoteMaxTotal && |
1147 | (IsKickVote() || IsSpecVote())) |
1148 | Total = g_Config.m_SvVoteMaxTotal; |
1149 | |
1150 | if((Yes > Total / (100.0f / g_Config.m_SvVoteYesPercentage)) && !Veto) |
1151 | m_VoteEnforce = VOTE_ENFORCE_YES; |
1152 | else if(No >= Total - Total / (100.0f / g_Config.m_SvVoteYesPercentage)) |
1153 | m_VoteEnforce = VOTE_ENFORCE_NO; |
1154 | |
1155 | if(VetoStop) |
1156 | m_VoteEnforce = VOTE_ENFORCE_NO; |
1157 | |
1158 | m_VoteWillPass = Yes > (Yes + No) / (100.0f / g_Config.m_SvVoteYesPercentage); |
1159 | } |
1160 | |
1161 | if(time_get() > m_VoteCloseTime && !g_Config.m_SvVoteMajority) |
1162 | m_VoteEnforce = (m_VoteWillPass && !Veto) ? VOTE_ENFORCE_YES : VOTE_ENFORCE_NO; |
1163 | |
1164 | // / Ensure minimum time for vote to end when moderating. |
1165 | if(m_VoteEnforce == VOTE_ENFORCE_YES && !(PlayerModerating() && |
1166 | (IsKickVote() || IsSpecVote()) && time_get() < m_VoteCloseTime)) |
1167 | { |
1168 | Server()->SetRconCid(IServer::RCON_CID_VOTE); |
1169 | Console()->ExecuteLine(pStr: m_aVoteCommand); |
1170 | Server()->SetRconCid(IServer::RCON_CID_SERV); |
1171 | EndVote(); |
1172 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote passed" , SpamProtectionClientId: -1, Flags: CHAT_SIX); |
1173 | |
1174 | if(m_apPlayers[m_VoteCreator] && !IsKickVote() && !IsSpecVote()) |
1175 | m_apPlayers[m_VoteCreator]->m_LastVoteCall = 0; |
1176 | } |
1177 | else if(m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN) |
1178 | { |
1179 | Console()->ExecuteLine(pStr: m_aVoteCommand, ClientId: m_VoteEnforcer); |
1180 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote passed enforced by authorized player" , SpamProtectionClientId: -1, Flags: CHAT_SIX); |
1181 | EndVote(); |
1182 | } |
1183 | else if(m_VoteEnforce == VOTE_ENFORCE_NO_ADMIN) |
1184 | { |
1185 | EndVote(); |
1186 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote failed enforced by authorized player" , SpamProtectionClientId: -1, Flags: CHAT_SIX); |
1187 | } |
1188 | //else if(m_VoteEnforce == VOTE_ENFORCE_NO || time_get() > m_VoteCloseTime) |
1189 | else if(m_VoteEnforce == VOTE_ENFORCE_NO || (time_get() > m_VoteCloseTime && g_Config.m_SvVoteMajority)) |
1190 | { |
1191 | EndVote(); |
1192 | if(VetoStop || (m_VoteWillPass && Veto)) |
1193 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote failed because of veto. Find an empty server instead" , SpamProtectionClientId: -1, Flags: CHAT_SIX); |
1194 | else |
1195 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: "Vote failed" , SpamProtectionClientId: -1, Flags: CHAT_SIX); |
1196 | } |
1197 | else if(m_VoteUpdate) |
1198 | { |
1199 | m_VoteUpdate = false; |
1200 | SendVoteStatus(ClientId: -1, Total, Yes, No); |
1201 | } |
1202 | } |
1203 | } |
1204 | for(int i = 0; i < m_NumMutes; i++) |
1205 | { |
1206 | if(m_aMutes[i].m_Expire <= Server()->Tick()) |
1207 | { |
1208 | m_NumMutes--; |
1209 | m_aMutes[i] = m_aMutes[m_NumMutes]; |
1210 | } |
1211 | } |
1212 | for(int i = 0; i < m_NumVoteMutes; i++) |
1213 | { |
1214 | if(m_aVoteMutes[i].m_Expire <= Server()->Tick()) |
1215 | { |
1216 | m_NumVoteMutes--; |
1217 | m_aVoteMutes[i] = m_aVoteMutes[m_NumVoteMutes]; |
1218 | } |
1219 | } |
1220 | |
1221 | if(Server()->Tick() % (g_Config.m_SvAnnouncementInterval * Server()->TickSpeed() * 60) == 0) |
1222 | { |
1223 | const char *pLine = Server()->GetAnnouncementLine(pFileName: g_Config.m_SvAnnouncementFileName); |
1224 | if(pLine) |
1225 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: pLine); |
1226 | } |
1227 | |
1228 | for(auto &Switcher : Switchers()) |
1229 | { |
1230 | for(int j = 0; j < MAX_CLIENTS; ++j) |
1231 | { |
1232 | if(Switcher.m_aEndTick[j] <= Server()->Tick() && Switcher.m_aType[j] == TILE_SWITCHTIMEDOPEN) |
1233 | { |
1234 | Switcher.m_aStatus[j] = false; |
1235 | Switcher.m_aEndTick[j] = 0; |
1236 | Switcher.m_aType[j] = TILE_SWITCHCLOSE; |
1237 | } |
1238 | else if(Switcher.m_aEndTick[j] <= Server()->Tick() && Switcher.m_aType[j] == TILE_SWITCHTIMEDCLOSE) |
1239 | { |
1240 | Switcher.m_aStatus[j] = true; |
1241 | Switcher.m_aEndTick[j] = 0; |
1242 | Switcher.m_aType[j] = TILE_SWITCHOPEN; |
1243 | } |
1244 | } |
1245 | } |
1246 | |
1247 | if(m_SqlRandomMapResult != nullptr && m_SqlRandomMapResult->m_Completed) |
1248 | { |
1249 | if(m_SqlRandomMapResult->m_Success) |
1250 | { |
1251 | if(PlayerExists(ClientId: m_SqlRandomMapResult->m_ClientId) && m_SqlRandomMapResult->m_aMessage[0] != '\0') |
1252 | SendChatTarget(To: m_SqlRandomMapResult->m_ClientId, pText: m_SqlRandomMapResult->m_aMessage); |
1253 | if(m_SqlRandomMapResult->m_aMap[0] != '\0') |
1254 | Server()->ChangeMap(pMap: m_SqlRandomMapResult->m_aMap); |
1255 | else |
1256 | m_LastMapVote = 0; |
1257 | } |
1258 | m_SqlRandomMapResult = nullptr; |
1259 | } |
1260 | |
1261 | // Record player position at the end of the tick |
1262 | if(m_TeeHistorianActive) |
1263 | { |
1264 | for(int i = 0; i < MAX_CLIENTS; i++) |
1265 | { |
1266 | if(m_apPlayers[i] && m_apPlayers[i]->GetCharacter()) |
1267 | { |
1268 | CNetObj_CharacterCore Char; |
1269 | m_apPlayers[i]->GetCharacter()->GetCore().Write(pObjCore: &Char); |
1270 | m_TeeHistorian.RecordPlayer(ClientId: i, pChar: &Char); |
1271 | } |
1272 | else |
1273 | { |
1274 | m_TeeHistorian.RecordDeadPlayer(ClientId: i); |
1275 | } |
1276 | } |
1277 | m_TeeHistorian.EndPlayers(); |
1278 | m_TeeHistorian.BeginInputs(); |
1279 | } |
1280 | // Warning: do not put code in this function directly above or below this comment |
1281 | } |
1282 | |
1283 | static int PlayerFlags_SevenToSix(int Flags) |
1284 | { |
1285 | int Six = 0; |
1286 | if(Flags & protocol7::PLAYERFLAG_CHATTING) |
1287 | Six |= PLAYERFLAG_CHATTING; |
1288 | if(Flags & protocol7::PLAYERFLAG_SCOREBOARD) |
1289 | Six |= PLAYERFLAG_SCOREBOARD; |
1290 | if(Flags & protocol7::PLAYERFLAG_AIM) |
1291 | Six |= PLAYERFLAG_AIM; |
1292 | return Six; |
1293 | } |
1294 | |
1295 | // Server hooks |
1296 | void CGameContext::OnClientPrepareInput(int ClientId, void *pInput) |
1297 | { |
1298 | auto *pPlayerInput = (CNetObj_PlayerInput *)pInput; |
1299 | if(Server()->IsSixup(ClientId)) |
1300 | pPlayerInput->m_PlayerFlags = PlayerFlags_SevenToSix(Flags: pPlayerInput->m_PlayerFlags); |
1301 | } |
1302 | |
1303 | void CGameContext::OnClientDirectInput(int ClientId, void *pInput) |
1304 | { |
1305 | if(!m_World.m_Paused) |
1306 | m_apPlayers[ClientId]->OnDirectInput(pNewInput: (CNetObj_PlayerInput *)pInput); |
1307 | |
1308 | int Flags = ((CNetObj_PlayerInput *)pInput)->m_PlayerFlags; |
1309 | if((Flags & 256) || (Flags & 512)) |
1310 | { |
1311 | Server()->Kick(ClientId, pReason: "please update your client or use DDNet client" ); |
1312 | } |
1313 | } |
1314 | |
1315 | void CGameContext::OnClientPredictedInput(int ClientId, void *pInput) |
1316 | { |
1317 | // early return if no input at all has been sent by a player |
1318 | if(pInput == nullptr && !m_aPlayerHasInput[ClientId]) |
1319 | return; |
1320 | |
1321 | // set to last sent input when no new input has been sent |
1322 | CNetObj_PlayerInput *pApplyInput = (CNetObj_PlayerInput *)pInput; |
1323 | if(pApplyInput == nullptr) |
1324 | { |
1325 | pApplyInput = &m_aLastPlayerInput[ClientId]; |
1326 | } |
1327 | |
1328 | if(!m_World.m_Paused) |
1329 | m_apPlayers[ClientId]->OnPredictedInput(pNewInput: pApplyInput); |
1330 | } |
1331 | |
1332 | void CGameContext::OnClientPredictedEarlyInput(int ClientId, void *pInput) |
1333 | { |
1334 | // early return if no input at all has been sent by a player |
1335 | if(pInput == nullptr && !m_aPlayerHasInput[ClientId]) |
1336 | return; |
1337 | |
1338 | // set to last sent input when no new input has been sent |
1339 | CNetObj_PlayerInput *pApplyInput = (CNetObj_PlayerInput *)pInput; |
1340 | if(pApplyInput == nullptr) |
1341 | { |
1342 | pApplyInput = &m_aLastPlayerInput[ClientId]; |
1343 | } |
1344 | else |
1345 | { |
1346 | // Store input in this function and not in `OnClientPredictedInput`, |
1347 | // because this function is called on all inputs, while |
1348 | // `OnClientPredictedInput` is only called on the first input of each |
1349 | // tick. |
1350 | mem_copy(dest: &m_aLastPlayerInput[ClientId], source: pApplyInput, size: sizeof(m_aLastPlayerInput[ClientId])); |
1351 | m_aPlayerHasInput[ClientId] = true; |
1352 | } |
1353 | |
1354 | if(!m_World.m_Paused) |
1355 | m_apPlayers[ClientId]->OnPredictedEarlyInput(pNewInput: pApplyInput); |
1356 | |
1357 | if(m_TeeHistorianActive) |
1358 | { |
1359 | m_TeeHistorian.RecordPlayerInput(ClientId, UniqueClientId: m_apPlayers[ClientId]->GetUniqueCid(), pInput: pApplyInput); |
1360 | } |
1361 | } |
1362 | |
1363 | const CVoteOptionServer *CGameContext::GetVoteOption(int Index) const |
1364 | { |
1365 | const CVoteOptionServer *pCurrent; |
1366 | for(pCurrent = m_pVoteOptionFirst; |
1367 | Index > 0 && pCurrent; |
1368 | Index--, pCurrent = pCurrent->m_pNext) |
1369 | ; |
1370 | |
1371 | if(Index > 0) |
1372 | return 0; |
1373 | return pCurrent; |
1374 | } |
1375 | |
1376 | void CGameContext::ProgressVoteOptions(int ClientId) |
1377 | { |
1378 | CPlayer *pPl = m_apPlayers[ClientId]; |
1379 | |
1380 | if(pPl->m_SendVoteIndex == -1) |
1381 | return; // we didn't start sending options yet |
1382 | |
1383 | if(pPl->m_SendVoteIndex > m_NumVoteOptions) |
1384 | return; // shouldn't happen / fail silently |
1385 | |
1386 | int VotesLeft = m_NumVoteOptions - pPl->m_SendVoteIndex; |
1387 | int NumVotesToSend = minimum(a: g_Config.m_SvSendVotesPerTick, b: VotesLeft); |
1388 | |
1389 | if(!VotesLeft) |
1390 | { |
1391 | // player has up to date vote option list |
1392 | return; |
1393 | } |
1394 | |
1395 | // build vote option list msg |
1396 | int CurIndex = 0; |
1397 | |
1398 | CNetMsg_Sv_VoteOptionListAdd OptionMsg; |
1399 | OptionMsg.m_pDescription0 = "" ; |
1400 | OptionMsg.m_pDescription1 = "" ; |
1401 | OptionMsg.m_pDescription2 = "" ; |
1402 | OptionMsg.m_pDescription3 = "" ; |
1403 | OptionMsg.m_pDescription4 = "" ; |
1404 | OptionMsg.m_pDescription5 = "" ; |
1405 | OptionMsg.m_pDescription6 = "" ; |
1406 | OptionMsg.m_pDescription7 = "" ; |
1407 | OptionMsg.m_pDescription8 = "" ; |
1408 | OptionMsg.m_pDescription9 = "" ; |
1409 | OptionMsg.m_pDescription10 = "" ; |
1410 | OptionMsg.m_pDescription11 = "" ; |
1411 | OptionMsg.m_pDescription12 = "" ; |
1412 | OptionMsg.m_pDescription13 = "" ; |
1413 | OptionMsg.m_pDescription14 = "" ; |
1414 | |
1415 | // get current vote option by index |
1416 | const CVoteOptionServer *pCurrent = GetVoteOption(Index: pPl->m_SendVoteIndex); |
1417 | |
1418 | while(CurIndex < NumVotesToSend && pCurrent != NULL) |
1419 | { |
1420 | switch(CurIndex) |
1421 | { |
1422 | case 0: OptionMsg.m_pDescription0 = pCurrent->m_aDescription; break; |
1423 | case 1: OptionMsg.m_pDescription1 = pCurrent->m_aDescription; break; |
1424 | case 2: OptionMsg.m_pDescription2 = pCurrent->m_aDescription; break; |
1425 | case 3: OptionMsg.m_pDescription3 = pCurrent->m_aDescription; break; |
1426 | case 4: OptionMsg.m_pDescription4 = pCurrent->m_aDescription; break; |
1427 | case 5: OptionMsg.m_pDescription5 = pCurrent->m_aDescription; break; |
1428 | case 6: OptionMsg.m_pDescription6 = pCurrent->m_aDescription; break; |
1429 | case 7: OptionMsg.m_pDescription7 = pCurrent->m_aDescription; break; |
1430 | case 8: OptionMsg.m_pDescription8 = pCurrent->m_aDescription; break; |
1431 | case 9: OptionMsg.m_pDescription9 = pCurrent->m_aDescription; break; |
1432 | case 10: OptionMsg.m_pDescription10 = pCurrent->m_aDescription; break; |
1433 | case 11: OptionMsg.m_pDescription11 = pCurrent->m_aDescription; break; |
1434 | case 12: OptionMsg.m_pDescription12 = pCurrent->m_aDescription; break; |
1435 | case 13: OptionMsg.m_pDescription13 = pCurrent->m_aDescription; break; |
1436 | case 14: OptionMsg.m_pDescription14 = pCurrent->m_aDescription; break; |
1437 | } |
1438 | |
1439 | CurIndex++; |
1440 | pCurrent = pCurrent->m_pNext; |
1441 | } |
1442 | |
1443 | // send msg |
1444 | if(pPl->m_SendVoteIndex == 0) |
1445 | { |
1446 | CNetMsg_Sv_VoteOptionGroupStart StartMsg; |
1447 | Server()->SendPackMsg(pMsg: &StartMsg, Flags: MSGFLAG_VITAL, ClientId); |
1448 | } |
1449 | |
1450 | OptionMsg.m_NumOptions = NumVotesToSend; |
1451 | Server()->SendPackMsg(pMsg: &OptionMsg, Flags: MSGFLAG_VITAL, ClientId); |
1452 | |
1453 | pPl->m_SendVoteIndex += NumVotesToSend; |
1454 | |
1455 | if(pPl->m_SendVoteIndex == m_NumVoteOptions) |
1456 | { |
1457 | CNetMsg_Sv_VoteOptionGroupEnd EndMsg; |
1458 | Server()->SendPackMsg(pMsg: &EndMsg, Flags: MSGFLAG_VITAL, ClientId); |
1459 | } |
1460 | } |
1461 | |
1462 | void CGameContext::OnClientEnter(int ClientId) |
1463 | { |
1464 | if(m_TeeHistorianActive) |
1465 | { |
1466 | m_TeeHistorian.RecordPlayerReady(ClientId); |
1467 | } |
1468 | m_pController->OnPlayerConnect(pPlayer: m_apPlayers[ClientId]); |
1469 | |
1470 | if(Server()->IsSixup(ClientId)) |
1471 | { |
1472 | { |
1473 | protocol7::CNetMsg_Sv_GameInfo Msg; |
1474 | Msg.m_GameFlags = protocol7::GAMEFLAG_RACE; |
1475 | Msg.m_MatchCurrent = 1; |
1476 | Msg.m_MatchNum = 0; |
1477 | Msg.m_ScoreLimit = 0; |
1478 | Msg.m_TimeLimit = 0; |
1479 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1480 | } |
1481 | |
1482 | // /team is essential |
1483 | { |
1484 | protocol7::CNetMsg_Sv_CommandInfoRemove Msg; |
1485 | Msg.m_pName = "team" ; |
1486 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1487 | } |
1488 | } |
1489 | |
1490 | { |
1491 | CNetMsg_Sv_CommandInfoGroupStart Msg; |
1492 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1493 | } |
1494 | for(const IConsole::CCommandInfo *pCmd = Console()->FirstCommandInfo(AccessLevel: IConsole::ACCESS_LEVEL_USER, Flagmask: CFGFLAG_CHAT); |
1495 | pCmd; pCmd = pCmd->NextCommandInfo(AccessLevel: IConsole::ACCESS_LEVEL_USER, FlagMask: CFGFLAG_CHAT)) |
1496 | { |
1497 | const char *pName = pCmd->m_pName; |
1498 | |
1499 | if(Server()->IsSixup(ClientId)) |
1500 | { |
1501 | if(!str_comp_nocase(a: pName, b: "w" ) || !str_comp_nocase(a: pName, b: "whisper" )) |
1502 | continue; |
1503 | |
1504 | if(!str_comp_nocase(a: pName, b: "r" )) |
1505 | pName = "rescue" ; |
1506 | |
1507 | protocol7::CNetMsg_Sv_CommandInfo Msg; |
1508 | Msg.m_pName = pName; |
1509 | Msg.m_pArgsFormat = pCmd->m_pParams; |
1510 | Msg.m_pHelpText = pCmd->m_pHelp; |
1511 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1512 | } |
1513 | else |
1514 | { |
1515 | CNetMsg_Sv_CommandInfo Msg; |
1516 | Msg.m_pName = pName; |
1517 | Msg.m_pArgsFormat = pCmd->m_pParams; |
1518 | Msg.m_pHelpText = pCmd->m_pHelp; |
1519 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1520 | } |
1521 | } |
1522 | { |
1523 | CNetMsg_Sv_CommandInfoGroupEnd Msg; |
1524 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1525 | } |
1526 | |
1527 | { |
1528 | int Empty = -1; |
1529 | for(int i = 0; i < MAX_CLIENTS; i++) |
1530 | { |
1531 | if(Server()->ClientSlotEmpty(ClientId: i)) |
1532 | { |
1533 | Empty = i; |
1534 | break; |
1535 | } |
1536 | } |
1537 | CNetMsg_Sv_Chat Msg; |
1538 | Msg.m_Team = 0; |
1539 | Msg.m_ClientId = Empty; |
1540 | Msg.m_pMessage = "Do you know someone who uses a bot? Please report them to the moderators." ; |
1541 | m_apPlayers[ClientId]->m_EligibleForFinishCheck = time_get(); |
1542 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1543 | } |
1544 | |
1545 | IServer::CClientInfo Info; |
1546 | if(Server()->GetClientInfo(ClientId, pInfo: &Info) && Info.m_GotDDNetVersion) |
1547 | { |
1548 | if(OnClientDDNetVersionKnown(ClientId)) |
1549 | return; // kicked |
1550 | } |
1551 | |
1552 | if(!Server()->ClientPrevIngame(ClientId)) |
1553 | { |
1554 | if(g_Config.m_SvWelcome[0] != 0) |
1555 | SendChatTarget(To: ClientId, pText: g_Config.m_SvWelcome); |
1556 | |
1557 | if(g_Config.m_SvShowOthersDefault > SHOW_OTHERS_OFF) |
1558 | { |
1559 | if(g_Config.m_SvShowOthers) |
1560 | SendChatTarget(To: ClientId, pText: "You can see other players. To disable this use DDNet client and type /showothers" ); |
1561 | |
1562 | m_apPlayers[ClientId]->m_ShowOthers = g_Config.m_SvShowOthersDefault; |
1563 | } |
1564 | } |
1565 | m_VoteUpdate = true; |
1566 | |
1567 | // send active vote |
1568 | if(m_VoteCloseTime) |
1569 | SendVoteSet(ClientId); |
1570 | |
1571 | Server()->ExpireServerInfo(); |
1572 | |
1573 | CPlayer *pNewPlayer = m_apPlayers[ClientId]; |
1574 | mem_zero(block: &m_aLastPlayerInput[ClientId], size: sizeof(m_aLastPlayerInput[ClientId])); |
1575 | m_aPlayerHasInput[ClientId] = false; |
1576 | |
1577 | // new info for others |
1578 | protocol7::CNetMsg_Sv_ClientInfo NewClientInfoMsg; |
1579 | NewClientInfoMsg.m_ClientId = ClientId; |
1580 | NewClientInfoMsg.m_Local = 0; |
1581 | NewClientInfoMsg.m_Team = pNewPlayer->GetTeam(); |
1582 | NewClientInfoMsg.m_pName = Server()->ClientName(ClientId); |
1583 | NewClientInfoMsg.m_pClan = Server()->ClientClan(ClientId); |
1584 | NewClientInfoMsg.m_Country = Server()->ClientCountry(ClientId); |
1585 | NewClientInfoMsg.m_Silent = false; |
1586 | |
1587 | for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) |
1588 | { |
1589 | NewClientInfoMsg.m_apSkinPartNames[p] = pNewPlayer->m_TeeInfos.m_apSkinPartNames[p]; |
1590 | NewClientInfoMsg.m_aUseCustomColors[p] = pNewPlayer->m_TeeInfos.m_aUseCustomColors[p]; |
1591 | NewClientInfoMsg.m_aSkinPartColors[p] = pNewPlayer->m_TeeInfos.m_aSkinPartColors[p]; |
1592 | } |
1593 | |
1594 | // update client infos (others before local) |
1595 | for(int i = 0; i < Server()->MaxClients(); ++i) |
1596 | { |
1597 | if(i == ClientId || !m_apPlayers[i] || !Server()->ClientIngame(ClientId: i)) |
1598 | continue; |
1599 | |
1600 | CPlayer *pPlayer = m_apPlayers[i]; |
1601 | |
1602 | if(Server()->IsSixup(ClientId: i)) |
1603 | Server()->SendPackMsg(pMsg: &NewClientInfoMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
1604 | |
1605 | if(Server()->IsSixup(ClientId)) |
1606 | { |
1607 | // existing infos for new player |
1608 | protocol7::CNetMsg_Sv_ClientInfo ClientInfoMsg; |
1609 | ClientInfoMsg.m_ClientId = i; |
1610 | ClientInfoMsg.m_Local = 0; |
1611 | ClientInfoMsg.m_Team = pPlayer->GetTeam(); |
1612 | ClientInfoMsg.m_pName = Server()->ClientName(ClientId: i); |
1613 | ClientInfoMsg.m_pClan = Server()->ClientClan(ClientId: i); |
1614 | ClientInfoMsg.m_Country = Server()->ClientCountry(ClientId: i); |
1615 | ClientInfoMsg.m_Silent = 0; |
1616 | |
1617 | for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) |
1618 | { |
1619 | ClientInfoMsg.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; |
1620 | ClientInfoMsg.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; |
1621 | ClientInfoMsg.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; |
1622 | } |
1623 | |
1624 | Server()->SendPackMsg(pMsg: &ClientInfoMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1625 | } |
1626 | } |
1627 | |
1628 | // local info |
1629 | if(Server()->IsSixup(ClientId)) |
1630 | { |
1631 | NewClientInfoMsg.m_Local = 1; |
1632 | Server()->SendPackMsg(pMsg: &NewClientInfoMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
1633 | } |
1634 | |
1635 | // initial chat delay |
1636 | if(g_Config.m_SvChatInitialDelay != 0 && m_apPlayers[ClientId]->m_JoinTick > m_NonEmptySince + 10 * Server()->TickSpeed()) |
1637 | { |
1638 | char aBuf[128]; |
1639 | NETADDR Addr; |
1640 | Server()->GetClientAddr(ClientId, pAddr: &Addr); |
1641 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "This server has an initial chat delay, you will need to wait %d seconds before talking." , g_Config.m_SvChatInitialDelay); |
1642 | SendChatTarget(To: ClientId, pText: aBuf); |
1643 | Mute(pAddr: &Addr, Secs: g_Config.m_SvChatInitialDelay, pDisplayName: Server()->ClientName(ClientId), pReason: "Initial chat delay" , InitialChatDelay: true); |
1644 | } |
1645 | |
1646 | LogEvent(Description: "Connect" , ClientId); |
1647 | } |
1648 | |
1649 | bool CGameContext::OnClientDataPersist(int ClientId, void *pData) |
1650 | { |
1651 | CPersistentClientData *pPersistent = (CPersistentClientData *)pData; |
1652 | if(!m_apPlayers[ClientId]) |
1653 | { |
1654 | return false; |
1655 | } |
1656 | pPersistent->m_IsSpectator = m_apPlayers[ClientId]->GetTeam() == TEAM_SPECTATORS; |
1657 | pPersistent->m_IsAfk = m_apPlayers[ClientId]->IsAfk(); |
1658 | return true; |
1659 | } |
1660 | |
1661 | void CGameContext::OnClientConnected(int ClientId, void *pData) |
1662 | { |
1663 | CPersistentClientData *pPersistentData = (CPersistentClientData *)pData; |
1664 | bool Spec = false; |
1665 | bool Afk = true; |
1666 | if(pPersistentData) |
1667 | { |
1668 | Spec = pPersistentData->m_IsSpectator; |
1669 | Afk = pPersistentData->m_IsAfk; |
1670 | } |
1671 | |
1672 | { |
1673 | bool Empty = true; |
1674 | for(auto &pPlayer : m_apPlayers) |
1675 | { |
1676 | // connecting clients with spoofed ips can clog slots without being ingame |
1677 | if(pPlayer && Server()->ClientIngame(ClientId: pPlayer->GetCid())) |
1678 | { |
1679 | Empty = false; |
1680 | break; |
1681 | } |
1682 | } |
1683 | if(Empty) |
1684 | { |
1685 | m_NonEmptySince = Server()->Tick(); |
1686 | } |
1687 | } |
1688 | |
1689 | // Check which team the player should be on |
1690 | const int StartTeam = (Spec || g_Config.m_SvTournamentMode) ? TEAM_SPECTATORS : m_pController->GetAutoTeam(NotThisId: ClientId); |
1691 | |
1692 | if(m_apPlayers[ClientId]) |
1693 | delete m_apPlayers[ClientId]; |
1694 | m_apPlayers[ClientId] = new(ClientId) CPlayer(this, NextUniqueClientId, ClientId, StartTeam); |
1695 | m_apPlayers[ClientId]->SetInitialAfk(Afk); |
1696 | NextUniqueClientId += 1; |
1697 | |
1698 | SendMotd(ClientId); |
1699 | SendSettings(ClientId); |
1700 | |
1701 | Server()->ExpireServerInfo(); |
1702 | } |
1703 | |
1704 | void CGameContext::OnClientDrop(int ClientId, const char *pReason) |
1705 | { |
1706 | LogEvent(Description: "Disconnect" , ClientId); |
1707 | |
1708 | AbortVoteKickOnDisconnect(ClientId); |
1709 | m_pController->OnPlayerDisconnect(pPlayer: m_apPlayers[ClientId], pReason); |
1710 | delete m_apPlayers[ClientId]; |
1711 | m_apPlayers[ClientId] = 0; |
1712 | |
1713 | m_VoteUpdate = true; |
1714 | |
1715 | // update spectator modes |
1716 | for(auto &pPlayer : m_apPlayers) |
1717 | { |
1718 | if(pPlayer && pPlayer->m_SpectatorId == ClientId) |
1719 | pPlayer->m_SpectatorId = SPEC_FREEVIEW; |
1720 | } |
1721 | |
1722 | // update conversation targets |
1723 | for(auto &pPlayer : m_apPlayers) |
1724 | { |
1725 | if(pPlayer && pPlayer->m_LastWhisperTo == ClientId) |
1726 | pPlayer->m_LastWhisperTo = -1; |
1727 | } |
1728 | |
1729 | protocol7::CNetMsg_Sv_ClientDrop Msg; |
1730 | Msg.m_ClientId = ClientId; |
1731 | Msg.m_pReason = pReason; |
1732 | Msg.m_Silent = false; |
1733 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: -1); |
1734 | |
1735 | Server()->ExpireServerInfo(); |
1736 | } |
1737 | |
1738 | void CGameContext::TeehistorianRecordAntibot(const void *pData, int DataSize) |
1739 | { |
1740 | if(m_TeeHistorianActive) |
1741 | { |
1742 | m_TeeHistorian.RecordAntibot(pData, DataSize); |
1743 | } |
1744 | } |
1745 | |
1746 | void CGameContext::TeehistorianRecordPlayerJoin(int ClientId, bool Sixup) |
1747 | { |
1748 | if(m_TeeHistorianActive) |
1749 | { |
1750 | m_TeeHistorian.RecordPlayerJoin(ClientId, Protocol: !Sixup ? CTeeHistorian::PROTOCOL_6 : CTeeHistorian::PROTOCOL_7); |
1751 | } |
1752 | } |
1753 | |
1754 | void CGameContext::TeehistorianRecordPlayerDrop(int ClientId, const char *pReason) |
1755 | { |
1756 | if(m_TeeHistorianActive) |
1757 | { |
1758 | m_TeeHistorian.RecordPlayerDrop(ClientId, pReason); |
1759 | } |
1760 | } |
1761 | |
1762 | void CGameContext::TeehistorianRecordPlayerRejoin(int ClientId) |
1763 | { |
1764 | if(m_TeeHistorianActive) |
1765 | { |
1766 | m_TeeHistorian.RecordPlayerRejoin(ClientId); |
1767 | } |
1768 | } |
1769 | |
1770 | bool CGameContext::OnClientDDNetVersionKnown(int ClientId) |
1771 | { |
1772 | IServer::CClientInfo Info; |
1773 | dbg_assert(Server()->GetClientInfo(ClientId, &Info), "failed to get client info" ); |
1774 | int ClientVersion = Info.m_DDNetVersion; |
1775 | dbg_msg(sys: "ddnet" , fmt: "cid=%d version=%d" , ClientId, ClientVersion); |
1776 | |
1777 | if(m_TeeHistorianActive) |
1778 | { |
1779 | if(Info.m_pConnectionId && Info.m_pDDNetVersionStr) |
1780 | { |
1781 | m_TeeHistorian.RecordDDNetVersion(ClientId, ConnectionId: *Info.m_pConnectionId, DDNetVersion: ClientVersion, pDDNetVersionStr: Info.m_pDDNetVersionStr); |
1782 | } |
1783 | else |
1784 | { |
1785 | m_TeeHistorian.RecordDDNetVersionOld(ClientId, DDNetVersion: ClientVersion); |
1786 | } |
1787 | } |
1788 | |
1789 | // Autoban known bot versions. |
1790 | if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(Version: ClientVersion)) |
1791 | { |
1792 | Server()->Kick(ClientId, pReason: "unsupported client" ); |
1793 | return true; |
1794 | } |
1795 | |
1796 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
1797 | if(ClientVersion >= VERSION_DDNET_GAMETICK) |
1798 | pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType; |
1799 | |
1800 | // First update the teams state. |
1801 | m_pController->Teams().SendTeamsState(ClientId); |
1802 | |
1803 | // Then send records. |
1804 | SendRecord(ClientId); |
1805 | |
1806 | // And report correct tunings. |
1807 | if(ClientVersion < VERSION_DDNET_EARLY_VERSION) |
1808 | SendTuningParams(ClientId, Zone: pPlayer->m_TuneZone); |
1809 | |
1810 | // Tell old clients to update. |
1811 | if(ClientVersion < VERSION_DDNET_UPDATER_FIXED && g_Config.m_SvClientSuggestionOld[0] != '\0') |
1812 | SendBroadcast(pText: g_Config.m_SvClientSuggestionOld, ClientId); |
1813 | // Tell known bot clients that they're botting and we know it. |
1814 | if(((ClientVersion >= 15 && ClientVersion < 100) || ClientVersion == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0') |
1815 | SendBroadcast(pText: g_Config.m_SvClientSuggestionBot, ClientId); |
1816 | |
1817 | return false; |
1818 | } |
1819 | |
1820 | void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientId) |
1821 | { |
1822 | if(Server()->IsSixup(ClientId) && *pMsgId < OFFSET_UUID) |
1823 | { |
1824 | void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(Type: *pMsgId, pUnpacker); |
1825 | if(!pRawMsg) |
1826 | return 0; |
1827 | |
1828 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
1829 | static char s_aRawMsg[1024]; |
1830 | |
1831 | if(*pMsgId == protocol7::NETMSGTYPE_CL_SAY) |
1832 | { |
1833 | protocol7::CNetMsg_Cl_Say *pMsg7 = (protocol7::CNetMsg_Cl_Say *)pRawMsg; |
1834 | // Should probably use a placement new to start the lifetime of the object to avoid future weirdness |
1835 | ::CNetMsg_Cl_Say *pMsg = (::CNetMsg_Cl_Say *)s_aRawMsg; |
1836 | |
1837 | if(pMsg7->m_Target >= 0) |
1838 | { |
1839 | if(ProcessSpamProtection(ClientId)) |
1840 | return 0; |
1841 | |
1842 | // Should we maybe recraft the message so that it can go through the usual path? |
1843 | WhisperId(ClientId, VictimId: pMsg7->m_Target, pMessage: pMsg7->m_pMessage); |
1844 | return 0; |
1845 | } |
1846 | |
1847 | pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM; |
1848 | pMsg->m_pMessage = pMsg7->m_pMessage; |
1849 | } |
1850 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_STARTINFO) |
1851 | { |
1852 | protocol7::CNetMsg_Cl_StartInfo *pMsg7 = (protocol7::CNetMsg_Cl_StartInfo *)pRawMsg; |
1853 | ::CNetMsg_Cl_StartInfo *pMsg = (::CNetMsg_Cl_StartInfo *)s_aRawMsg; |
1854 | |
1855 | pMsg->m_pName = pMsg7->m_pName; |
1856 | pMsg->m_pClan = pMsg7->m_pClan; |
1857 | pMsg->m_Country = pMsg7->m_Country; |
1858 | |
1859 | CTeeInfo Info(pMsg7->m_apSkinPartNames, pMsg7->m_aUseCustomColors, pMsg7->m_aSkinPartColors); |
1860 | Info.FromSixup(); |
1861 | pPlayer->m_TeeInfos = Info; |
1862 | |
1863 | str_copy(dst: s_aRawMsg + sizeof(*pMsg), src: Info.m_aSkinName, dst_size: sizeof(s_aRawMsg) - sizeof(*pMsg)); |
1864 | |
1865 | pMsg->m_pSkin = s_aRawMsg + sizeof(*pMsg); |
1866 | pMsg->m_UseCustomColor = pPlayer->m_TeeInfos.m_UseCustomColor; |
1867 | pMsg->m_ColorBody = pPlayer->m_TeeInfos.m_ColorBody; |
1868 | pMsg->m_ColorFeet = pPlayer->m_TeeInfos.m_ColorFeet; |
1869 | } |
1870 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_SKINCHANGE) |
1871 | { |
1872 | protocol7::CNetMsg_Cl_SkinChange *pMsg = (protocol7::CNetMsg_Cl_SkinChange *)pRawMsg; |
1873 | if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && |
1874 | pPlayer->m_LastChangeInfo + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay > Server()->Tick()) |
1875 | return 0; |
1876 | |
1877 | pPlayer->m_LastChangeInfo = Server()->Tick(); |
1878 | |
1879 | CTeeInfo Info(pMsg->m_apSkinPartNames, pMsg->m_aUseCustomColors, pMsg->m_aSkinPartColors); |
1880 | Info.FromSixup(); |
1881 | pPlayer->m_TeeInfos = Info; |
1882 | |
1883 | protocol7::CNetMsg_Sv_SkinChange Msg; |
1884 | Msg.m_ClientId = ClientId; |
1885 | for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) |
1886 | { |
1887 | Msg.m_apSkinPartNames[p] = pMsg->m_apSkinPartNames[p]; |
1888 | Msg.m_aSkinPartColors[p] = pMsg->m_aSkinPartColors[p]; |
1889 | Msg.m_aUseCustomColors[p] = pMsg->m_aUseCustomColors[p]; |
1890 | } |
1891 | |
1892 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: -1); |
1893 | |
1894 | return 0; |
1895 | } |
1896 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETSPECTATORMODE) |
1897 | { |
1898 | protocol7::CNetMsg_Cl_SetSpectatorMode *pMsg7 = (protocol7::CNetMsg_Cl_SetSpectatorMode *)pRawMsg; |
1899 | ::CNetMsg_Cl_SetSpectatorMode *pMsg = (::CNetMsg_Cl_SetSpectatorMode *)s_aRawMsg; |
1900 | |
1901 | if(pMsg7->m_SpecMode == protocol7::SPEC_FREEVIEW) |
1902 | pMsg->m_SpectatorId = SPEC_FREEVIEW; |
1903 | else if(pMsg7->m_SpecMode == protocol7::SPEC_PLAYER) |
1904 | pMsg->m_SpectatorId = pMsg7->m_SpectatorId; |
1905 | else |
1906 | pMsg->m_SpectatorId = SPEC_FREEVIEW; // Probably not needed |
1907 | } |
1908 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETTEAM) |
1909 | { |
1910 | protocol7::CNetMsg_Cl_SetTeam *pMsg7 = (protocol7::CNetMsg_Cl_SetTeam *)pRawMsg; |
1911 | ::CNetMsg_Cl_SetTeam *pMsg = (::CNetMsg_Cl_SetTeam *)s_aRawMsg; |
1912 | |
1913 | pMsg->m_Team = pMsg7->m_Team; |
1914 | } |
1915 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_COMMAND) |
1916 | { |
1917 | protocol7::CNetMsg_Cl_Command *pMsg7 = (protocol7::CNetMsg_Cl_Command *)pRawMsg; |
1918 | ::CNetMsg_Cl_Say *pMsg = (::CNetMsg_Cl_Say *)s_aRawMsg; |
1919 | |
1920 | str_format(buffer: s_aRawMsg + sizeof(*pMsg), buffer_size: sizeof(s_aRawMsg) - sizeof(*pMsg), format: "/%s %s" , pMsg7->m_pName, pMsg7->m_pArguments); |
1921 | pMsg->m_pMessage = s_aRawMsg + sizeof(*pMsg); |
1922 | dbg_msg(sys: "debug" , fmt: "line='%s'" , s_aRawMsg + sizeof(*pMsg)); |
1923 | pMsg->m_Team = 0; |
1924 | |
1925 | *pMsgId = NETMSGTYPE_CL_SAY; |
1926 | return s_aRawMsg; |
1927 | } |
1928 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_CALLVOTE) |
1929 | { |
1930 | protocol7::CNetMsg_Cl_CallVote *pMsg7 = (protocol7::CNetMsg_Cl_CallVote *)pRawMsg; |
1931 | ::CNetMsg_Cl_CallVote *pMsg = (::CNetMsg_Cl_CallVote *)s_aRawMsg; |
1932 | |
1933 | int Authed = Server()->GetAuthedState(ClientId); |
1934 | if(pMsg7->m_Force) |
1935 | { |
1936 | str_format(buffer: s_aRawMsg, buffer_size: sizeof(s_aRawMsg), format: "force_vote \"%s\" \"%s\" \"%s\"" , pMsg7->m_pType, pMsg7->m_pValue, pMsg7->m_pReason); |
1937 | Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); |
1938 | Console()->ExecuteLine(pStr: s_aRawMsg, ClientId, InterpretSemicolons: false); |
1939 | Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); |
1940 | return 0; |
1941 | } |
1942 | |
1943 | pMsg->m_pValue = pMsg7->m_pValue; |
1944 | pMsg->m_pReason = pMsg7->m_pReason; |
1945 | pMsg->m_pType = pMsg7->m_pType; |
1946 | } |
1947 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_EMOTICON) |
1948 | { |
1949 | protocol7::CNetMsg_Cl_Emoticon *pMsg7 = (protocol7::CNetMsg_Cl_Emoticon *)pRawMsg; |
1950 | ::CNetMsg_Cl_Emoticon *pMsg = (::CNetMsg_Cl_Emoticon *)s_aRawMsg; |
1951 | |
1952 | pMsg->m_Emoticon = pMsg7->m_Emoticon; |
1953 | } |
1954 | else if(*pMsgId == protocol7::NETMSGTYPE_CL_VOTE) |
1955 | { |
1956 | protocol7::CNetMsg_Cl_Vote *pMsg7 = (protocol7::CNetMsg_Cl_Vote *)pRawMsg; |
1957 | ::CNetMsg_Cl_Vote *pMsg = (::CNetMsg_Cl_Vote *)s_aRawMsg; |
1958 | |
1959 | pMsg->m_Vote = pMsg7->m_Vote; |
1960 | } |
1961 | |
1962 | *pMsgId = Msg_SevenToSix(a: *pMsgId); |
1963 | |
1964 | return s_aRawMsg; |
1965 | } |
1966 | else |
1967 | return m_NetObjHandler.SecureUnpackMsg(Type: *pMsgId, pUnpacker); |
1968 | } |
1969 | |
1970 | void CGameContext::CensorMessage(char *pCensoredMessage, const char *pMessage, int Size) |
1971 | { |
1972 | str_copy(dst: pCensoredMessage, src: pMessage, dst_size: Size); |
1973 | |
1974 | for(auto &Item : m_vCensorlist) |
1975 | { |
1976 | char *pCurLoc = pCensoredMessage; |
1977 | do |
1978 | { |
1979 | pCurLoc = (char *)str_utf8_find_nocase(haystack: pCurLoc, needle: Item.c_str()); |
1980 | if(pCurLoc) |
1981 | { |
1982 | for(int i = 0; i < (int)Item.length(); i++) |
1983 | { |
1984 | pCurLoc[i] = '*'; |
1985 | } |
1986 | pCurLoc++; |
1987 | } |
1988 | } while(pCurLoc); |
1989 | } |
1990 | } |
1991 | |
1992 | void CGameContext::OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId) |
1993 | { |
1994 | if(m_TeeHistorianActive) |
1995 | { |
1996 | if(m_NetObjHandler.TeeHistorianRecordMsg(Type: MsgId)) |
1997 | { |
1998 | m_TeeHistorian.RecordPlayerMessage(ClientId, pMsg: pUnpacker->CompleteData(), MsgSize: pUnpacker->CompleteSize()); |
1999 | } |
2000 | } |
2001 | |
2002 | void *pRawMsg = PreProcessMsg(pMsgId: &MsgId, pUnpacker, ClientId); |
2003 | |
2004 | if(!pRawMsg) |
2005 | return; |
2006 | |
2007 | if(Server()->ClientIngame(ClientId)) |
2008 | { |
2009 | switch(MsgId) |
2010 | { |
2011 | case NETMSGTYPE_CL_SAY: |
2012 | OnSayNetMessage(pMsg: static_cast<CNetMsg_Cl_Say *>(pRawMsg), ClientId, pUnpacker); |
2013 | break; |
2014 | case NETMSGTYPE_CL_CALLVOTE: |
2015 | OnCallVoteNetMessage(pMsg: static_cast<CNetMsg_Cl_CallVote *>(pRawMsg), ClientId); |
2016 | break; |
2017 | case NETMSGTYPE_CL_VOTE: |
2018 | OnVoteNetMessage(pMsg: static_cast<CNetMsg_Cl_Vote *>(pRawMsg), ClientId); |
2019 | break; |
2020 | case NETMSGTYPE_CL_SETTEAM: |
2021 | OnSetTeamNetMessage(pMsg: static_cast<CNetMsg_Cl_SetTeam *>(pRawMsg), ClientId); |
2022 | break; |
2023 | case NETMSGTYPE_CL_ISDDNETLEGACY: |
2024 | OnIsDDNetLegacyNetMessage(pMsg: static_cast<CNetMsg_Cl_IsDDNetLegacy *>(pRawMsg), ClientId, pUnpacker); |
2025 | break; |
2026 | case NETMSGTYPE_CL_SHOWOTHERSLEGACY: |
2027 | OnShowOthersLegacyNetMessage(pMsg: static_cast<CNetMsg_Cl_ShowOthersLegacy *>(pRawMsg), ClientId); |
2028 | break; |
2029 | case NETMSGTYPE_CL_SHOWOTHERS: |
2030 | OnShowOthersNetMessage(pMsg: static_cast<CNetMsg_Cl_ShowOthers *>(pRawMsg), ClientId); |
2031 | break; |
2032 | case NETMSGTYPE_CL_SHOWDISTANCE: |
2033 | OnShowDistanceNetMessage(pMsg: static_cast<CNetMsg_Cl_ShowDistance *>(pRawMsg), ClientId); |
2034 | break; |
2035 | case NETMSGTYPE_CL_SETSPECTATORMODE: |
2036 | OnSetSpectatorModeNetMessage(pMsg: static_cast<CNetMsg_Cl_SetSpectatorMode *>(pRawMsg), ClientId); |
2037 | break; |
2038 | case NETMSGTYPE_CL_CHANGEINFO: |
2039 | OnChangeInfoNetMessage(pMsg: static_cast<CNetMsg_Cl_ChangeInfo *>(pRawMsg), ClientId); |
2040 | break; |
2041 | case NETMSGTYPE_CL_EMOTICON: |
2042 | OnEmoticonNetMessage(pMsg: static_cast<CNetMsg_Cl_Emoticon *>(pRawMsg), ClientId); |
2043 | break; |
2044 | case NETMSGTYPE_CL_KILL: |
2045 | OnKillNetMessage(pMsg: static_cast<CNetMsg_Cl_Kill *>(pRawMsg), ClientId); |
2046 | break; |
2047 | default: |
2048 | break; |
2049 | } |
2050 | } |
2051 | if(MsgId == NETMSGTYPE_CL_STARTINFO) |
2052 | { |
2053 | OnStartInfoNetMessage(pMsg: static_cast<CNetMsg_Cl_StartInfo *>(pRawMsg), ClientId); |
2054 | } |
2055 | } |
2056 | |
2057 | void CGameContext::OnSayNetMessage(const CNetMsg_Cl_Say *pMsg, int ClientId, const CUnpacker *pUnpacker) |
2058 | { |
2059 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2060 | bool Check = !pPlayer->m_NotEligibleForFinish && pPlayer->m_EligibleForFinishCheck + 10 * time_freq() >= time_get(); |
2061 | if(Check && str_comp(a: pMsg->m_pMessage, b: "xd sure chillerbot.png is lyfe" ) == 0 && pMsg->m_Team == 0) |
2062 | { |
2063 | if(m_TeeHistorianActive) |
2064 | { |
2065 | m_TeeHistorian.RecordPlayerMessage(ClientId, pMsg: pUnpacker->CompleteData(), MsgSize: pUnpacker->CompleteSize()); |
2066 | } |
2067 | |
2068 | pPlayer->m_NotEligibleForFinish = true; |
2069 | dbg_msg(sys: "hack" , fmt: "bot detected, cid=%d" , ClientId); |
2070 | return; |
2071 | } |
2072 | int Team = pMsg->m_Team; |
2073 | |
2074 | // trim right and set maximum length to 256 utf8-characters |
2075 | int Length = 0; |
2076 | const char *p = pMsg->m_pMessage; |
2077 | const char *pEnd = 0; |
2078 | while(*p) |
2079 | { |
2080 | const char *pStrOld = p; |
2081 | int Code = str_utf8_decode(ptr: &p); |
2082 | |
2083 | // check if unicode is not empty |
2084 | if(!str_utf8_isspace(code: Code)) |
2085 | { |
2086 | pEnd = 0; |
2087 | } |
2088 | else if(pEnd == 0) |
2089 | pEnd = pStrOld; |
2090 | |
2091 | if(++Length >= 256) |
2092 | { |
2093 | *(const_cast<char *>(p)) = 0; |
2094 | break; |
2095 | } |
2096 | } |
2097 | if(pEnd != 0) |
2098 | *(const_cast<char *>(pEnd)) = 0; |
2099 | |
2100 | // drop empty and autocreated spam messages (more than 32 characters per second) |
2101 | if(Length == 0 || (pMsg->m_pMessage[0] != '/' && (g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat + Server()->TickSpeed() * ((31 + Length) / 32) > Server()->Tick()))) |
2102 | return; |
2103 | |
2104 | int GameTeam = GetDDRaceTeam(ClientId: pPlayer->GetCid()); |
2105 | if(Team) |
2106 | Team = ((pPlayer->GetTeam() == TEAM_SPECTATORS) ? CHAT_SPEC : GameTeam); |
2107 | else |
2108 | Team = CHAT_ALL; |
2109 | |
2110 | if(pMsg->m_pMessage[0] == '/') |
2111 | { |
2112 | if(str_startswith_nocase(str: pMsg->m_pMessage + 1, prefix: "w " )) |
2113 | { |
2114 | char aWhisperMsg[256]; |
2115 | str_copy(dst: aWhisperMsg, src: pMsg->m_pMessage + 3, dst_size: 256); |
2116 | Whisper(ClientId: pPlayer->GetCid(), pStr: aWhisperMsg); |
2117 | } |
2118 | else if(str_startswith_nocase(str: pMsg->m_pMessage + 1, prefix: "whisper " )) |
2119 | { |
2120 | char aWhisperMsg[256]; |
2121 | str_copy(dst: aWhisperMsg, src: pMsg->m_pMessage + 9, dst_size: 256); |
2122 | Whisper(ClientId: pPlayer->GetCid(), pStr: aWhisperMsg); |
2123 | } |
2124 | else if(str_startswith_nocase(str: pMsg->m_pMessage + 1, prefix: "c " )) |
2125 | { |
2126 | char aWhisperMsg[256]; |
2127 | str_copy(dst: aWhisperMsg, src: pMsg->m_pMessage + 3, dst_size: 256); |
2128 | Converse(ClientId: pPlayer->GetCid(), pStr: aWhisperMsg); |
2129 | } |
2130 | else if(str_startswith_nocase(str: pMsg->m_pMessage + 1, prefix: "converse " )) |
2131 | { |
2132 | char aWhisperMsg[256]; |
2133 | str_copy(dst: aWhisperMsg, src: pMsg->m_pMessage + 10, dst_size: 256); |
2134 | Converse(ClientId: pPlayer->GetCid(), pStr: aWhisperMsg); |
2135 | } |
2136 | else |
2137 | { |
2138 | if(g_Config.m_SvSpamprotection && !str_startswith(str: pMsg->m_pMessage + 1, prefix: "timeout " ) && pPlayer->m_aLastCommands[0] && pPlayer->m_aLastCommands[0] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[1] && pPlayer->m_aLastCommands[1] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[2] && pPlayer->m_aLastCommands[2] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[3] && pPlayer->m_aLastCommands[3] + Server()->TickSpeed() > Server()->Tick()) |
2139 | return; |
2140 | |
2141 | int64_t Now = Server()->Tick(); |
2142 | pPlayer->m_aLastCommands[pPlayer->m_LastCommandPos] = Now; |
2143 | pPlayer->m_LastCommandPos = (pPlayer->m_LastCommandPos + 1) % 4; |
2144 | |
2145 | Console()->SetFlagMask(CFGFLAG_CHAT); |
2146 | int Authed = Server()->GetAuthedState(ClientId); |
2147 | if(Authed) |
2148 | Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); |
2149 | else |
2150 | Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); |
2151 | |
2152 | { |
2153 | CClientChatLogger Logger(this, ClientId, log_get_scope_logger()); |
2154 | CLogScope Scope(&Logger); |
2155 | Console()->ExecuteLine(pStr: pMsg->m_pMessage + 1, ClientId, InterpretSemicolons: false); |
2156 | } |
2157 | // m_apPlayers[ClientId] can be NULL, if the player used a |
2158 | // timeout code and replaced another client. |
2159 | char aBuf[256]; |
2160 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d used %s" , ClientId, pMsg->m_pMessage); |
2161 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "chat-command" , pStr: aBuf); |
2162 | |
2163 | Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); |
2164 | Console()->SetFlagMask(CFGFLAG_SERVER); |
2165 | } |
2166 | } |
2167 | else |
2168 | { |
2169 | pPlayer->UpdatePlaytime(); |
2170 | char aCensoredMessage[256]; |
2171 | CensorMessage(pCensoredMessage: aCensoredMessage, pMessage: pMsg->m_pMessage, Size: sizeof(aCensoredMessage)); |
2172 | SendChat(ChatterClientId: ClientId, Team, pText: aCensoredMessage, SpamProtectionClientId: ClientId); |
2173 | } |
2174 | } |
2175 | |
2176 | void CGameContext::OnCallVoteNetMessage(const CNetMsg_Cl_CallVote *pMsg, int ClientId) |
2177 | { |
2178 | if(RateLimitPlayerVote(ClientId) || m_VoteCloseTime) |
2179 | return; |
2180 | |
2181 | m_apPlayers[ClientId]->UpdatePlaytime(); |
2182 | |
2183 | m_VoteType = VOTE_TYPE_UNKNOWN; |
2184 | char aChatmsg[512] = {0}; |
2185 | char aDesc[VOTE_DESC_LENGTH] = {0}; |
2186 | char aSixupDesc[VOTE_DESC_LENGTH] = {0}; |
2187 | char aCmd[VOTE_CMD_LENGTH] = {0}; |
2188 | char aReason[VOTE_REASON_LENGTH] = "No reason given" ; |
2189 | if(pMsg->m_pReason[0]) |
2190 | { |
2191 | str_copy(dst: aReason, src: pMsg->m_pReason, dst_size: sizeof(aReason)); |
2192 | } |
2193 | |
2194 | if(str_comp_nocase(a: pMsg->m_pType, b: "option" ) == 0) |
2195 | { |
2196 | int Authed = Server()->GetAuthedState(ClientId); |
2197 | CVoteOptionServer *pOption = m_pVoteOptionFirst; |
2198 | while(pOption) |
2199 | { |
2200 | if(str_comp_nocase(a: pMsg->m_pValue, b: pOption->m_aDescription) == 0) |
2201 | { |
2202 | if(!Console()->LineIsValid(pStr: pOption->m_aCommand)) |
2203 | { |
2204 | SendChatTarget(To: ClientId, pText: "Invalid option" ); |
2205 | return; |
2206 | } |
2207 | if((str_find(haystack: pOption->m_aCommand, needle: "sv_map " ) != 0 || str_find(haystack: pOption->m_aCommand, needle: "change_map " ) != 0 || str_find(haystack: pOption->m_aCommand, needle: "random_map" ) != 0 || str_find(haystack: pOption->m_aCommand, needle: "random_unfinished_map" ) != 0) && RateLimitPlayerMapVote(ClientId)) |
2208 | { |
2209 | return; |
2210 | } |
2211 | |
2212 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' called vote to change server option '%s' (%s)" , Server()->ClientName(ClientId), |
2213 | pOption->m_aDescription, aReason); |
2214 | str_copy(dst&: aDesc, src: pOption->m_aDescription); |
2215 | |
2216 | if((str_endswith(str: pOption->m_aCommand, suffix: "random_map" ) || str_endswith(str: pOption->m_aCommand, suffix: "random_unfinished_map" )) && str_length(str: aReason) == 1 && aReason[0] >= '0' && aReason[0] <= '5') |
2217 | { |
2218 | int Stars = aReason[0] - '0'; |
2219 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "%s %d" , pOption->m_aCommand, Stars); |
2220 | } |
2221 | else |
2222 | { |
2223 | str_copy(dst&: aCmd, src: pOption->m_aCommand); |
2224 | } |
2225 | |
2226 | m_LastMapVote = time_get(); |
2227 | break; |
2228 | } |
2229 | |
2230 | pOption = pOption->m_pNext; |
2231 | } |
2232 | |
2233 | if(!pOption) |
2234 | { |
2235 | if(Authed != AUTHED_ADMIN) // allow admins to call any vote they want |
2236 | { |
2237 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' isn't an option on this server" , pMsg->m_pValue); |
2238 | SendChatTarget(To: ClientId, pText: aChatmsg); |
2239 | return; |
2240 | } |
2241 | else |
2242 | { |
2243 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' called vote to change server option '%s'" , Server()->ClientName(ClientId), pMsg->m_pValue); |
2244 | str_copy(dst&: aDesc, src: pMsg->m_pValue); |
2245 | str_copy(dst&: aCmd, src: pMsg->m_pValue); |
2246 | } |
2247 | } |
2248 | |
2249 | m_VoteType = VOTE_TYPE_OPTION; |
2250 | } |
2251 | else if(str_comp_nocase(a: pMsg->m_pType, b: "kick" ) == 0) |
2252 | { |
2253 | int Authed = Server()->GetAuthedState(ClientId); |
2254 | |
2255 | if(!g_Config.m_SvVoteKick && !Authed) // allow admins to call kick votes even if they are forbidden |
2256 | { |
2257 | SendChatTarget(To: ClientId, pText: "Server does not allow voting to kick players" ); |
2258 | return; |
2259 | } |
2260 | if(!Authed && time_get() < m_apPlayers[ClientId]->m_Last_KickVote + (time_freq() * g_Config.m_SvVoteKickDelay)) |
2261 | { |
2262 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "There's a %d second wait time between kick votes for each player please wait %d second(s)" , |
2263 | g_Config.m_SvVoteKickDelay, |
2264 | (int)((m_apPlayers[ClientId]->m_Last_KickVote + g_Config.m_SvVoteKickDelay * time_freq() - time_get()) / time_freq())); |
2265 | SendChatTarget(To: ClientId, pText: aChatmsg); |
2266 | return; |
2267 | } |
2268 | |
2269 | if(g_Config.m_SvVoteKickMin && !GetDDRaceTeam(ClientId)) |
2270 | { |
2271 | char aaAddresses[MAX_CLIENTS][NETADDR_MAXSTRSIZE] = {{0}}; |
2272 | for(int i = 0; i < MAX_CLIENTS; i++) |
2273 | { |
2274 | if(m_apPlayers[i]) |
2275 | { |
2276 | Server()->GetClientAddr(ClientId: i, pAddrStr: aaAddresses[i], Size: NETADDR_MAXSTRSIZE); |
2277 | } |
2278 | } |
2279 | int NumPlayers = 0; |
2280 | for(int i = 0; i < MAX_CLIENTS; ++i) |
2281 | { |
2282 | if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(ClientId: i)) |
2283 | { |
2284 | NumPlayers++; |
2285 | for(int j = 0; j < i; j++) |
2286 | { |
2287 | if(m_apPlayers[j] && m_apPlayers[j]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(ClientId: j)) |
2288 | { |
2289 | if(str_comp(a: aaAddresses[i], b: aaAddresses[j]) == 0) |
2290 | { |
2291 | NumPlayers--; |
2292 | break; |
2293 | } |
2294 | } |
2295 | } |
2296 | } |
2297 | } |
2298 | |
2299 | if(NumPlayers < g_Config.m_SvVoteKickMin) |
2300 | { |
2301 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "Kick voting requires %d players" , g_Config.m_SvVoteKickMin); |
2302 | SendChatTarget(To: ClientId, pText: aChatmsg); |
2303 | return; |
2304 | } |
2305 | } |
2306 | |
2307 | int KickId = str_toint(str: pMsg->m_pValue); |
2308 | |
2309 | if(KickId < 0 || KickId >= MAX_CLIENTS || !m_apPlayers[KickId]) |
2310 | { |
2311 | SendChatTarget(To: ClientId, pText: "Invalid client id to kick" ); |
2312 | return; |
2313 | } |
2314 | if(KickId == ClientId) |
2315 | { |
2316 | SendChatTarget(To: ClientId, pText: "You can't kick yourself" ); |
2317 | return; |
2318 | } |
2319 | if(!Server()->ReverseTranslate(Target&: KickId, Client: ClientId)) |
2320 | { |
2321 | return; |
2322 | } |
2323 | int KickedAuthed = Server()->GetAuthedState(ClientId: KickId); |
2324 | if(KickedAuthed > Authed) |
2325 | { |
2326 | SendChatTarget(To: ClientId, pText: "You can't kick authorized players" ); |
2327 | char aBufKick[128]; |
2328 | str_format(buffer: aBufKick, buffer_size: sizeof(aBufKick), format: "'%s' called for vote to kick you" , Server()->ClientName(ClientId)); |
2329 | SendChatTarget(To: KickId, pText: aBufKick); |
2330 | return; |
2331 | } |
2332 | |
2333 | // Don't allow kicking if a player has no character |
2334 | if(!GetPlayerChar(ClientId) || !GetPlayerChar(ClientId: KickId) || GetDDRaceTeam(ClientId) != GetDDRaceTeam(ClientId: KickId)) |
2335 | { |
2336 | SendChatTarget(To: ClientId, pText: "You can kick only your team member" ); |
2337 | return; |
2338 | } |
2339 | |
2340 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' called for vote to kick '%s' (%s)" , Server()->ClientName(ClientId), Server()->ClientName(ClientId: KickId), aReason); |
2341 | str_format(buffer: aSixupDesc, buffer_size: sizeof(aSixupDesc), format: "%2d: %s" , KickId, Server()->ClientName(ClientId: KickId)); |
2342 | if(!GetDDRaceTeam(ClientId)) |
2343 | { |
2344 | if(!g_Config.m_SvVoteKickBantime) |
2345 | { |
2346 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "kick %d Kicked by vote" , KickId); |
2347 | str_format(buffer: aDesc, buffer_size: sizeof(aDesc), format: "Kick '%s'" , Server()->ClientName(ClientId: KickId)); |
2348 | } |
2349 | else |
2350 | { |
2351 | char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; |
2352 | Server()->GetClientAddr(ClientId: KickId, pAddrStr: aAddrStr, Size: sizeof(aAddrStr)); |
2353 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "ban %s %d Banned by vote" , aAddrStr, g_Config.m_SvVoteKickBantime); |
2354 | str_format(buffer: aDesc, buffer_size: sizeof(aDesc), format: "Ban '%s'" , Server()->ClientName(ClientId: KickId)); |
2355 | } |
2356 | } |
2357 | else |
2358 | { |
2359 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "uninvite %d %d; set_team_ddr %d 0" , KickId, GetDDRaceTeam(ClientId: KickId), KickId); |
2360 | str_format(buffer: aDesc, buffer_size: sizeof(aDesc), format: "Move '%s' to team 0" , Server()->ClientName(ClientId: KickId)); |
2361 | } |
2362 | m_apPlayers[ClientId]->m_Last_KickVote = time_get(); |
2363 | m_VoteType = VOTE_TYPE_KICK; |
2364 | m_VoteVictim = KickId; |
2365 | } |
2366 | else if(str_comp_nocase(a: pMsg->m_pType, b: "spectate" ) == 0) |
2367 | { |
2368 | if(!g_Config.m_SvVoteSpectate) |
2369 | { |
2370 | SendChatTarget(To: ClientId, pText: "Server does not allow voting to move players to spectators" ); |
2371 | return; |
2372 | } |
2373 | |
2374 | int SpectateId = str_toint(str: pMsg->m_pValue); |
2375 | |
2376 | if(SpectateId < 0 || SpectateId >= MAX_CLIENTS || !m_apPlayers[SpectateId] || m_apPlayers[SpectateId]->GetTeam() == TEAM_SPECTATORS) |
2377 | { |
2378 | SendChatTarget(To: ClientId, pText: "Invalid client id to move" ); |
2379 | return; |
2380 | } |
2381 | if(SpectateId == ClientId) |
2382 | { |
2383 | SendChatTarget(To: ClientId, pText: "You can't move yourself" ); |
2384 | return; |
2385 | } |
2386 | if(!Server()->ReverseTranslate(Target&: SpectateId, Client: ClientId)) |
2387 | { |
2388 | return; |
2389 | } |
2390 | |
2391 | if(!GetPlayerChar(ClientId) || !GetPlayerChar(ClientId: SpectateId) || GetDDRaceTeam(ClientId) != GetDDRaceTeam(ClientId: SpectateId)) |
2392 | { |
2393 | SendChatTarget(To: ClientId, pText: "You can only move your team member to spectators" ); |
2394 | return; |
2395 | } |
2396 | |
2397 | str_format(buffer: aSixupDesc, buffer_size: sizeof(aSixupDesc), format: "%2d: %s" , SpectateId, Server()->ClientName(ClientId: SpectateId)); |
2398 | if(g_Config.m_SvPauseable && g_Config.m_SvVotePause) |
2399 | { |
2400 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' called for vote to pause '%s' for %d seconds (%s)" , Server()->ClientName(ClientId), Server()->ClientName(ClientId: SpectateId), g_Config.m_SvVotePauseTime, aReason); |
2401 | str_format(buffer: aDesc, buffer_size: sizeof(aDesc), format: "Pause '%s' (%ds)" , Server()->ClientName(ClientId: SpectateId), g_Config.m_SvVotePauseTime); |
2402 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "uninvite %d %d; force_pause %d %d" , SpectateId, GetDDRaceTeam(ClientId: SpectateId), SpectateId, g_Config.m_SvVotePauseTime); |
2403 | } |
2404 | else |
2405 | { |
2406 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "'%s' called for vote to move '%s' to spectators (%s)" , Server()->ClientName(ClientId), Server()->ClientName(ClientId: SpectateId), aReason); |
2407 | str_format(buffer: aDesc, buffer_size: sizeof(aDesc), format: "Move '%s' to spectators" , Server()->ClientName(ClientId: SpectateId)); |
2408 | str_format(buffer: aCmd, buffer_size: sizeof(aCmd), format: "uninvite %d %d; set_team %d -1 %d" , SpectateId, GetDDRaceTeam(ClientId: SpectateId), SpectateId, g_Config.m_SvVoteSpectateRejoindelay); |
2409 | } |
2410 | m_VoteType = VOTE_TYPE_SPECTATE; |
2411 | m_VoteVictim = SpectateId; |
2412 | } |
2413 | |
2414 | if(aCmd[0] && str_comp_nocase(a: aCmd, b: "info" ) != 0) |
2415 | CallVote(ClientId, pDesc: aDesc, pCmd: aCmd, pReason: aReason, pChatmsg: aChatmsg, pSixupDesc: aSixupDesc[0] ? aSixupDesc : 0); |
2416 | } |
2417 | |
2418 | void CGameContext::OnVoteNetMessage(const CNetMsg_Cl_Vote *pMsg, int ClientId) |
2419 | { |
2420 | if(!m_VoteCloseTime) |
2421 | return; |
2422 | |
2423 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2424 | |
2425 | if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + Server()->TickSpeed() * 3 > Server()->Tick()) |
2426 | return; |
2427 | |
2428 | int64_t Now = Server()->Tick(); |
2429 | |
2430 | pPlayer->m_LastVoteTry = Now; |
2431 | pPlayer->UpdatePlaytime(); |
2432 | |
2433 | if(!pMsg->m_Vote) |
2434 | return; |
2435 | |
2436 | // Allow the vote creator to cancel the vote |
2437 | if(pPlayer->GetCid() == m_VoteCreator && pMsg->m_Vote == -1) |
2438 | { |
2439 | m_VoteEnforce = VOTE_ENFORCE_CANCEL; |
2440 | return; |
2441 | } |
2442 | |
2443 | pPlayer->m_Vote = pMsg->m_Vote; |
2444 | pPlayer->m_VotePos = ++m_VotePos; |
2445 | m_VoteUpdate = true; |
2446 | |
2447 | CNetMsg_Sv_YourVote Msg = {.m_Voted: pMsg->m_Vote}; |
2448 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
2449 | } |
2450 | |
2451 | void CGameContext::OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int ClientId) |
2452 | { |
2453 | if(m_World.m_Paused) |
2454 | return; |
2455 | |
2456 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2457 | |
2458 | if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam + Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick())) |
2459 | return; |
2460 | |
2461 | // Kill Protection |
2462 | CCharacter *pChr = pPlayer->GetCharacter(); |
2463 | if(pChr) |
2464 | { |
2465 | int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); |
2466 | if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) |
2467 | { |
2468 | SendChatTarget(To: ClientId, pText: "Kill Protection enabled. If you really want to join the spectators, first type /kill" ); |
2469 | return; |
2470 | } |
2471 | } |
2472 | |
2473 | if(pPlayer->m_TeamChangeTick > Server()->Tick()) |
2474 | { |
2475 | pPlayer->m_LastSetTeam = Server()->Tick(); |
2476 | int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick()) / Server()->TickSpeed(); |
2477 | char aTime[32]; |
2478 | str_time(centisecs: (int64_t)TimeLeft * 100, format: TIME_HOURS, buffer: aTime, buffer_size: sizeof(aTime)); |
2479 | char aBuf[128]; |
2480 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Time to wait before changing team: %s" , aTime); |
2481 | SendBroadcast(pText: aBuf, ClientId); |
2482 | return; |
2483 | } |
2484 | |
2485 | // Switch team on given client and kill/respawn them |
2486 | char aTeamJoinError[512]; |
2487 | if(m_pController->CanJoinTeam(Team: pMsg->m_Team, NotThisId: ClientId, pErrorReason: aTeamJoinError, ErrorReasonSize: sizeof(aTeamJoinError))) |
2488 | { |
2489 | if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) |
2490 | m_VoteUpdate = true; |
2491 | m_pController->DoTeamChange(pPlayer, Team: pMsg->m_Team); |
2492 | pPlayer->m_TeamChangeTick = Server()->Tick(); |
2493 | } |
2494 | else |
2495 | SendBroadcast(pText: aTeamJoinError, ClientId); |
2496 | } |
2497 | |
2498 | void CGameContext::OnIsDDNetLegacyNetMessage(const CNetMsg_Cl_IsDDNetLegacy *pMsg, int ClientId, CUnpacker *pUnpacker) |
2499 | { |
2500 | IServer::CClientInfo Info; |
2501 | if(Server()->GetClientInfo(ClientId, pInfo: &Info) && Info.m_GotDDNetVersion) |
2502 | { |
2503 | return; |
2504 | } |
2505 | int DDNetVersion = pUnpacker->GetInt(); |
2506 | if(pUnpacker->Error() || DDNetVersion < 0) |
2507 | { |
2508 | DDNetVersion = VERSION_DDRACE; |
2509 | } |
2510 | Server()->SetClientDDNetVersion(ClientId, DDNetVersion); |
2511 | OnClientDDNetVersionKnown(ClientId); |
2512 | } |
2513 | |
2514 | void CGameContext::OnShowOthersLegacyNetMessage(const CNetMsg_Cl_ShowOthersLegacy *pMsg, int ClientId) |
2515 | { |
2516 | if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) |
2517 | { |
2518 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2519 | pPlayer->m_ShowOthers = pMsg->m_Show; |
2520 | } |
2521 | } |
2522 | |
2523 | void CGameContext::OnShowOthersNetMessage(const CNetMsg_Cl_ShowOthers *pMsg, int ClientId) |
2524 | { |
2525 | if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) |
2526 | { |
2527 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2528 | pPlayer->m_ShowOthers = pMsg->m_Show; |
2529 | } |
2530 | } |
2531 | |
2532 | void CGameContext::OnShowDistanceNetMessage(const CNetMsg_Cl_ShowDistance *pMsg, int ClientId) |
2533 | { |
2534 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2535 | pPlayer->m_ShowDistance = vec2(pMsg->m_X, pMsg->m_Y); |
2536 | } |
2537 | |
2538 | void CGameContext::OnSetSpectatorModeNetMessage(const CNetMsg_Cl_SetSpectatorMode *pMsg, int ClientId) |
2539 | { |
2540 | if(m_World.m_Paused) |
2541 | return; |
2542 | |
2543 | int SpectatorId = clamp(val: pMsg->m_SpectatorId, lo: (int)SPEC_FOLLOW, hi: MAX_CLIENTS - 1); |
2544 | if(SpectatorId >= 0) |
2545 | if(!Server()->ReverseTranslate(Target&: SpectatorId, Client: ClientId)) |
2546 | return; |
2547 | |
2548 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2549 | if((g_Config.m_SvSpamprotection && pPlayer->m_LastSetSpectatorMode && pPlayer->m_LastSetSpectatorMode + Server()->TickSpeed() / 4 > Server()->Tick())) |
2550 | return; |
2551 | |
2552 | pPlayer->m_LastSetSpectatorMode = Server()->Tick(); |
2553 | pPlayer->UpdatePlaytime(); |
2554 | if(SpectatorId >= 0 && (!m_apPlayers[SpectatorId] || m_apPlayers[SpectatorId]->GetTeam() == TEAM_SPECTATORS)) |
2555 | SendChatTarget(To: ClientId, pText: "Invalid spectator id used" ); |
2556 | else |
2557 | pPlayer->m_SpectatorId = SpectatorId; |
2558 | } |
2559 | |
2560 | void CGameContext::OnChangeInfoNetMessage(const CNetMsg_Cl_ChangeInfo *pMsg, int ClientId) |
2561 | { |
2562 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2563 | if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay > Server()->Tick()) |
2564 | return; |
2565 | |
2566 | bool SixupNeedsUpdate = false; |
2567 | |
2568 | pPlayer->m_LastChangeInfo = Server()->Tick(); |
2569 | pPlayer->UpdatePlaytime(); |
2570 | |
2571 | if(g_Config.m_SvSpamprotection) |
2572 | { |
2573 | CNetMsg_Sv_ChangeInfoCooldown ChangeInfoCooldownMsg; |
2574 | ChangeInfoCooldownMsg.m_WaitUntil = Server()->Tick() + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay; |
2575 | Server()->SendPackMsg(pMsg: &ChangeInfoCooldownMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
2576 | } |
2577 | |
2578 | // set infos |
2579 | if(Server()->WouldClientNameChange(ClientId, pNameRequest: pMsg->m_pName) && !ProcessSpamProtection(ClientId)) |
2580 | { |
2581 | char aOldName[MAX_NAME_LENGTH]; |
2582 | str_copy(dst: aOldName, src: Server()->ClientName(ClientId), dst_size: sizeof(aOldName)); |
2583 | |
2584 | Server()->SetClientName(ClientId, pName: pMsg->m_pName); |
2585 | |
2586 | char aChatText[256]; |
2587 | str_format(buffer: aChatText, buffer_size: sizeof(aChatText), format: "'%s' changed name to '%s'" , aOldName, Server()->ClientName(ClientId)); |
2588 | SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: aChatText); |
2589 | |
2590 | // reload scores |
2591 | Score()->PlayerData(Id: ClientId)->Reset(); |
2592 | m_apPlayers[ClientId]->m_Score.reset(); |
2593 | Score()->LoadPlayerData(ClientId); |
2594 | |
2595 | SixupNeedsUpdate = true; |
2596 | |
2597 | LogEvent(Description: "Name change" , ClientId); |
2598 | } |
2599 | |
2600 | if(Server()->WouldClientClanChange(ClientId, pClanRequest: pMsg->m_pClan)) |
2601 | { |
2602 | SixupNeedsUpdate = true; |
2603 | Server()->SetClientClan(ClientId, pClan: pMsg->m_pClan); |
2604 | } |
2605 | |
2606 | if(Server()->ClientCountry(ClientId) != pMsg->m_Country) |
2607 | SixupNeedsUpdate = true; |
2608 | Server()->SetClientCountry(ClientId, Country: pMsg->m_Country); |
2609 | |
2610 | str_copy(dst: pPlayer->m_TeeInfos.m_aSkinName, src: pMsg->m_pSkin, dst_size: sizeof(pPlayer->m_TeeInfos.m_aSkinName)); |
2611 | pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; |
2612 | pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; |
2613 | pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; |
2614 | if(!Server()->IsSixup(ClientId)) |
2615 | pPlayer->m_TeeInfos.ToSixup(); |
2616 | |
2617 | if(SixupNeedsUpdate) |
2618 | { |
2619 | protocol7::CNetMsg_Sv_ClientDrop Drop; |
2620 | Drop.m_ClientId = ClientId; |
2621 | Drop.m_pReason = "" ; |
2622 | Drop.m_Silent = true; |
2623 | |
2624 | protocol7::CNetMsg_Sv_ClientInfo Info; |
2625 | Info.m_ClientId = ClientId; |
2626 | Info.m_pName = Server()->ClientName(ClientId); |
2627 | Info.m_Country = pMsg->m_Country; |
2628 | Info.m_pClan = pMsg->m_pClan; |
2629 | Info.m_Local = 0; |
2630 | Info.m_Silent = true; |
2631 | Info.m_Team = pPlayer->GetTeam(); |
2632 | |
2633 | for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) |
2634 | { |
2635 | Info.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; |
2636 | Info.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; |
2637 | Info.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; |
2638 | } |
2639 | |
2640 | for(int i = 0; i < Server()->MaxClients(); i++) |
2641 | { |
2642 | if(i != ClientId) |
2643 | { |
2644 | Server()->SendPackMsg(pMsg: &Drop, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
2645 | Server()->SendPackMsg(pMsg: &Info, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: i); |
2646 | } |
2647 | } |
2648 | } |
2649 | else |
2650 | { |
2651 | protocol7::CNetMsg_Sv_SkinChange Msg; |
2652 | Msg.m_ClientId = ClientId; |
2653 | for(int p = 0; p < protocol7::NUM_SKINPARTS; p++) |
2654 | { |
2655 | Msg.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; |
2656 | Msg.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; |
2657 | Msg.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; |
2658 | } |
2659 | |
2660 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: -1); |
2661 | } |
2662 | |
2663 | Server()->ExpireServerInfo(); |
2664 | } |
2665 | |
2666 | void CGameContext::OnEmoticonNetMessage(const CNetMsg_Cl_Emoticon *pMsg, int ClientId) |
2667 | { |
2668 | if(m_World.m_Paused) |
2669 | return; |
2670 | |
2671 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2672 | |
2673 | auto &&CheckPreventEmote = [&](int64_t LastEmote, int64_t DelayInMs) { |
2674 | return (LastEmote * (int64_t)1000) + (int64_t)Server()->TickSpeed() * DelayInMs > ((int64_t)Server()->Tick() * (int64_t)1000); |
2675 | }; |
2676 | |
2677 | if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmote, (int64_t)g_Config.m_SvEmoticonMsDelay)) |
2678 | return; |
2679 | |
2680 | CCharacter *pChr = pPlayer->GetCharacter(); |
2681 | |
2682 | // player needs a character to send emotes |
2683 | if(!pChr) |
2684 | return; |
2685 | |
2686 | pPlayer->m_LastEmote = Server()->Tick(); |
2687 | pPlayer->UpdatePlaytime(); |
2688 | |
2689 | // check if the global emoticon is prevented and emotes are only send to nearby players |
2690 | if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmoteGlobal, (int64_t)g_Config.m_SvGlobalEmoticonMsDelay)) |
2691 | { |
2692 | for(int i = 0; i < MAX_CLIENTS; ++i) |
2693 | { |
2694 | if(m_apPlayers[i] && pChr->CanSnapCharacter(SnappingClient: i) && pChr->IsSnappingCharacterInView(SnappingClientId: i)) |
2695 | { |
2696 | SendEmoticon(ClientId, Emoticon: pMsg->m_Emoticon, TargetClientId: i); |
2697 | } |
2698 | } |
2699 | } |
2700 | else |
2701 | { |
2702 | // else send emoticons to all players |
2703 | pPlayer->m_LastEmoteGlobal = Server()->Tick(); |
2704 | SendEmoticon(ClientId, Emoticon: pMsg->m_Emoticon, TargetClientId: -1); |
2705 | } |
2706 | |
2707 | if(g_Config.m_SvEmotionalTees == 1 && pPlayer->m_EyeEmoteEnabled) |
2708 | { |
2709 | int EmoteType = EMOTE_NORMAL; |
2710 | switch(pMsg->m_Emoticon) |
2711 | { |
2712 | case EMOTICON_EXCLAMATION: |
2713 | case EMOTICON_GHOST: |
2714 | case EMOTICON_QUESTION: |
2715 | case EMOTICON_WTF: |
2716 | EmoteType = EMOTE_SURPRISE; |
2717 | break; |
2718 | case EMOTICON_DOTDOT: |
2719 | case EMOTICON_DROP: |
2720 | case EMOTICON_ZZZ: |
2721 | EmoteType = EMOTE_BLINK; |
2722 | break; |
2723 | case EMOTICON_EYES: |
2724 | case EMOTICON_HEARTS: |
2725 | case EMOTICON_MUSIC: |
2726 | EmoteType = EMOTE_HAPPY; |
2727 | break; |
2728 | case EMOTICON_OOP: |
2729 | case EMOTICON_SORRY: |
2730 | case EMOTICON_SUSHI: |
2731 | EmoteType = EMOTE_PAIN; |
2732 | break; |
2733 | case EMOTICON_DEVILTEE: |
2734 | case EMOTICON_SPLATTEE: |
2735 | case EMOTICON_ZOMG: |
2736 | EmoteType = EMOTE_ANGRY; |
2737 | break; |
2738 | default: |
2739 | break; |
2740 | } |
2741 | pChr->SetEmote(Emote: EmoteType, Tick: Server()->Tick() + 2 * Server()->TickSpeed()); |
2742 | } |
2743 | } |
2744 | |
2745 | void CGameContext::OnKillNetMessage(const CNetMsg_Cl_Kill *pMsg, int ClientId) |
2746 | { |
2747 | if(m_World.m_Paused) |
2748 | return; |
2749 | |
2750 | if(m_VoteCloseTime && m_VoteCreator == ClientId && GetDDRaceTeam(ClientId) && (IsKickVote() || IsSpecVote())) |
2751 | { |
2752 | SendChatTarget(To: ClientId, pText: "You are running a vote please try again after the vote is done!" ); |
2753 | return; |
2754 | } |
2755 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2756 | if(pPlayer->m_LastKill && pPlayer->m_LastKill + Server()->TickSpeed() * g_Config.m_SvKillDelay > Server()->Tick()) |
2757 | return; |
2758 | if(pPlayer->IsPaused()) |
2759 | return; |
2760 | |
2761 | CCharacter *pChr = pPlayer->GetCharacter(); |
2762 | if(!pChr) |
2763 | return; |
2764 | |
2765 | // Kill Protection |
2766 | int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); |
2767 | if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) |
2768 | { |
2769 | SendChatTarget(To: ClientId, pText: "Kill Protection enabled. If you really want to kill, type /kill" ); |
2770 | return; |
2771 | } |
2772 | |
2773 | pPlayer->m_LastKill = Server()->Tick(); |
2774 | pPlayer->KillCharacter(Weapon: WEAPON_SELF); |
2775 | pPlayer->Respawn(); |
2776 | } |
2777 | |
2778 | void CGameContext::OnStartInfoNetMessage(const CNetMsg_Cl_StartInfo *pMsg, int ClientId) |
2779 | { |
2780 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
2781 | |
2782 | if(pPlayer->m_IsReady) |
2783 | return; |
2784 | |
2785 | pPlayer->m_LastChangeInfo = Server()->Tick(); |
2786 | |
2787 | // set start infos |
2788 | Server()->SetClientName(ClientId, pName: pMsg->m_pName); |
2789 | // trying to set client name can delete the player object, check if it still exists |
2790 | if(!m_apPlayers[ClientId]) |
2791 | { |
2792 | return; |
2793 | } |
2794 | Server()->SetClientClan(ClientId, pClan: pMsg->m_pClan); |
2795 | // trying to set client clan can delete the player object, check if it still exists |
2796 | if(!m_apPlayers[ClientId]) |
2797 | { |
2798 | return; |
2799 | } |
2800 | Server()->SetClientCountry(ClientId, Country: pMsg->m_Country); |
2801 | str_copy(dst: pPlayer->m_TeeInfos.m_aSkinName, src: pMsg->m_pSkin, dst_size: sizeof(pPlayer->m_TeeInfos.m_aSkinName)); |
2802 | pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; |
2803 | pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; |
2804 | pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; |
2805 | if(!Server()->IsSixup(ClientId)) |
2806 | pPlayer->m_TeeInfos.ToSixup(); |
2807 | |
2808 | // send clear vote options |
2809 | CNetMsg_Sv_VoteClearOptions ClearMsg; |
2810 | Server()->SendPackMsg(pMsg: &ClearMsg, Flags: MSGFLAG_VITAL, ClientId); |
2811 | |
2812 | // begin sending vote options |
2813 | pPlayer->m_SendVoteIndex = 0; |
2814 | |
2815 | // send tuning parameters to client |
2816 | SendTuningParams(ClientId, Zone: pPlayer->m_TuneZone); |
2817 | |
2818 | // client is ready to enter |
2819 | pPlayer->m_IsReady = true; |
2820 | CNetMsg_Sv_ReadyToEnter m; |
2821 | Server()->SendPackMsg(pMsg: &m, Flags: MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientId); |
2822 | |
2823 | Server()->ExpireServerInfo(); |
2824 | } |
2825 | |
2826 | void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData) |
2827 | { |
2828 | CGameContext *pSelf = (CGameContext *)pUserData; |
2829 | const char *pParamName = pResult->GetString(Index: 0); |
2830 | |
2831 | char aBuf[256]; |
2832 | if(pResult->NumArguments() == 2) |
2833 | { |
2834 | float NewValue = pResult->GetFloat(Index: 1); |
2835 | if(pSelf->Tuning()->Set(pName: pParamName, Value: NewValue) && pSelf->Tuning()->Get(pName: pParamName, pValue: &NewValue)) |
2836 | { |
2837 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s changed to %.2f" , pParamName, NewValue); |
2838 | pSelf->SendTuningParams(ClientId: -1); |
2839 | } |
2840 | else |
2841 | { |
2842 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such tuning parameter: %s" , pParamName); |
2843 | } |
2844 | } |
2845 | else |
2846 | { |
2847 | float Value; |
2848 | if(pSelf->Tuning()->Get(pName: pParamName, pValue: &Value)) |
2849 | { |
2850 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %.2f" , pParamName, Value); |
2851 | } |
2852 | else |
2853 | { |
2854 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such tuning parameter: %s" , pParamName); |
2855 | } |
2856 | } |
2857 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2858 | } |
2859 | |
2860 | void CGameContext::ConToggleTuneParam(IConsole::IResult *pResult, void *pUserData) |
2861 | { |
2862 | CGameContext *pSelf = (CGameContext *)pUserData; |
2863 | const char *pParamName = pResult->GetString(Index: 0); |
2864 | float OldValue; |
2865 | |
2866 | char aBuf[256]; |
2867 | if(!pSelf->Tuning()->Get(pName: pParamName, pValue: &OldValue)) |
2868 | { |
2869 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such tuning parameter: %s" , pParamName); |
2870 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2871 | return; |
2872 | } |
2873 | |
2874 | float NewValue = absolute(a: OldValue - pResult->GetFloat(Index: 1)) < 0.0001f ? pResult->GetFloat(Index: 2) : pResult->GetFloat(Index: 1); |
2875 | |
2876 | pSelf->Tuning()->Set(pName: pParamName, Value: NewValue); |
2877 | pSelf->Tuning()->Get(pName: pParamName, pValue: &NewValue); |
2878 | |
2879 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s changed to %.2f" , pParamName, NewValue); |
2880 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2881 | pSelf->SendTuningParams(ClientId: -1); |
2882 | } |
2883 | |
2884 | void CGameContext::ConTuneReset(IConsole::IResult *pResult, void *pUserData) |
2885 | { |
2886 | CGameContext *pSelf = (CGameContext *)pUserData; |
2887 | if(pResult->NumArguments()) |
2888 | { |
2889 | const char *pParamName = pResult->GetString(Index: 0); |
2890 | float DefaultValue = 0.0f; |
2891 | char aBuf[256]; |
2892 | CTuningParams TuningParams; |
2893 | if(TuningParams.Get(pName: pParamName, pValue: &DefaultValue) && pSelf->Tuning()->Set(pName: pParamName, Value: DefaultValue) && pSelf->Tuning()->Get(pName: pParamName, pValue: &DefaultValue)) |
2894 | { |
2895 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s reset to %.2f" , pParamName, DefaultValue); |
2896 | pSelf->SendTuningParams(ClientId: -1); |
2897 | } |
2898 | else |
2899 | { |
2900 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such tuning parameter: %s" , pParamName); |
2901 | } |
2902 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2903 | } |
2904 | else |
2905 | { |
2906 | pSelf->ResetTuning(); |
2907 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: "Tuning reset" ); |
2908 | } |
2909 | } |
2910 | |
2911 | void CGameContext::ConTunes(IConsole::IResult *pResult, void *pUserData) |
2912 | { |
2913 | CGameContext *pSelf = (CGameContext *)pUserData; |
2914 | char aBuf[256]; |
2915 | for(int i = 0; i < CTuningParams::Num(); i++) |
2916 | { |
2917 | float Value; |
2918 | pSelf->Tuning()->Get(Index: i, pValue: &Value); |
2919 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %.2f" , CTuningParams::Name(Index: i), Value); |
2920 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2921 | } |
2922 | } |
2923 | |
2924 | void CGameContext::ConTuneZone(IConsole::IResult *pResult, void *pUserData) |
2925 | { |
2926 | CGameContext *pSelf = (CGameContext *)pUserData; |
2927 | int List = pResult->GetInteger(Index: 0); |
2928 | const char *pParamName = pResult->GetString(Index: 1); |
2929 | float NewValue = pResult->GetFloat(Index: 2); |
2930 | |
2931 | if(List >= 0 && List < NUM_TUNEZONES) |
2932 | { |
2933 | char aBuf[256]; |
2934 | if(pSelf->TuningList()[List].Set(pName: pParamName, Value: NewValue) && pSelf->TuningList()[List].Get(pName: pParamName, pValue: &NewValue)) |
2935 | { |
2936 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s in zone %d changed to %.2f" , pParamName, List, NewValue); |
2937 | pSelf->SendTuningParams(ClientId: -1, Zone: List); |
2938 | } |
2939 | else |
2940 | { |
2941 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No such tuning parameter: %s" , pParamName); |
2942 | } |
2943 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2944 | } |
2945 | } |
2946 | |
2947 | void CGameContext::ConTuneDumpZone(IConsole::IResult *pResult, void *pUserData) |
2948 | { |
2949 | CGameContext *pSelf = (CGameContext *)pUserData; |
2950 | int List = pResult->GetInteger(Index: 0); |
2951 | char aBuf[256]; |
2952 | if(List >= 0 && List < NUM_TUNEZONES) |
2953 | { |
2954 | for(int i = 0; i < CTuningParams::Num(); i++) |
2955 | { |
2956 | float Value; |
2957 | pSelf->TuningList()[List].Get(Index: i, pValue: &Value); |
2958 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "zone %d: %s %.2f" , List, CTuningParams::Name(Index: i), Value); |
2959 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2960 | } |
2961 | } |
2962 | } |
2963 | |
2964 | void CGameContext::ConTuneResetZone(IConsole::IResult *pResult, void *pUserData) |
2965 | { |
2966 | CGameContext *pSelf = (CGameContext *)pUserData; |
2967 | CTuningParams TuningParams; |
2968 | if(pResult->NumArguments()) |
2969 | { |
2970 | int List = pResult->GetInteger(Index: 0); |
2971 | if(List >= 0 && List < NUM_TUNEZONES) |
2972 | { |
2973 | pSelf->TuningList()[List] = TuningParams; |
2974 | char aBuf[256]; |
2975 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Tunezone %d reset" , List); |
2976 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: aBuf); |
2977 | pSelf->SendTuningParams(ClientId: -1, Zone: List); |
2978 | } |
2979 | } |
2980 | else |
2981 | { |
2982 | for(int i = 0; i < NUM_TUNEZONES; i++) |
2983 | { |
2984 | *(pSelf->TuningList() + i) = TuningParams; |
2985 | pSelf->SendTuningParams(ClientId: -1, Zone: i); |
2986 | } |
2987 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "tuning" , pStr: "All Tunezones reset" ); |
2988 | } |
2989 | } |
2990 | |
2991 | void CGameContext::ConTuneSetZoneMsgEnter(IConsole::IResult *pResult, void *pUserData) |
2992 | { |
2993 | CGameContext *pSelf = (CGameContext *)pUserData; |
2994 | if(pResult->NumArguments()) |
2995 | { |
2996 | int List = pResult->GetInteger(Index: 0); |
2997 | if(List >= 0 && List < NUM_TUNEZONES) |
2998 | { |
2999 | str_copy(dst: pSelf->m_aaZoneEnterMsg[List], src: pResult->GetString(Index: 1), dst_size: sizeof(pSelf->m_aaZoneEnterMsg[List])); |
3000 | } |
3001 | } |
3002 | } |
3003 | |
3004 | void CGameContext::ConTuneSetZoneMsgLeave(IConsole::IResult *pResult, void *pUserData) |
3005 | { |
3006 | CGameContext *pSelf = (CGameContext *)pUserData; |
3007 | if(pResult->NumArguments()) |
3008 | { |
3009 | int List = pResult->GetInteger(Index: 0); |
3010 | if(List >= 0 && List < NUM_TUNEZONES) |
3011 | { |
3012 | str_copy(dst: pSelf->m_aaZoneLeaveMsg[List], src: pResult->GetString(Index: 1), dst_size: sizeof(pSelf->m_aaZoneLeaveMsg[List])); |
3013 | } |
3014 | } |
3015 | } |
3016 | |
3017 | void CGameContext::ConMapbug(IConsole::IResult *pResult, void *pUserData) |
3018 | { |
3019 | CGameContext *pSelf = (CGameContext *)pUserData; |
3020 | const char *pMapBugName = pResult->GetString(Index: 0); |
3021 | |
3022 | if(pSelf->m_pController) |
3023 | { |
3024 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "mapbugs" , pStr: "can't add map bugs after the game started" ); |
3025 | return; |
3026 | } |
3027 | |
3028 | switch(pSelf->m_MapBugs.Update(pBug: pMapBugName)) |
3029 | { |
3030 | case MAPBUGUPDATE_OK: |
3031 | break; |
3032 | case MAPBUGUPDATE_OVERRIDDEN: |
3033 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "mapbugs" , pStr: "map-internal setting overridden by database" ); |
3034 | break; |
3035 | case MAPBUGUPDATE_NOTFOUND: |
3036 | { |
3037 | char aBuf[64]; |
3038 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "unknown map bug '%s', ignoring" , pMapBugName); |
3039 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "mapbugs" , pStr: aBuf); |
3040 | } |
3041 | break; |
3042 | default: |
3043 | dbg_assert(0, "unreachable" ); |
3044 | } |
3045 | } |
3046 | |
3047 | void CGameContext::ConSwitchOpen(IConsole::IResult *pResult, void *pUserData) |
3048 | { |
3049 | CGameContext *pSelf = (CGameContext *)pUserData; |
3050 | int Switch = pResult->GetInteger(Index: 0); |
3051 | |
3052 | if(in_range(a: Switch, upper: (int)pSelf->Switchers().size() - 1)) |
3053 | { |
3054 | pSelf->Switchers()[Switch].m_Initial = false; |
3055 | char aBuf[256]; |
3056 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "switch %d opened by default" , Switch); |
3057 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3058 | } |
3059 | } |
3060 | |
3061 | void CGameContext::ConPause(IConsole::IResult *pResult, void *pUserData) |
3062 | { |
3063 | CGameContext *pSelf = (CGameContext *)pUserData; |
3064 | |
3065 | pSelf->m_World.m_Paused ^= 1; |
3066 | } |
3067 | |
3068 | void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData) |
3069 | { |
3070 | CGameContext *pSelf = (CGameContext *)pUserData; |
3071 | pSelf->m_pController->ChangeMap(pToMap: pResult->NumArguments() ? pResult->GetString(Index: 0) : "" ); |
3072 | } |
3073 | |
3074 | void CGameContext::ConRandomMap(IConsole::IResult *pResult, void *pUserData) |
3075 | { |
3076 | CGameContext *pSelf = (CGameContext *)pUserData; |
3077 | |
3078 | int Stars = pResult->NumArguments() ? pResult->GetInteger(Index: 0) : -1; |
3079 | |
3080 | pSelf->m_pScore->RandomMap(ClientId: pSelf->m_VoteCreator, Stars); |
3081 | } |
3082 | |
3083 | void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUserData) |
3084 | { |
3085 | CGameContext *pSelf = (CGameContext *)pUserData; |
3086 | |
3087 | int Stars = pResult->NumArguments() ? pResult->GetInteger(Index: 0) : -1; |
3088 | |
3089 | pSelf->m_pScore->RandomUnfinishedMap(ClientId: pSelf->m_VoteCreator, Stars); |
3090 | } |
3091 | |
3092 | void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData) |
3093 | { |
3094 | CGameContext *pSelf = (CGameContext *)pUserData; |
3095 | if(pResult->NumArguments()) |
3096 | pSelf->m_pController->DoWarmup(Seconds: pResult->GetInteger(Index: 0)); |
3097 | else |
3098 | pSelf->m_pController->StartRound(); |
3099 | } |
3100 | |
3101 | void CGameContext::ConBroadcast(IConsole::IResult *pResult, void *pUserData) |
3102 | { |
3103 | CGameContext *pSelf = (CGameContext *)pUserData; |
3104 | |
3105 | char aBuf[1024]; |
3106 | str_copy(dst: aBuf, src: pResult->GetString(Index: 0), dst_size: sizeof(aBuf)); |
3107 | |
3108 | int i, j; |
3109 | for(i = 0, j = 0; aBuf[i]; i++, j++) |
3110 | { |
3111 | if(aBuf[i] == '\\' && aBuf[i + 1] == 'n') |
3112 | { |
3113 | aBuf[j] = '\n'; |
3114 | i++; |
3115 | } |
3116 | else if(i != j) |
3117 | { |
3118 | aBuf[j] = aBuf[i]; |
3119 | } |
3120 | } |
3121 | aBuf[j] = '\0'; |
3122 | |
3123 | pSelf->SendBroadcast(pText: aBuf, ClientId: -1); |
3124 | } |
3125 | |
3126 | void CGameContext::ConSay(IConsole::IResult *pResult, void *pUserData) |
3127 | { |
3128 | CGameContext *pSelf = (CGameContext *)pUserData; |
3129 | pSelf->SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: pResult->GetString(Index: 0)); |
3130 | } |
3131 | |
3132 | void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData) |
3133 | { |
3134 | CGameContext *pSelf = (CGameContext *)pUserData; |
3135 | int ClientId = clamp(val: pResult->GetInteger(Index: 0), lo: 0, hi: (int)MAX_CLIENTS - 1); |
3136 | int Team = clamp(val: pResult->GetInteger(Index: 1), lo: -1, hi: 1); |
3137 | int Delay = pResult->NumArguments() > 2 ? pResult->GetInteger(Index: 2) : 0; |
3138 | if(!pSelf->m_apPlayers[ClientId]) |
3139 | return; |
3140 | |
3141 | char aBuf[256]; |
3142 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "moved client %d to team %d" , ClientId, Team); |
3143 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3144 | |
3145 | pSelf->m_apPlayers[ClientId]->Pause(State: CPlayer::PAUSE_NONE, Force: false); // reset /spec and /pause to allow rejoin |
3146 | pSelf->m_apPlayers[ClientId]->m_TeamChangeTick = pSelf->Server()->Tick() + pSelf->Server()->TickSpeed() * Delay * 60; |
3147 | pSelf->m_pController->DoTeamChange(pPlayer: pSelf->m_apPlayers[ClientId], Team); |
3148 | if(Team == TEAM_SPECTATORS) |
3149 | pSelf->m_apPlayers[ClientId]->Pause(State: CPlayer::PAUSE_NONE, Force: true); |
3150 | } |
3151 | |
3152 | void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData) |
3153 | { |
3154 | CGameContext *pSelf = (CGameContext *)pUserData; |
3155 | int Team = clamp(val: pResult->GetInteger(Index: 0), lo: -1, hi: 1); |
3156 | |
3157 | char aBuf[256]; |
3158 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "All players were moved to the %s" , pSelf->m_pController->GetTeamName(Team)); |
3159 | pSelf->SendChat(ChatterClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf); |
3160 | |
3161 | for(auto &pPlayer : pSelf->m_apPlayers) |
3162 | if(pPlayer) |
3163 | pSelf->m_pController->DoTeamChange(pPlayer, Team, DoChatMsg: false); |
3164 | } |
3165 | |
3166 | void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData) |
3167 | { |
3168 | CGameContext *pSelf = (CGameContext *)pUserData; |
3169 | const char *pDescription = pResult->GetString(Index: 0); |
3170 | const char *pCommand = pResult->GetString(Index: 1); |
3171 | |
3172 | pSelf->AddVote(pDescription, pCommand); |
3173 | } |
3174 | |
3175 | void CGameContext::AddVote(const char *pDescription, const char *pCommand) |
3176 | { |
3177 | if(m_NumVoteOptions == MAX_VOTE_OPTIONS) |
3178 | { |
3179 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: "maximum number of vote options reached" ); |
3180 | return; |
3181 | } |
3182 | |
3183 | // check for valid option |
3184 | if(!Console()->LineIsValid(pStr: pCommand) || str_length(str: pCommand) >= VOTE_CMD_LENGTH) |
3185 | { |
3186 | char aBuf[256]; |
3187 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "skipped invalid command '%s'" , pCommand); |
3188 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3189 | return; |
3190 | } |
3191 | while(*pDescription == ' ') |
3192 | pDescription++; |
3193 | if(str_length(str: pDescription) >= VOTE_DESC_LENGTH || *pDescription == 0) |
3194 | { |
3195 | char aBuf[256]; |
3196 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "skipped invalid option '%s'" , pDescription); |
3197 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3198 | return; |
3199 | } |
3200 | |
3201 | // check for duplicate entry |
3202 | CVoteOptionServer *pOption = m_pVoteOptionFirst; |
3203 | while(pOption) |
3204 | { |
3205 | if(str_comp_nocase(a: pDescription, b: pOption->m_aDescription) == 0) |
3206 | { |
3207 | char aBuf[256]; |
3208 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "option '%s' already exists" , pDescription); |
3209 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3210 | return; |
3211 | } |
3212 | pOption = pOption->m_pNext; |
3213 | } |
3214 | |
3215 | // add the option |
3216 | ++m_NumVoteOptions; |
3217 | int Len = str_length(str: pCommand); |
3218 | |
3219 | pOption = (CVoteOptionServer *)m_pVoteOptionHeap->Allocate(Size: sizeof(CVoteOptionServer) + Len, Alignment: alignof(CVoteOptionServer)); |
3220 | pOption->m_pNext = 0; |
3221 | pOption->m_pPrev = m_pVoteOptionLast; |
3222 | if(pOption->m_pPrev) |
3223 | pOption->m_pPrev->m_pNext = pOption; |
3224 | m_pVoteOptionLast = pOption; |
3225 | if(!m_pVoteOptionFirst) |
3226 | m_pVoteOptionFirst = pOption; |
3227 | |
3228 | str_copy(dst: pOption->m_aDescription, src: pDescription, dst_size: sizeof(pOption->m_aDescription)); |
3229 | mem_copy(dest: pOption->m_aCommand, source: pCommand, size: Len + 1); |
3230 | } |
3231 | |
3232 | void CGameContext::ConRemoveVote(IConsole::IResult *pResult, void *pUserData) |
3233 | { |
3234 | CGameContext *pSelf = (CGameContext *)pUserData; |
3235 | const char *pDescription = pResult->GetString(Index: 0); |
3236 | |
3237 | // check for valid option |
3238 | CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst; |
3239 | while(pOption) |
3240 | { |
3241 | if(str_comp_nocase(a: pDescription, b: pOption->m_aDescription) == 0) |
3242 | break; |
3243 | pOption = pOption->m_pNext; |
3244 | } |
3245 | if(!pOption) |
3246 | { |
3247 | char aBuf[256]; |
3248 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "option '%s' does not exist" , pDescription); |
3249 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3250 | return; |
3251 | } |
3252 | |
3253 | // start reloading vote option list |
3254 | // clear vote options |
3255 | CNetMsg_Sv_VoteClearOptions VoteClearOptionsMsg; |
3256 | pSelf->Server()->SendPackMsg(pMsg: &VoteClearOptionsMsg, Flags: MSGFLAG_VITAL, ClientId: -1); |
3257 | |
3258 | // reset sending of vote options |
3259 | for(auto &pPlayer : pSelf->m_apPlayers) |
3260 | { |
3261 | if(pPlayer) |
3262 | pPlayer->m_SendVoteIndex = 0; |
3263 | } |
3264 | |
3265 | // TODO: improve this |
3266 | // remove the option |
3267 | --pSelf->m_NumVoteOptions; |
3268 | |
3269 | CHeap *pVoteOptionHeap = new CHeap(); |
3270 | CVoteOptionServer *pVoteOptionFirst = 0; |
3271 | CVoteOptionServer *pVoteOptionLast = 0; |
3272 | int NumVoteOptions = pSelf->m_NumVoteOptions; |
3273 | for(CVoteOptionServer *pSrc = pSelf->m_pVoteOptionFirst; pSrc; pSrc = pSrc->m_pNext) |
3274 | { |
3275 | if(pSrc == pOption) |
3276 | continue; |
3277 | |
3278 | // copy option |
3279 | int Len = str_length(str: pSrc->m_aCommand); |
3280 | CVoteOptionServer *pDst = (CVoteOptionServer *)pVoteOptionHeap->Allocate(Size: sizeof(CVoteOptionServer) + Len); |
3281 | pDst->m_pNext = 0; |
3282 | pDst->m_pPrev = pVoteOptionLast; |
3283 | if(pDst->m_pPrev) |
3284 | pDst->m_pPrev->m_pNext = pDst; |
3285 | pVoteOptionLast = pDst; |
3286 | if(!pVoteOptionFirst) |
3287 | pVoteOptionFirst = pDst; |
3288 | |
3289 | str_copy(dst: pDst->m_aDescription, src: pSrc->m_aDescription, dst_size: sizeof(pDst->m_aDescription)); |
3290 | mem_copy(dest: pDst->m_aCommand, source: pSrc->m_aCommand, size: Len + 1); |
3291 | } |
3292 | |
3293 | // clean up |
3294 | delete pSelf->m_pVoteOptionHeap; |
3295 | pSelf->m_pVoteOptionHeap = pVoteOptionHeap; |
3296 | pSelf->m_pVoteOptionFirst = pVoteOptionFirst; |
3297 | pSelf->m_pVoteOptionLast = pVoteOptionLast; |
3298 | pSelf->m_NumVoteOptions = NumVoteOptions; |
3299 | } |
3300 | |
3301 | void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData) |
3302 | { |
3303 | CGameContext *pSelf = (CGameContext *)pUserData; |
3304 | const char *pType = pResult->GetString(Index: 0); |
3305 | const char *pValue = pResult->GetString(Index: 1); |
3306 | const char *pReason = pResult->NumArguments() > 2 && pResult->GetString(Index: 2)[0] ? pResult->GetString(Index: 2) : "No reason given" ; |
3307 | char aBuf[128] = {0}; |
3308 | |
3309 | if(str_comp_nocase(a: pType, b: "option" ) == 0) |
3310 | { |
3311 | CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst; |
3312 | while(pOption) |
3313 | { |
3314 | if(str_comp_nocase(a: pValue, b: pOption->m_aDescription) == 0) |
3315 | { |
3316 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "authorized player forced server option '%s' (%s)" , pValue, pReason); |
3317 | pSelf->SendChatTarget(To: -1, pText: aBuf, Flags: CHAT_SIX); |
3318 | pSelf->Console()->ExecuteLine(pStr: pOption->m_aCommand); |
3319 | break; |
3320 | } |
3321 | |
3322 | pOption = pOption->m_pNext; |
3323 | } |
3324 | |
3325 | if(!pOption) |
3326 | { |
3327 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' isn't an option on this server" , pValue); |
3328 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
3329 | return; |
3330 | } |
3331 | } |
3332 | else if(str_comp_nocase(a: pType, b: "kick" ) == 0) |
3333 | { |
3334 | int KickId = str_toint(str: pValue); |
3335 | if(KickId < 0 || KickId >= MAX_CLIENTS || !pSelf->m_apPlayers[KickId]) |
3336 | { |
3337 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: "Invalid client id to kick" ); |
3338 | return; |
3339 | } |
3340 | |
3341 | if(!g_Config.m_SvVoteKickBantime) |
3342 | { |
3343 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "kick %d %s" , KickId, pReason); |
3344 | pSelf->Console()->ExecuteLine(pStr: aBuf); |
3345 | } |
3346 | else |
3347 | { |
3348 | char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; |
3349 | pSelf->Server()->GetClientAddr(ClientId: KickId, pAddrStr: aAddrStr, Size: sizeof(aAddrStr)); |
3350 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban %s %d %s" , aAddrStr, g_Config.m_SvVoteKickBantime, pReason); |
3351 | pSelf->Console()->ExecuteLine(pStr: aBuf); |
3352 | } |
3353 | } |
3354 | else if(str_comp_nocase(a: pType, b: "spectate" ) == 0) |
3355 | { |
3356 | int SpectateId = str_toint(str: pValue); |
3357 | if(SpectateId < 0 || SpectateId >= MAX_CLIENTS || !pSelf->m_apPlayers[SpectateId] || pSelf->m_apPlayers[SpectateId]->GetTeam() == TEAM_SPECTATORS) |
3358 | { |
3359 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: "Invalid client id to move" ); |
3360 | return; |
3361 | } |
3362 | |
3363 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' was moved to spectator (%s)" , pSelf->Server()->ClientName(ClientId: SpectateId), pReason); |
3364 | pSelf->SendChatTarget(To: -1, pText: aBuf); |
3365 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "set_team %d -1 %d" , SpectateId, g_Config.m_SvVoteSpectateRejoindelay); |
3366 | pSelf->Console()->ExecuteLine(pStr: aBuf); |
3367 | } |
3368 | } |
3369 | |
3370 | void CGameContext::ConClearVotes(IConsole::IResult *pResult, void *pUserData) |
3371 | { |
3372 | CGameContext *pSelf = (CGameContext *)pUserData; |
3373 | |
3374 | CNetMsg_Sv_VoteClearOptions VoteClearOptionsMsg; |
3375 | pSelf->Server()->SendPackMsg(pMsg: &VoteClearOptionsMsg, Flags: MSGFLAG_VITAL, ClientId: -1); |
3376 | pSelf->m_pVoteOptionHeap->Reset(); |
3377 | pSelf->m_pVoteOptionFirst = 0; |
3378 | pSelf->m_pVoteOptionLast = 0; |
3379 | pSelf->m_NumVoteOptions = 0; |
3380 | |
3381 | // reset sending of vote options |
3382 | for(auto &pPlayer : pSelf->m_apPlayers) |
3383 | { |
3384 | if(pPlayer) |
3385 | pPlayer->m_SendVoteIndex = 0; |
3386 | } |
3387 | } |
3388 | |
3389 | struct CMapNameItem |
3390 | { |
3391 | char m_aName[IO_MAX_PATH_LENGTH - 4]; |
3392 | |
3393 | bool operator<(const CMapNameItem &Other) const { return str_comp_nocase(a: m_aName, b: Other.m_aName) < 0; } |
3394 | }; |
3395 | |
3396 | void CGameContext::ConAddMapVotes(IConsole::IResult *pResult, void *pUserData) |
3397 | { |
3398 | CGameContext *pSelf = (CGameContext *)pUserData; |
3399 | |
3400 | std::vector<CMapNameItem> vMapList; |
3401 | pSelf->Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "maps" , pfnCallback: MapScan, pUser: &vMapList); |
3402 | std::sort(first: vMapList.begin(), last: vMapList.end()); |
3403 | |
3404 | for(auto &Item : vMapList) |
3405 | { |
3406 | char aDescription[64]; |
3407 | str_format(buffer: aDescription, buffer_size: sizeof(aDescription), format: "Map: %s" , Item.m_aName); |
3408 | |
3409 | char aCommand[IO_MAX_PATH_LENGTH * 2 + 10]; |
3410 | char aMapEscaped[IO_MAX_PATH_LENGTH * 2]; |
3411 | char *pDst = aMapEscaped; |
3412 | str_escape(dst: &pDst, src: Item.m_aName, end: aMapEscaped + sizeof(aMapEscaped)); |
3413 | str_format(buffer: aCommand, buffer_size: sizeof(aCommand), format: "change_map \"%s\"" , aMapEscaped); |
3414 | |
3415 | pSelf->AddVote(pDescription: aDescription, pCommand: aCommand); |
3416 | } |
3417 | |
3418 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: "added maps to votes" ); |
3419 | } |
3420 | |
3421 | int CGameContext::MapScan(const char *pName, int IsDir, int DirType, void *pUserData) |
3422 | { |
3423 | if(IsDir || !str_endswith(str: pName, suffix: ".map" )) |
3424 | return 0; |
3425 | |
3426 | CMapNameItem Item; |
3427 | str_truncate(dst: Item.m_aName, dst_size: sizeof(Item.m_aName), src: pName, truncation_len: str_length(str: pName) - str_length(str: ".map" )); |
3428 | static_cast<std::vector<CMapNameItem> *>(pUserData)->push_back(x: Item); |
3429 | |
3430 | return 0; |
3431 | } |
3432 | |
3433 | void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData) |
3434 | { |
3435 | CGameContext *pSelf = (CGameContext *)pUserData; |
3436 | |
3437 | if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "yes" ) == 0) |
3438 | pSelf->ForceVote(EnforcerId: pResult->m_ClientId, Success: true); |
3439 | else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "no" ) == 0) |
3440 | pSelf->ForceVote(EnforcerId: pResult->m_ClientId, Success: false); |
3441 | } |
3442 | |
3443 | void CGameContext::ConVotes(IConsole::IResult *pResult, void *pUserData) |
3444 | { |
3445 | CGameContext *pSelf = (CGameContext *)pUserData; |
3446 | |
3447 | int Page = pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 0; |
3448 | static const int s_EntriesPerPage = 20; |
3449 | const int Start = Page * s_EntriesPerPage; |
3450 | const int End = (Page + 1) * s_EntriesPerPage; |
3451 | |
3452 | char aBuf[512]; |
3453 | int Count = 0; |
3454 | for(CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst; pOption; pOption = pOption->m_pNext, Count++) |
3455 | { |
3456 | if(Count < Start || Count >= End) |
3457 | { |
3458 | continue; |
3459 | } |
3460 | |
3461 | str_copy(dst&: aBuf, src: "add_vote \"" ); |
3462 | char *pDst = aBuf + str_length(str: aBuf); |
3463 | str_escape(dst: &pDst, src: pOption->m_aDescription, end: aBuf + sizeof(aBuf)); |
3464 | str_append(dst&: aBuf, src: "\" \"" ); |
3465 | pDst = aBuf + str_length(str: aBuf); |
3466 | str_escape(dst: &pDst, src: pOption->m_aCommand, end: aBuf + sizeof(aBuf)); |
3467 | str_append(dst&: aBuf, src: "\"" ); |
3468 | |
3469 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "votes" , pStr: aBuf); |
3470 | } |
3471 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d %s, showing entries %d - %d" , Count, Count == 1 ? "vote" : "votes" , Start, End - 1); |
3472 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "votes" , pStr: aBuf); |
3473 | } |
3474 | |
3475 | void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) |
3476 | { |
3477 | pfnCallback(pResult, pCallbackUserData); |
3478 | if(pResult->NumArguments()) |
3479 | { |
3480 | CGameContext *pSelf = (CGameContext *)pUserData; |
3481 | pSelf->SendMotd(ClientId: -1); |
3482 | } |
3483 | } |
3484 | |
3485 | void CGameContext::ConchainSettingUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) |
3486 | { |
3487 | pfnCallback(pResult, pCallbackUserData); |
3488 | if(pResult->NumArguments()) |
3489 | { |
3490 | CGameContext *pSelf = (CGameContext *)pUserData; |
3491 | pSelf->SendSettings(ClientId: -1); |
3492 | } |
3493 | } |
3494 | |
3495 | void CGameContext::OnConsoleInit() |
3496 | { |
3497 | m_pServer = Kernel()->RequestInterface<IServer>(); |
3498 | m_pConfigManager = Kernel()->RequestInterface<IConfigManager>(); |
3499 | m_pConfig = m_pConfigManager->Values(); |
3500 | m_pConsole = Kernel()->RequestInterface<IConsole>(); |
3501 | m_pEngine = Kernel()->RequestInterface<IEngine>(); |
3502 | m_pStorage = Kernel()->RequestInterface<IStorage>(); |
3503 | |
3504 | Console()->Register(pName: "tune" , pParams: "s[tuning] ?f[value]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConTuneParam, pUser: this, pHelp: "Tune variable to value or show current value" ); |
3505 | Console()->Register(pName: "toggle_tune" , pParams: "s[tuning] f[value 1] f[value 2]" , Flags: CFGFLAG_SERVER, pfnFunc: ConToggleTuneParam, pUser: this, pHelp: "Toggle tune variable" ); |
3506 | Console()->Register(pName: "tune_reset" , pParams: "?s[tuning]" , Flags: CFGFLAG_SERVER, pfnFunc: ConTuneReset, pUser: this, pHelp: "Reset all or one tuning variable to default" ); |
3507 | Console()->Register(pName: "tunes" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConTunes, pUser: this, pHelp: "List all tuning variables and their values" ); |
3508 | Console()->Register(pName: "tune_zone" , pParams: "i[zone] s[tuning] f[value]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConTuneZone, pUser: this, pHelp: "Tune in zone a variable to value" ); |
3509 | Console()->Register(pName: "tune_zone_dump" , pParams: "i[zone]" , Flags: CFGFLAG_SERVER, pfnFunc: ConTuneDumpZone, pUser: this, pHelp: "Dump zone tuning in zone x" ); |
3510 | Console()->Register(pName: "tune_zone_reset" , pParams: "?i[zone]" , Flags: CFGFLAG_SERVER, pfnFunc: ConTuneResetZone, pUser: this, pHelp: "Reset zone tuning in zone x or in all zones" ); |
3511 | Console()->Register(pName: "tune_zone_enter" , pParams: "i[zone] r[message]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConTuneSetZoneMsgEnter, pUser: this, pHelp: "Which message to display on zone enter; use 0 for normal area" ); |
3512 | Console()->Register(pName: "tune_zone_leave" , pParams: "i[zone] r[message]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConTuneSetZoneMsgLeave, pUser: this, pHelp: "Which message to display on zone leave; use 0 for normal area" ); |
3513 | Console()->Register(pName: "mapbug" , pParams: "s[mapbug]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConMapbug, pUser: this, pHelp: "Enable map compatibility mode using the specified bug (example: grenade-doubleexplosion@ddnet.tw)" ); |
3514 | Console()->Register(pName: "switch_open" , pParams: "i[switch]" , Flags: CFGFLAG_SERVER | CFGFLAG_GAME, pfnFunc: ConSwitchOpen, pUser: this, pHelp: "Whether a switch is deactivated by default (otherwise activated)" ); |
3515 | Console()->Register(pName: "pause_game" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConPause, pUser: this, pHelp: "Pause/unpause game" ); |
3516 | Console()->Register(pName: "change_map" , pParams: "?r[map]" , Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConChangeMap, pUser: this, pHelp: "Change map" ); |
3517 | Console()->Register(pName: "random_map" , pParams: "?i[stars]" , Flags: CFGFLAG_SERVER, pfnFunc: ConRandomMap, pUser: this, pHelp: "Random map" ); |
3518 | Console()->Register(pName: "random_unfinished_map" , pParams: "?i[stars]" , Flags: CFGFLAG_SERVER, pfnFunc: ConRandomUnfinishedMap, pUser: this, pHelp: "Random unfinished map" ); |
3519 | Console()->Register(pName: "restart" , pParams: "?i[seconds]" , Flags: CFGFLAG_SERVER | CFGFLAG_STORE, pfnFunc: ConRestart, pUser: this, pHelp: "Restart in x seconds (0 = abort)" ); |
3520 | Console()->Register(pName: "broadcast" , pParams: "r[message]" , Flags: CFGFLAG_SERVER, pfnFunc: ConBroadcast, pUser: this, pHelp: "Broadcast message" ); |
3521 | Console()->Register(pName: "say" , pParams: "r[message]" , Flags: CFGFLAG_SERVER, pfnFunc: ConSay, pUser: this, pHelp: "Say in chat" ); |
3522 | Console()->Register(pName: "set_team" , pParams: "i[id] i[team-id] ?i[delay in minutes]" , Flags: CFGFLAG_SERVER, pfnFunc: ConSetTeam, pUser: this, pHelp: "Set team of player to team" ); |
3523 | Console()->Register(pName: "set_team_all" , pParams: "i[team-id]" , Flags: CFGFLAG_SERVER, pfnFunc: ConSetTeamAll, pUser: this, pHelp: "Set team of all players to team" ); |
3524 | |
3525 | Console()->Register(pName: "add_vote" , pParams: "s[name] r[command]" , Flags: CFGFLAG_SERVER, pfnFunc: ConAddVote, pUser: this, pHelp: "Add a voting option" ); |
3526 | Console()->Register(pName: "remove_vote" , pParams: "r[name]" , Flags: CFGFLAG_SERVER, pfnFunc: ConRemoveVote, pUser: this, pHelp: "remove a voting option" ); |
3527 | Console()->Register(pName: "force_vote" , pParams: "s[name] s[command] ?r[reason]" , Flags: CFGFLAG_SERVER, pfnFunc: ConForceVote, pUser: this, pHelp: "Force a voting option" ); |
3528 | Console()->Register(pName: "clear_votes" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConClearVotes, pUser: this, pHelp: "Clears the voting options" ); |
3529 | Console()->Register(pName: "add_map_votes" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConAddMapVotes, pUser: this, pHelp: "Automatically adds voting options for all maps" ); |
3530 | Console()->Register(pName: "vote" , pParams: "r['yes'|'no']" , Flags: CFGFLAG_SERVER, pfnFunc: ConVote, pUser: this, pHelp: "Force a vote to yes/no" ); |
3531 | Console()->Register(pName: "votes" , pParams: "?i[page]" , Flags: CFGFLAG_SERVER, pfnFunc: ConVotes, pUser: this, pHelp: "Show all votes (page 0 by default, 20 entries per page)" ); |
3532 | Console()->Register(pName: "dump_antibot" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConDumpAntibot, pUser: this, pHelp: "Dumps the antibot status" ); |
3533 | Console()->Register(pName: "antibot" , pParams: "r[command]" , Flags: CFGFLAG_SERVER, pfnFunc: ConAntibot, pUser: this, pHelp: "Sends a command to the antibot" ); |
3534 | |
3535 | Console()->Chain(pName: "sv_motd" , pfnChainFunc: ConchainSpecialMotdupdate, pUser: this); |
3536 | |
3537 | Console()->Chain(pName: "sv_vote_kick" , pfnChainFunc: ConchainSettingUpdate, pUser: this); |
3538 | Console()->Chain(pName: "sv_vote_kick_min" , pfnChainFunc: ConchainSettingUpdate, pUser: this); |
3539 | Console()->Chain(pName: "sv_vote_spectate" , pfnChainFunc: ConchainSettingUpdate, pUser: this); |
3540 | Console()->Chain(pName: "sv_spectator_slots" , pfnChainFunc: ConchainSettingUpdate, pUser: this); |
3541 | Console()->Chain(pName: "sv_max_clients" , pfnChainFunc: ConchainSettingUpdate, pUser: this); |
3542 | |
3543 | RegisterDDRaceCommands(); |
3544 | RegisterChatCommands(); |
3545 | } |
3546 | |
3547 | void CGameContext::RegisterDDRaceCommands() |
3548 | { |
3549 | Console()->Register(pName: "kill_pl" , pParams: "v[id]" , Flags: CFGFLAG_SERVER, pfnFunc: ConKillPlayer, pUser: this, pHelp: "Kills player v and announces the kill" ); |
3550 | Console()->Register(pName: "totele" , pParams: "i[number]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConToTeleporter, pUser: this, pHelp: "Teleports you to teleporter v" ); |
3551 | Console()->Register(pName: "totelecp" , pParams: "i[number]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConToCheckTeleporter, pUser: this, pHelp: "Teleports you to checkpoint teleporter v" ); |
3552 | Console()->Register(pName: "tele" , pParams: "?i[id] ?i[id]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConTeleport, pUser: this, pHelp: "Teleports player i (or you) to player i (or you to where you look at)" ); |
3553 | Console()->Register(pName: "addweapon" , pParams: "i[weapon-id]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConAddWeapon, pUser: this, pHelp: "Gives weapon with id i to you (all = -1, hammer = 0, gun = 1, shotgun = 2, grenade = 3, laser = 4, ninja = 5)" ); |
3554 | Console()->Register(pName: "removeweapon" , pParams: "i[weapon-id]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConRemoveWeapon, pUser: this, pHelp: "removes weapon with id i from you (all = -1, hammer = 0, gun = 1, shotgun = 2, grenade = 3, laser = 4, ninja = 5)" ); |
3555 | Console()->Register(pName: "shotgun" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConShotgun, pUser: this, pHelp: "Gives a shotgun to you" ); |
3556 | Console()->Register(pName: "grenade" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConGrenade, pUser: this, pHelp: "Gives a grenade launcher to you" ); |
3557 | Console()->Register(pName: "laser" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConLaser, pUser: this, pHelp: "Gives a laser to you" ); |
3558 | Console()->Register(pName: "rifle" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConLaser, pUser: this, pHelp: "Gives a laser to you" ); |
3559 | Console()->Register(pName: "jetpack" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConJetpack, pUser: this, pHelp: "Gives jetpack to you" ); |
3560 | Console()->Register(pName: "weapons" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConWeapons, pUser: this, pHelp: "Gives all weapons to you" ); |
3561 | Console()->Register(pName: "unshotgun" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnShotgun, pUser: this, pHelp: "Removes the shotgun from you" ); |
3562 | Console()->Register(pName: "ungrenade" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnGrenade, pUser: this, pHelp: "Removes the grenade launcher from you" ); |
3563 | Console()->Register(pName: "unlaser" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnLaser, pUser: this, pHelp: "Removes the laser from you" ); |
3564 | Console()->Register(pName: "unrifle" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnLaser, pUser: this, pHelp: "Removes the laser from you" ); |
3565 | Console()->Register(pName: "unjetpack" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnJetpack, pUser: this, pHelp: "Removes the jetpack from you" ); |
3566 | Console()->Register(pName: "unweapons" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnWeapons, pUser: this, pHelp: "Removes all weapons from you" ); |
3567 | Console()->Register(pName: "ninja" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConNinja, pUser: this, pHelp: "Makes you a ninja" ); |
3568 | Console()->Register(pName: "unninja" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnNinja, pUser: this, pHelp: "Removes ninja from you" ); |
3569 | Console()->Register(pName: "super" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConSuper, pUser: this, pHelp: "Makes you super" ); |
3570 | Console()->Register(pName: "unsuper" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConUnSuper, pUser: this, pHelp: "Removes super from you" ); |
3571 | Console()->Register(pName: "endless_hook" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConEndlessHook, pUser: this, pHelp: "Gives you endless hook" ); |
3572 | Console()->Register(pName: "unendless_hook" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnEndlessHook, pUser: this, pHelp: "Removes endless hook from you" ); |
3573 | Console()->Register(pName: "solo" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConSolo, pUser: this, pHelp: "Puts you into solo part" ); |
3574 | Console()->Register(pName: "unsolo" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnSolo, pUser: this, pHelp: "Puts you out of solo part" ); |
3575 | Console()->Register(pName: "freeze" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConFreeze, pUser: this, pHelp: "Puts you into freeze" ); |
3576 | Console()->Register(pName: "unfreeze" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnFreeze, pUser: this, pHelp: "Puts you out of freeze" ); |
3577 | Console()->Register(pName: "deep" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConDeep, pUser: this, pHelp: "Puts you into deep freeze" ); |
3578 | Console()->Register(pName: "undeep" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnDeep, pUser: this, pHelp: "Puts you out of deep freeze" ); |
3579 | Console()->Register(pName: "livefreeze" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConLiveFreeze, pUser: this, pHelp: "Makes you live frozen" ); |
3580 | Console()->Register(pName: "unlivefreeze" , pParams: "" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnLiveFreeze, pUser: this, pHelp: "Puts you out of live freeze" ); |
3581 | Console()->Register(pName: "left" , pParams: "?i[tiles]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConGoLeft, pUser: this, pHelp: "Makes you move 1 tile left" ); |
3582 | Console()->Register(pName: "right" , pParams: "?i[tiles]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConGoRight, pUser: this, pHelp: "Makes you move 1 tile right" ); |
3583 | Console()->Register(pName: "up" , pParams: "?i[tiles]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConGoUp, pUser: this, pHelp: "Makes you move 1 tile up" ); |
3584 | Console()->Register(pName: "down" , pParams: "?i[tiles]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConGoDown, pUser: this, pHelp: "Makes you move 1 tile down" ); |
3585 | |
3586 | Console()->Register(pName: "move" , pParams: "i[x] i[y]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConMove, pUser: this, pHelp: "Moves to the tile with x/y-number ii" ); |
3587 | Console()->Register(pName: "move_raw" , pParams: "i[x] i[y]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConMoveRaw, pUser: this, pHelp: "Moves to the point with x/y-coordinates ii" ); |
3588 | Console()->Register(pName: "force_pause" , pParams: "v[id] i[seconds]" , Flags: CFGFLAG_SERVER, pfnFunc: ConForcePause, pUser: this, pHelp: "Force i to pause for i seconds" ); |
3589 | Console()->Register(pName: "force_unpause" , pParams: "v[id]" , Flags: CFGFLAG_SERVER, pfnFunc: ConForcePause, pUser: this, pHelp: "Set force-pause timer of i to 0." ); |
3590 | |
3591 | Console()->Register(pName: "set_team_ddr" , pParams: "v[id] i[team]" , Flags: CFGFLAG_SERVER, pfnFunc: ConSetDDRTeam, pUser: this, pHelp: "Set ddrace team of a player" ); |
3592 | Console()->Register(pName: "uninvite" , pParams: "v[id] i[team]" , Flags: CFGFLAG_SERVER, pfnFunc: ConUninvite, pUser: this, pHelp: "Uninvite player from team" ); |
3593 | |
3594 | Console()->Register(pName: "vote_mute" , pParams: "v[id] i[seconds] ?r[reason]" , Flags: CFGFLAG_SERVER, pfnFunc: ConVoteMute, pUser: this, pHelp: "Remove v's right to vote for i seconds" ); |
3595 | Console()->Register(pName: "vote_unmute" , pParams: "v[id]" , Flags: CFGFLAG_SERVER, pfnFunc: ConVoteUnmute, pUser: this, pHelp: "Give back v's right to vote." ); |
3596 | Console()->Register(pName: "vote_mutes" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConVoteMutes, pUser: this, pHelp: "List the current active vote mutes." ); |
3597 | Console()->Register(pName: "mute" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConMute, pUser: this, pHelp: "Use either 'muteid <client_id> <seconds> <reason>' or 'muteip <ip> <seconds> <reason>'" ); |
3598 | Console()->Register(pName: "muteid" , pParams: "v[id] i[seconds] ?r[reason]" , Flags: CFGFLAG_SERVER, pfnFunc: ConMuteId, pUser: this, pHelp: "Mute player with id" ); |
3599 | Console()->Register(pName: "muteip" , pParams: "s[ip] i[seconds] ?r[reason]" , Flags: CFGFLAG_SERVER, pfnFunc: ConMuteIp, pUser: this, pHelp: "Mute player with IP address" ); |
3600 | Console()->Register(pName: "unmute" , pParams: "i[muteid]" , Flags: CFGFLAG_SERVER, pfnFunc: ConUnmute, pUser: this, pHelp: "Unmute mute with number from \"mutes\"" ); |
3601 | Console()->Register(pName: "unmuteid" , pParams: "v[id]" , Flags: CFGFLAG_SERVER, pfnFunc: ConUnmuteId, pUser: this, pHelp: "Unmute player with id" ); |
3602 | Console()->Register(pName: "mutes" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConMutes, pUser: this, pHelp: "Show all active mutes" ); |
3603 | Console()->Register(pName: "moderate" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConModerate, pUser: this, pHelp: "Enables/disables active moderator mode for the player" ); |
3604 | Console()->Register(pName: "vote_no" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConVoteNo, pUser: this, pHelp: "Same as \"vote no\"" ); |
3605 | Console()->Register(pName: "save_dry" , pParams: "" , Flags: CFGFLAG_SERVER, pfnFunc: ConDrySave, pUser: this, pHelp: "Dump the current savestring" ); |
3606 | Console()->Register(pName: "dump_log" , pParams: "?i[seconds]" , Flags: CFGFLAG_SERVER, pfnFunc: ConDumpLog, pUser: this, pHelp: "Show logs of the last i seconds" ); |
3607 | |
3608 | Console()->Register(pName: "freezehammer" , pParams: "v[id]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConFreezeHammer, pUser: this, pHelp: "Gives a player Freeze Hammer" ); |
3609 | Console()->Register(pName: "unfreezehammer" , pParams: "v[id]" , Flags: CFGFLAG_SERVER | CMDFLAG_TEST, pfnFunc: ConUnFreezeHammer, pUser: this, pHelp: "Removes Freeze Hammer from a player" ); |
3610 | } |
3611 | |
3612 | void CGameContext::RegisterChatCommands() |
3613 | { |
3614 | Console()->Register(pName: "credits" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConCredits, pUser: this, pHelp: "Shows the credits of the DDNet mod" ); |
3615 | Console()->Register(pName: "rules" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConRules, pUser: this, pHelp: "Shows the server rules" ); |
3616 | Console()->Register(pName: "emote" , pParams: "?s[emote name] i[duration in seconds]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConEyeEmote, pUser: this, pHelp: "Sets your tee's eye emote" ); |
3617 | Console()->Register(pName: "eyeemote" , pParams: "?s['on'|'off'|'toggle']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSetEyeEmote, pUser: this, pHelp: "Toggles use of standard eye-emotes on/off, eyeemote s, where s = on for on, off for off, toggle for toggle and nothing to show current status" ); |
3618 | Console()->Register(pName: "settings" , pParams: "?s[configname]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSettings, pUser: this, pHelp: "Shows gameplay information for this server" ); |
3619 | Console()->Register(pName: "help" , pParams: "?r[command]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConHelp, pUser: this, pHelp: "Shows help to command r, general help if left blank" ); |
3620 | Console()->Register(pName: "info" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConInfo, pUser: this, pHelp: "Shows info about this server" ); |
3621 | Console()->Register(pName: "list" , pParams: "?s[filter]" , Flags: CFGFLAG_CHAT, pfnFunc: ConList, pUser: this, pHelp: "List connected players with optional case-insensitive substring matching filter" ); |
3622 | Console()->Register(pName: "me" , pParams: "r[message]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConMe, pUser: this, pHelp: "Like the famous irc command '/me says hi' will display '<yourname> says hi'" ); |
3623 | Console()->Register(pName: "w" , pParams: "s[player name] r[message]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConWhisper, pUser: this, pHelp: "Whisper something to someone (private message)" ); |
3624 | Console()->Register(pName: "whisper" , pParams: "s[player name] r[message]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConWhisper, pUser: this, pHelp: "Whisper something to someone (private message)" ); |
3625 | Console()->Register(pName: "c" , pParams: "r[message]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConConverse, pUser: this, pHelp: "Converse with the last person you whispered to (private message)" ); |
3626 | Console()->Register(pName: "converse" , pParams: "r[message]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConConverse, pUser: this, pHelp: "Converse with the last person you whispered to (private message)" ); |
3627 | Console()->Register(pName: "pause" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTogglePause, pUser: this, pHelp: "Toggles pause" ); |
3628 | Console()->Register(pName: "spec" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConToggleSpec, pUser: this, pHelp: "Toggles spec (if not available behaves as /pause)" ); |
3629 | Console()->Register(pName: "pausevoted" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTogglePauseVoted, pUser: this, pHelp: "Toggles pause on the currently voted player" ); |
3630 | Console()->Register(pName: "specvoted" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConToggleSpecVoted, pUser: this, pHelp: "Toggles spec on the currently voted player" ); |
3631 | Console()->Register(pName: "dnd" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConDND, pUser: this, pHelp: "Toggle Do Not Disturb (no chat and server messages)" ); |
3632 | Console()->Register(pName: "mapinfo" , pParams: "?r[map]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConMapInfo, pUser: this, pHelp: "Show info about the map with name r gives (current map by default)" ); |
3633 | Console()->Register(pName: "timeout" , pParams: "?s[code]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTimeout, pUser: this, pHelp: "Set timeout protection code s" ); |
3634 | Console()->Register(pName: "practice" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConPractice, pUser: this, pHelp: "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank" ); |
3635 | Console()->Register(pName: "swap" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSwap, pUser: this, pHelp: "Request to swap your tee with another team member" ); |
3636 | Console()->Register(pName: "save" , pParams: "?r[code]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSave, pUser: this, pHelp: "Save team with code r." ); |
3637 | Console()->Register(pName: "load" , pParams: "?r[code]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConLoad, pUser: this, pHelp: "Load with code r. /load to check your existing saves" ); |
3638 | Console()->Register(pName: "map" , pParams: "?r[map]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConMap, pUser: this, pHelp: "Vote a map by name" ); |
3639 | |
3640 | Console()->Register(pName: "rankteam" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeamRank, pUser: this, pHelp: "Shows the team rank of player with name r (your team rank by default)" ); |
3641 | Console()->Register(pName: "teamrank" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeamRank, pUser: this, pHelp: "Shows the team rank of player with name r (your team rank by default)" ); |
3642 | |
3643 | Console()->Register(pName: "rank" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConRank, pUser: this, pHelp: "Shows the rank of player with name r (your rank by default)" ); |
3644 | Console()->Register(pName: "top5team" , pParams: "?s[player name] ?i[rank to start with]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeamTop5, pUser: this, pHelp: "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)" ); |
3645 | Console()->Register(pName: "teamtop5" , pParams: "?s[player name] ?i[rank to start with]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeamTop5, pUser: this, pHelp: "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)" ); |
3646 | Console()->Register(pName: "top" , pParams: "?i[rank to start with]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTop, pUser: this, pHelp: "Shows the top ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)" ); |
3647 | Console()->Register(pName: "top5" , pParams: "?i[rank to start with]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTop, pUser: this, pHelp: "Shows the top ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)" ); |
3648 | Console()->Register(pName: "times" , pParams: "?s[player name] ?i[number of times to skip]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTimes, pUser: this, pHelp: "/times ?s?i shows last 5 times of the server or of a player beginning with name s starting with time i (i = 1 by default, -1 for first)" ); |
3649 | Console()->Register(pName: "points" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConPoints, pUser: this, pHelp: "Shows the global points of a player beginning with name r (your rank by default)" ); |
3650 | Console()->Register(pName: "top5points" , pParams: "?i[number]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTopPoints, pUser: this, pHelp: "Shows five points of the global point ladder beginning with rank i (1 by default)" ); |
3651 | Console()->Register(pName: "timecp" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTimeCP, pUser: this, pHelp: "Set your checkpoints based on another player" ); |
3652 | |
3653 | Console()->Register(pName: "team" , pParams: "?i[id]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeam, pUser: this, pHelp: "Lets you join team i (shows your team if left blank)" ); |
3654 | Console()->Register(pName: "lock" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConLock, pUser: this, pHelp: "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock" ); |
3655 | Console()->Register(pName: "unlock" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConUnlock, pUser: this, pHelp: "Unlock a team" ); |
3656 | Console()->Register(pName: "invite" , pParams: "r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConInvite, pUser: this, pHelp: "Invite a person to a locked team" ); |
3657 | Console()->Register(pName: "join" , pParams: "r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConJoin, pUser: this, pHelp: "Join the team of the specified player" ); |
3658 | Console()->Register(pName: "team0mode" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTeam0Mode, pUser: this, pHelp: "Toggle team between team 0 and team mode. This mode will make your team behave like team 0." ); |
3659 | |
3660 | Console()->Register(pName: "showothers" , pParams: "?i['0'|'1'|'2']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConShowOthers, pUser: this, pHelp: "Whether to show players from other teams or not (off by default), optional i = 0 for off, i = 1 for on, i = 2 for own team only" ); |
3661 | Console()->Register(pName: "showall" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConShowAll, pUser: this, pHelp: "Whether to show players at any distance (off by default), optional i = 0 for off else for on" ); |
3662 | Console()->Register(pName: "specteam" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSpecTeam, pUser: this, pHelp: "Whether to show players from other teams when spectating (on by default), optional i = 0 for off else for on" ); |
3663 | Console()->Register(pName: "ninjajetpack" , pParams: "?i['0'|'1']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConNinjaJetpack, pUser: this, pHelp: "Whether to use ninja jetpack or not. Makes jetpack look more awesome" ); |
3664 | Console()->Register(pName: "saytime" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConSayTime, pUser: this, pHelp: "Privately messages someone's current time in this current running race (your time by default)" ); |
3665 | Console()->Register(pName: "saytimeall" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, pfnFunc: ConSayTimeAll, pUser: this, pHelp: "Publicly messages everyone your current time in this current running race" ); |
3666 | Console()->Register(pName: "time" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConTime, pUser: this, pHelp: "Privately shows you your current time in this current running race in the broadcast message" ); |
3667 | Console()->Register(pName: "timer" , pParams: "?s['gametimer'|'broadcast'|'both'|'none'|'cycle']" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConSetTimerType, pUser: this, pHelp: "Personal Setting of showing time in either broadcast or game/round timer, timer s, where s = broadcast for broadcast, gametimer for game/round timer, cycle for cycle, both for both, none for no timer and nothing to show current status" ); |
3668 | |
3669 | Console()->Register(pName: "r" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConRescue, pUser: this, pHelp: "Teleport yourself out of freeze (use sv_rescue 1 to enable this feature)" ); |
3670 | Console()->Register(pName: "rescue" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConRescue, pUser: this, pHelp: "Teleport yourself out of freeze (use sv_rescue 1 to enable this feature)" ); |
3671 | Console()->Register(pName: "tp" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConTeleTo, pUser: this, pHelp: "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name" ); |
3672 | Console()->Register(pName: "teleport" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConTeleTo, pUser: this, pHelp: "Depending on the number of supplied arguments, teleport yourself to; (0.) where you are spectating or aiming; (1.) the specified player name" ); |
3673 | Console()->Register(pName: "tpxy" , pParams: "f[x] f[y]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConTeleXY, pUser: this, pHelp: "Teleport yourself to the specified coordinates. A tilde (~) can be used to denote your current position, e.g. '/tpxy ~1 ~' to teleport one tile to the right" ); |
3674 | Console()->Register(pName: "lasttp" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConLastTele, pUser: this, pHelp: "Teleport yourself to the last location you teleported to" ); |
3675 | Console()->Register(pName: "tc" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConTeleCursor, pUser: this, pHelp: "Teleport yourself to player or to where you are spectating/or looking if no player name is given" ); |
3676 | Console()->Register(pName: "telecursor" , pParams: "?r[player name]" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER | CMDFLAG_PRACTICE, pfnFunc: ConTeleCursor, pUser: this, pHelp: "Teleport yourself to player or to where you are spectating/or looking if no player name is given" ); |
3677 | Console()->Register(pName: "unsolo" , pParams: "" , Flags: CFGFLAG_CHAT | CMDFLAG_PRACTICE, pfnFunc: ConPracticeUnSolo, pUser: this, pHelp: "Puts you out of solo part" ); |
3678 | Console()->Register(pName: "solo" , pParams: "" , Flags: CFGFLAG_CHAT | CMDFLAG_PRACTICE, pfnFunc: ConPracticeSolo, pUser: this, pHelp: "Puts you into solo part" ); |
3679 | Console()->Register(pName: "undeep" , pParams: "" , Flags: CFGFLAG_CHAT | CMDFLAG_PRACTICE, pfnFunc: ConPracticeUnDeep, pUser: this, pHelp: "Puts you out of deep freeze" ); |
3680 | Console()->Register(pName: "deep" , pParams: "" , Flags: CFGFLAG_CHAT | CMDFLAG_PRACTICE, pfnFunc: ConPracticeDeep, pUser: this, pHelp: "Puts you into deep freeze" ); |
3681 | |
3682 | Console()->Register(pName: "kill" , pParams: "" , Flags: CFGFLAG_CHAT | CFGFLAG_SERVER, pfnFunc: ConProtectedKill, pUser: this, pHelp: "Kill yourself when kill-protected during a long game (use f1, kill for regular kill)" ); |
3683 | } |
3684 | |
3685 | void CGameContext::OnInit(const void *pPersistentData) |
3686 | { |
3687 | const CPersistentData *pPersistent = (const CPersistentData *)pPersistentData; |
3688 | |
3689 | m_pServer = Kernel()->RequestInterface<IServer>(); |
3690 | m_pConfigManager = Kernel()->RequestInterface<IConfigManager>(); |
3691 | m_pConfig = m_pConfigManager->Values(); |
3692 | m_pConsole = Kernel()->RequestInterface<IConsole>(); |
3693 | m_pEngine = Kernel()->RequestInterface<IEngine>(); |
3694 | m_pStorage = Kernel()->RequestInterface<IStorage>(); |
3695 | m_pAntibot = Kernel()->RequestInterface<IAntibot>(); |
3696 | m_World.SetGameServer(this); |
3697 | m_Events.SetGameServer(this); |
3698 | |
3699 | m_GameUuid = RandomUuid(); |
3700 | Console()->SetTeeHistorianCommandCallback(pfnCallback: CommandCallback, pUser: this); |
3701 | |
3702 | uint64_t aSeed[2]; |
3703 | secure_random_fill(bytes: aSeed, length: sizeof(aSeed)); |
3704 | m_Prng.Seed(aSeed); |
3705 | m_World.m_Core.m_pPrng = &m_Prng; |
3706 | |
3707 | DeleteTempfile(); |
3708 | |
3709 | for(int i = 0; i < NUM_NETOBJTYPES; i++) |
3710 | Server()->SnapSetStaticsize(ItemType: i, Size: m_NetObjHandler.GetObjSize(Type: i)); |
3711 | |
3712 | m_Layers.Init(pKernel: Kernel()); |
3713 | m_Collision.Init(pLayers: &m_Layers); |
3714 | m_World.m_pTuningList = m_aTuningList; |
3715 | m_World.m_Core.InitSwitchers(HighestSwitchNumber: m_Collision.m_HighestSwitchNumber); |
3716 | |
3717 | char aMapName[IO_MAX_PATH_LENGTH]; |
3718 | int MapSize; |
3719 | SHA256_DIGEST MapSha256; |
3720 | int MapCrc; |
3721 | Server()->GetMapInfo(pMapName: aMapName, MapNameSize: sizeof(aMapName), pMapSize: &MapSize, pSha256: &MapSha256, pMapCrc: &MapCrc); |
3722 | m_MapBugs = GetMapBugs(pName: aMapName, Size: MapSize, Sha256: MapSha256); |
3723 | |
3724 | // Reset Tunezones |
3725 | CTuningParams TuningParams; |
3726 | for(int i = 0; i < NUM_TUNEZONES; i++) |
3727 | { |
3728 | TuningList()[i] = TuningParams; |
3729 | TuningList()[i].Set(pName: "gun_curvature" , Value: 0); |
3730 | TuningList()[i].Set(pName: "gun_speed" , Value: 1400); |
3731 | TuningList()[i].Set(pName: "shotgun_curvature" , Value: 0); |
3732 | TuningList()[i].Set(pName: "shotgun_speed" , Value: 500); |
3733 | TuningList()[i].Set(pName: "shotgun_speeddiff" , Value: 0); |
3734 | } |
3735 | |
3736 | for(int i = 0; i < NUM_TUNEZONES; i++) |
3737 | { |
3738 | // Send no text by default when changing tune zones. |
3739 | m_aaZoneEnterMsg[i][0] = 0; |
3740 | m_aaZoneLeaveMsg[i][0] = 0; |
3741 | } |
3742 | // Reset Tuning |
3743 | if(g_Config.m_SvTuneReset) |
3744 | { |
3745 | ResetTuning(); |
3746 | } |
3747 | else |
3748 | { |
3749 | Tuning()->Set(pName: "gun_speed" , Value: 1400); |
3750 | Tuning()->Set(pName: "gun_curvature" , Value: 0); |
3751 | Tuning()->Set(pName: "shotgun_speed" , Value: 500); |
3752 | Tuning()->Set(pName: "shotgun_speeddiff" , Value: 0); |
3753 | Tuning()->Set(pName: "shotgun_curvature" , Value: 0); |
3754 | } |
3755 | |
3756 | if(g_Config.m_SvDDRaceTuneReset) |
3757 | { |
3758 | g_Config.m_SvHit = 1; |
3759 | g_Config.m_SvEndlessDrag = 0; |
3760 | g_Config.m_SvOldLaser = 0; |
3761 | g_Config.m_SvOldTeleportHook = 0; |
3762 | g_Config.m_SvOldTeleportWeapons = 0; |
3763 | g_Config.m_SvTeleportHoldHook = 0; |
3764 | g_Config.m_SvTeam = SV_TEAM_ALLOWED; |
3765 | g_Config.m_SvShowOthersDefault = SHOW_OTHERS_OFF; |
3766 | |
3767 | for(auto &Switcher : Switchers()) |
3768 | Switcher.m_Initial = true; |
3769 | } |
3770 | |
3771 | Console()->ExecuteFile(pFilename: g_Config.m_SvResetFile, ClientId: -1); |
3772 | |
3773 | LoadMapSettings(); |
3774 | |
3775 | m_MapBugs.Dump(); |
3776 | |
3777 | if(g_Config.m_SvSoloServer) |
3778 | { |
3779 | g_Config.m_SvTeam = SV_TEAM_FORCED_SOLO; |
3780 | g_Config.m_SvShowOthersDefault = SHOW_OTHERS_ON; |
3781 | |
3782 | Tuning()->Set(pName: "player_collision" , Value: 0); |
3783 | Tuning()->Set(pName: "player_hooking" , Value: 0); |
3784 | |
3785 | for(int i = 0; i < NUM_TUNEZONES; i++) |
3786 | { |
3787 | TuningList()[i].Set(pName: "player_collision" , Value: 0); |
3788 | TuningList()[i].Set(pName: "player_hooking" , Value: 0); |
3789 | } |
3790 | } |
3791 | |
3792 | if(!str_comp(a: Config()->m_SvGametype, b: "mod" )) |
3793 | m_pController = new CGameControllerMod(this); |
3794 | else |
3795 | m_pController = new CGameControllerDDRace(this); |
3796 | |
3797 | const char *pCensorFilename = "censorlist.txt" ; |
3798 | IOHANDLE File = Storage()->OpenFile(pFilename: pCensorFilename, Flags: IOFLAG_READ | IOFLAG_SKIP_BOM, Type: IStorage::TYPE_ALL); |
3799 | if(!File) |
3800 | { |
3801 | dbg_msg(sys: "censorlist" , fmt: "failed to open '%s'" , pCensorFilename); |
3802 | } |
3803 | else |
3804 | { |
3805 | CLineReader LineReader; |
3806 | LineReader.Init(File); |
3807 | char *pLine; |
3808 | while((pLine = LineReader.Get())) |
3809 | { |
3810 | m_vCensorlist.emplace_back(args&: pLine); |
3811 | } |
3812 | io_close(io: File); |
3813 | } |
3814 | |
3815 | m_TeeHistorianActive = g_Config.m_SvTeeHistorian; |
3816 | if(m_TeeHistorianActive) |
3817 | { |
3818 | char aGameUuid[UUID_MAXSTRSIZE]; |
3819 | FormatUuid(Uuid: m_GameUuid, pBuffer: aGameUuid, BufferLength: sizeof(aGameUuid)); |
3820 | |
3821 | char aFilename[IO_MAX_PATH_LENGTH]; |
3822 | str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "teehistorian/%s.teehistorian" , aGameUuid); |
3823 | |
3824 | IOHANDLE THFile = Storage()->OpenFile(pFilename: aFilename, Flags: IOFLAG_WRITE, Type: IStorage::TYPE_SAVE); |
3825 | if(!THFile) |
3826 | { |
3827 | dbg_msg(sys: "teehistorian" , fmt: "failed to open '%s'" , aFilename); |
3828 | Server()->SetErrorShutdown("teehistorian open error" ); |
3829 | return; |
3830 | } |
3831 | else |
3832 | { |
3833 | dbg_msg(sys: "teehistorian" , fmt: "recording to '%s'" , aFilename); |
3834 | } |
3835 | m_pTeeHistorianFile = aio_new(io: THFile); |
3836 | |
3837 | char aVersion[128]; |
3838 | if(GIT_SHORTREV_HASH) |
3839 | { |
3840 | str_format(buffer: aVersion, buffer_size: sizeof(aVersion), format: "%s (%s)" , GAME_VERSION, GIT_SHORTREV_HASH); |
3841 | } |
3842 | else |
3843 | { |
3844 | str_copy(dst&: aVersion, GAME_VERSION); |
3845 | } |
3846 | CTeeHistorian::CGameInfo GameInfo; |
3847 | GameInfo.m_GameUuid = m_GameUuid; |
3848 | GameInfo.m_pServerVersion = aVersion; |
3849 | GameInfo.m_StartTime = time(timer: 0); |
3850 | GameInfo.m_pPrngDescription = m_Prng.Description(); |
3851 | |
3852 | GameInfo.m_pServerName = g_Config.m_SvName; |
3853 | GameInfo.m_ServerPort = Server()->Port(); |
3854 | GameInfo.m_pGameType = m_pController->m_pGameType; |
3855 | |
3856 | GameInfo.m_pConfig = &g_Config; |
3857 | GameInfo.m_pTuning = Tuning(); |
3858 | GameInfo.m_pUuids = &g_UuidManager; |
3859 | |
3860 | GameInfo.m_pMapName = aMapName; |
3861 | GameInfo.m_MapSize = MapSize; |
3862 | GameInfo.m_MapSha256 = MapSha256; |
3863 | GameInfo.m_MapCrc = MapCrc; |
3864 | |
3865 | if(pPersistent) |
3866 | { |
3867 | GameInfo.m_HavePrevGameUuid = true; |
3868 | GameInfo.m_PrevGameUuid = pPersistent->m_PrevGameUuid; |
3869 | } |
3870 | else |
3871 | { |
3872 | GameInfo.m_HavePrevGameUuid = false; |
3873 | mem_zero(block: &GameInfo.m_PrevGameUuid, size: sizeof(GameInfo.m_PrevGameUuid)); |
3874 | } |
3875 | |
3876 | m_TeeHistorian.Reset(pGameInfo: &GameInfo, pfnWriteCallback: TeeHistorianWrite, pUser: this); |
3877 | |
3878 | for(int i = 0; i < MAX_CLIENTS; i++) |
3879 | { |
3880 | int Level = Server()->GetAuthedState(ClientId: i); |
3881 | if(Level) |
3882 | { |
3883 | m_TeeHistorian.RecordAuthInitial(ClientId: i, Level, pAuthName: Server()->GetAuthName(ClientId: i)); |
3884 | } |
3885 | } |
3886 | } |
3887 | |
3888 | Server()->DemoRecorder_HandleAutoStart(); |
3889 | |
3890 | if(!m_pScore) |
3891 | { |
3892 | m_pScore = new CScore(this, ((CServer *)Server())->DbPool()); |
3893 | } |
3894 | |
3895 | // create all entities from the game layer |
3896 | CreateAllEntities(Initial: true); |
3897 | |
3898 | m_pAntibot->RoundStart(pGameServer: this); |
3899 | } |
3900 | |
3901 | void CGameContext::CreateAllEntities(bool Initial) |
3902 | { |
3903 | const CMapItemLayerTilemap *pTileMap = m_Layers.GameLayer(); |
3904 | const CTile *pTiles = static_cast<CTile *>(Kernel()->RequestInterface<IMap>()->GetData(Index: pTileMap->m_Data)); |
3905 | |
3906 | const CTile *pFront = nullptr; |
3907 | if(m_Layers.FrontLayer()) |
3908 | pFront = static_cast<CTile *>(Kernel()->RequestInterface<IMap>()->GetData(Index: m_Layers.FrontLayer()->m_Front)); |
3909 | |
3910 | const CSwitchTile *pSwitch = nullptr; |
3911 | if(m_Layers.SwitchLayer()) |
3912 | pSwitch = static_cast<CSwitchTile *>(Kernel()->RequestInterface<IMap>()->GetData(Index: m_Layers.SwitchLayer()->m_Switch)); |
3913 | |
3914 | for(int y = 0; y < pTileMap->m_Height; y++) |
3915 | { |
3916 | for(int x = 0; x < pTileMap->m_Width; x++) |
3917 | { |
3918 | const int Index = y * pTileMap->m_Width + x; |
3919 | |
3920 | // Game layer |
3921 | { |
3922 | const int GameIndex = pTiles[Index].m_Index; |
3923 | if(GameIndex == TILE_OLDLASER) |
3924 | { |
3925 | g_Config.m_SvOldLaser = 1; |
3926 | dbg_msg(sys: "game_layer" , fmt: "found old laser tile" ); |
3927 | } |
3928 | else if(GameIndex == TILE_NPC) |
3929 | { |
3930 | m_Tuning.Set(pName: "player_collision" , Value: 0); |
3931 | dbg_msg(sys: "game_layer" , fmt: "found no collision tile" ); |
3932 | } |
3933 | else if(GameIndex == TILE_EHOOK) |
3934 | { |
3935 | g_Config.m_SvEndlessDrag = 1; |
3936 | dbg_msg(sys: "game_layer" , fmt: "found unlimited hook time tile" ); |
3937 | } |
3938 | else if(GameIndex == TILE_NOHIT) |
3939 | { |
3940 | g_Config.m_SvHit = 0; |
3941 | dbg_msg(sys: "game_layer" , fmt: "found no weapons hitting others tile" ); |
3942 | } |
3943 | else if(GameIndex == TILE_NPH) |
3944 | { |
3945 | m_Tuning.Set(pName: "player_hooking" , Value: 0); |
3946 | dbg_msg(sys: "game_layer" , fmt: "found no player hooking tile" ); |
3947 | } |
3948 | else if(GameIndex >= ENTITY_OFFSET) |
3949 | { |
3950 | m_pController->OnEntity(Index: GameIndex - ENTITY_OFFSET, x, y, Layer: LAYER_GAME, Flags: pTiles[Index].m_Flags, Initial); |
3951 | } |
3952 | } |
3953 | |
3954 | if(pFront) |
3955 | { |
3956 | const int FrontIndex = pFront[Index].m_Index; |
3957 | if(FrontIndex == TILE_OLDLASER) |
3958 | { |
3959 | g_Config.m_SvOldLaser = 1; |
3960 | dbg_msg(sys: "front_layer" , fmt: "found old laser tile" ); |
3961 | } |
3962 | else if(FrontIndex == TILE_NPC) |
3963 | { |
3964 | m_Tuning.Set(pName: "player_collision" , Value: 0); |
3965 | dbg_msg(sys: "front_layer" , fmt: "found no collision tile" ); |
3966 | } |
3967 | else if(FrontIndex == TILE_EHOOK) |
3968 | { |
3969 | g_Config.m_SvEndlessDrag = 1; |
3970 | dbg_msg(sys: "front_layer" , fmt: "found unlimited hook time tile" ); |
3971 | } |
3972 | else if(FrontIndex == TILE_NOHIT) |
3973 | { |
3974 | g_Config.m_SvHit = 0; |
3975 | dbg_msg(sys: "front_layer" , fmt: "found no weapons hitting others tile" ); |
3976 | } |
3977 | else if(FrontIndex == TILE_NPH) |
3978 | { |
3979 | m_Tuning.Set(pName: "player_hooking" , Value: 0); |
3980 | dbg_msg(sys: "front_layer" , fmt: "found no player hooking tile" ); |
3981 | } |
3982 | else if(FrontIndex >= ENTITY_OFFSET) |
3983 | { |
3984 | m_pController->OnEntity(Index: FrontIndex - ENTITY_OFFSET, x, y, Layer: LAYER_FRONT, Flags: pFront[Index].m_Flags, Initial); |
3985 | } |
3986 | } |
3987 | |
3988 | if(pSwitch) |
3989 | { |
3990 | const int SwitchType = pSwitch[Index].m_Type; |
3991 | // TODO: Add off by default door here |
3992 | // if(SwitchType == TILE_DOOR_OFF) |
3993 | if(SwitchType >= ENTITY_OFFSET) |
3994 | { |
3995 | m_pController->OnEntity(Index: SwitchType - ENTITY_OFFSET, x, y, Layer: LAYER_SWITCH, Flags: pSwitch[Index].m_Flags, Initial, Number: pSwitch[Index].m_Number); |
3996 | } |
3997 | } |
3998 | } |
3999 | } |
4000 | } |
4001 | |
4002 | void CGameContext::DeleteTempfile() |
4003 | { |
4004 | if(m_aDeleteTempfile[0] != 0) |
4005 | { |
4006 | Storage()->RemoveFile(pFilename: m_aDeleteTempfile, Type: IStorage::TYPE_SAVE); |
4007 | m_aDeleteTempfile[0] = 0; |
4008 | } |
4009 | } |
4010 | |
4011 | void CGameContext::OnMapChange(char *pNewMapName, int MapNameSize) |
4012 | { |
4013 | char aConfig[IO_MAX_PATH_LENGTH]; |
4014 | str_format(buffer: aConfig, buffer_size: sizeof(aConfig), format: "maps/%s.cfg" , g_Config.m_SvMap); |
4015 | |
4016 | IOHANDLE File = Storage()->OpenFile(pFilename: aConfig, Flags: IOFLAG_READ | IOFLAG_SKIP_BOM, Type: IStorage::TYPE_ALL); |
4017 | if(!File) |
4018 | { |
4019 | // No map-specific config, just return. |
4020 | return; |
4021 | } |
4022 | CLineReader LineReader; |
4023 | LineReader.Init(File); |
4024 | |
4025 | std::vector<char *> vLines; |
4026 | char *pLine; |
4027 | int TotalLength = 0; |
4028 | while((pLine = LineReader.Get())) |
4029 | { |
4030 | int Length = str_length(str: pLine) + 1; |
4031 | char *pCopy = (char *)malloc(size: Length); |
4032 | mem_copy(dest: pCopy, source: pLine, size: Length); |
4033 | vLines.push_back(x: pCopy); |
4034 | TotalLength += Length; |
4035 | } |
4036 | io_close(io: File); |
4037 | |
4038 | char *pSettings = (char *)malloc(size: maximum(a: 1, b: TotalLength)); |
4039 | int Offset = 0; |
4040 | for(auto &Line : vLines) |
4041 | { |
4042 | int Length = str_length(str: Line) + 1; |
4043 | mem_copy(dest: pSettings + Offset, source: Line, size: Length); |
4044 | Offset += Length; |
4045 | free(ptr: Line); |
4046 | } |
4047 | |
4048 | CDataFileReader Reader; |
4049 | Reader.Open(pStorage: Storage(), pFilename: pNewMapName, StorageType: IStorage::TYPE_ALL); |
4050 | |
4051 | CDataFileWriter Writer; |
4052 | |
4053 | int SettingsIndex = Reader.NumData(); |
4054 | bool FoundInfo = false; |
4055 | for(int i = 0; i < Reader.NumItems(); i++) |
4056 | { |
4057 | int TypeId; |
4058 | int ItemId; |
4059 | void *pData = Reader.GetItem(Index: i, pType: &TypeId, pId: &ItemId); |
4060 | int Size = Reader.GetItemSize(Index: i); |
4061 | CMapItemInfoSettings MapInfo; |
4062 | if(TypeId == MAPITEMTYPE_INFO && ItemId == 0) |
4063 | { |
4064 | FoundInfo = true; |
4065 | if(Size >= (int)sizeof(CMapItemInfoSettings)) |
4066 | { |
4067 | CMapItemInfoSettings *pInfo = (CMapItemInfoSettings *)pData; |
4068 | if(pInfo->m_Settings > -1) |
4069 | { |
4070 | SettingsIndex = pInfo->m_Settings; |
4071 | char *pMapSettings = (char *)Reader.GetData(Index: SettingsIndex); |
4072 | int DataSize = Reader.GetDataSize(Index: SettingsIndex); |
4073 | if(DataSize == TotalLength && mem_comp(a: pSettings, b: pMapSettings, size: DataSize) == 0) |
4074 | { |
4075 | // Configs coincide, no need to update map. |
4076 | free(ptr: pSettings); |
4077 | return; |
4078 | } |
4079 | Reader.UnloadData(Index: pInfo->m_Settings); |
4080 | } |
4081 | else |
4082 | { |
4083 | MapInfo = *pInfo; |
4084 | MapInfo.m_Settings = SettingsIndex; |
4085 | pData = &MapInfo; |
4086 | Size = sizeof(MapInfo); |
4087 | } |
4088 | } |
4089 | else |
4090 | { |
4091 | *(CMapItemInfo *)&MapInfo = *(CMapItemInfo *)pData; |
4092 | MapInfo.m_Settings = SettingsIndex; |
4093 | pData = &MapInfo; |
4094 | Size = sizeof(MapInfo); |
4095 | } |
4096 | } |
4097 | Writer.AddItem(Type: TypeId, Id: ItemId, Size, pData); |
4098 | } |
4099 | |
4100 | if(!FoundInfo) |
4101 | { |
4102 | CMapItemInfoSettings Info; |
4103 | Info.m_Version = 1; |
4104 | Info.m_Author = -1; |
4105 | Info.m_MapVersion = -1; |
4106 | Info.m_Credits = -1; |
4107 | Info.m_License = -1; |
4108 | Info.m_Settings = SettingsIndex; |
4109 | Writer.AddItem(Type: MAPITEMTYPE_INFO, Id: 0, Size: sizeof(Info), pData: &Info); |
4110 | } |
4111 | |
4112 | for(int i = 0; i < Reader.NumData() || i == SettingsIndex; i++) |
4113 | { |
4114 | if(i == SettingsIndex) |
4115 | { |
4116 | Writer.AddData(Size: TotalLength, pData: pSettings); |
4117 | continue; |
4118 | } |
4119 | const void *pData = Reader.GetData(Index: i); |
4120 | int Size = Reader.GetDataSize(Index: i); |
4121 | Writer.AddData(Size, pData); |
4122 | Reader.UnloadData(Index: i); |
4123 | } |
4124 | |
4125 | dbg_msg(sys: "mapchange" , fmt: "imported settings" ); |
4126 | free(ptr: pSettings); |
4127 | Reader.Close(); |
4128 | char aTemp[IO_MAX_PATH_LENGTH]; |
4129 | Writer.Open(pStorage: Storage(), pFilename: IStorage::FormatTmpPath(aBuf: aTemp, BufSize: sizeof(aTemp), pPath: pNewMapName)); |
4130 | Writer.Finish(); |
4131 | |
4132 | str_copy(dst: pNewMapName, src: aTemp, dst_size: MapNameSize); |
4133 | str_copy(dst: m_aDeleteTempfile, src: aTemp, dst_size: sizeof(m_aDeleteTempfile)); |
4134 | } |
4135 | |
4136 | void CGameContext::OnShutdown(void *pPersistentData) |
4137 | { |
4138 | CPersistentData *pPersistent = (CPersistentData *)pPersistentData; |
4139 | |
4140 | if(pPersistent) |
4141 | { |
4142 | pPersistent->m_PrevGameUuid = m_GameUuid; |
4143 | } |
4144 | |
4145 | Antibot()->RoundEnd(); |
4146 | |
4147 | if(m_TeeHistorianActive) |
4148 | { |
4149 | m_TeeHistorian.Finish(); |
4150 | aio_close(aio: m_pTeeHistorianFile); |
4151 | aio_wait(aio: m_pTeeHistorianFile); |
4152 | int Error = aio_error(aio: m_pTeeHistorianFile); |
4153 | if(Error) |
4154 | { |
4155 | dbg_msg(sys: "teehistorian" , fmt: "error closing file, err=%d" , Error); |
4156 | Server()->SetErrorShutdown("teehistorian close error" ); |
4157 | } |
4158 | aio_free(aio: m_pTeeHistorianFile); |
4159 | } |
4160 | |
4161 | // Stop any demos being recorded. |
4162 | Server()->StopDemos(); |
4163 | |
4164 | DeleteTempfile(); |
4165 | ConfigManager()->ResetGameSettings(); |
4166 | Collision()->Dest(); |
4167 | delete m_pController; |
4168 | m_pController = 0; |
4169 | Clear(); |
4170 | } |
4171 | |
4172 | void CGameContext::LoadMapSettings() |
4173 | { |
4174 | IMap *pMap = Kernel()->RequestInterface<IMap>(); |
4175 | int Start, Num; |
4176 | pMap->GetType(Type: MAPITEMTYPE_INFO, pStart: &Start, pNum: &Num); |
4177 | for(int i = Start; i < Start + Num; i++) |
4178 | { |
4179 | int ItemId; |
4180 | CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)pMap->GetItem(Index: i, pType: nullptr, pId: &ItemId); |
4181 | int ItemSize = pMap->GetItemSize(Index: i); |
4182 | if(!pItem || ItemId != 0) |
4183 | continue; |
4184 | |
4185 | if(ItemSize < (int)sizeof(CMapItemInfoSettings)) |
4186 | break; |
4187 | if(!(pItem->m_Settings > -1)) |
4188 | break; |
4189 | |
4190 | int Size = pMap->GetDataSize(Index: pItem->m_Settings); |
4191 | char *pSettings = (char *)pMap->GetData(Index: pItem->m_Settings); |
4192 | char *pNext = pSettings; |
4193 | while(pNext < pSettings + Size) |
4194 | { |
4195 | int StrSize = str_length(str: pNext) + 1; |
4196 | Console()->ExecuteLine(pStr: pNext, ClientId: IConsole::CLIENT_ID_GAME); |
4197 | pNext += StrSize; |
4198 | } |
4199 | pMap->UnloadData(Index: pItem->m_Settings); |
4200 | break; |
4201 | } |
4202 | |
4203 | char aBuf[IO_MAX_PATH_LENGTH]; |
4204 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "maps/%s.map.cfg" , g_Config.m_SvMap); |
4205 | Console()->ExecuteFile(pFilename: aBuf, ClientId: IConsole::CLIENT_ID_NO_GAME); |
4206 | } |
4207 | |
4208 | void CGameContext::OnSnap(int ClientId) |
4209 | { |
4210 | // add tuning to demo |
4211 | CTuningParams StandardTuning; |
4212 | if(Server()->IsRecording(ClientId: ClientId > -1 ? ClientId : MAX_CLIENTS) && mem_comp(a: &StandardTuning, b: &m_Tuning, size: sizeof(CTuningParams)) != 0) |
4213 | { |
4214 | CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS); |
4215 | int *pParams = (int *)&m_Tuning; |
4216 | for(unsigned i = 0; i < sizeof(m_Tuning) / sizeof(int); i++) |
4217 | Msg.AddInt(i: pParams[i]); |
4218 | Server()->SendMsg(pMsg: &Msg, Flags: MSGFLAG_RECORD | MSGFLAG_NOSEND, ClientId); |
4219 | } |
4220 | |
4221 | m_pController->Snap(SnappingClient: ClientId); |
4222 | |
4223 | for(auto &pPlayer : m_apPlayers) |
4224 | { |
4225 | if(pPlayer) |
4226 | pPlayer->Snap(SnappingClient: ClientId); |
4227 | } |
4228 | |
4229 | if(ClientId > -1) |
4230 | m_apPlayers[ClientId]->FakeSnap(); |
4231 | |
4232 | m_World.Snap(SnappingClient: ClientId); |
4233 | m_Events.Snap(SnappingClient: ClientId); |
4234 | } |
4235 | void CGameContext::OnPreSnap() {} |
4236 | void CGameContext::OnPostSnap() |
4237 | { |
4238 | m_Events.Clear(); |
4239 | } |
4240 | |
4241 | void CGameContext::UpdatePlayerMaps() |
4242 | { |
4243 | const auto DistCompare = [](std::pair<float, int> a, std::pair<float, int> b) -> bool { |
4244 | return (a.first < b.first); |
4245 | }; |
4246 | |
4247 | if(Server()->Tick() % g_Config.m_SvMapUpdateRate != 0) |
4248 | return; |
4249 | |
4250 | std::pair<float, int> Dist[MAX_CLIENTS]; |
4251 | for(int i = 0; i < MAX_CLIENTS; i++) |
4252 | { |
4253 | if(!Server()->ClientIngame(ClientId: i)) |
4254 | continue; |
4255 | if(Server()->GetClientVersion(ClientId: i) >= VERSION_DDNET_OLD) |
4256 | continue; |
4257 | int *pMap = Server()->GetIdMap(ClientId: i); |
4258 | |
4259 | // compute distances |
4260 | for(int j = 0; j < MAX_CLIENTS; j++) |
4261 | { |
4262 | Dist[j].second = j; |
4263 | if(j == i) |
4264 | continue; |
4265 | if(!Server()->ClientIngame(ClientId: j) || !m_apPlayers[j]) |
4266 | { |
4267 | Dist[j].first = 1e10; |
4268 | continue; |
4269 | } |
4270 | CCharacter *pChr = m_apPlayers[j]->GetCharacter(); |
4271 | if(!pChr) |
4272 | { |
4273 | Dist[j].first = 1e9; |
4274 | continue; |
4275 | } |
4276 | if(!pChr->CanSnapCharacter(SnappingClient: i)) |
4277 | Dist[j].first = 1e8; |
4278 | else |
4279 | Dist[j].first = length_squared(a: m_apPlayers[i]->m_ViewPos - pChr->GetPos()); |
4280 | } |
4281 | |
4282 | // always send the player themselves, even if all in same position |
4283 | Dist[i].first = -1; |
4284 | |
4285 | std::nth_element(first: &Dist[0], nth: &Dist[VANILLA_MAX_CLIENTS - 1], last: &Dist[MAX_CLIENTS], comp: DistCompare); |
4286 | |
4287 | int Index = 1; // exclude self client id |
4288 | for(int j = 0; j < VANILLA_MAX_CLIENTS - 1; j++) |
4289 | { |
4290 | pMap[j + 1] = -1; // also fill player with empty name to say chat msgs |
4291 | if(Dist[j].second == i || Dist[j].first > 5e9f) |
4292 | continue; |
4293 | pMap[Index++] = Dist[j].second; |
4294 | } |
4295 | |
4296 | // sort by real client ids, guarantee order on distance changes, O(Nlog(N)) worst case |
4297 | // sort just clients in game always except first (self client id) and last (fake client id) indexes |
4298 | std::sort(first: &pMap[1], last: &pMap[minimum(a: Index, b: VANILLA_MAX_CLIENTS - 1)]); |
4299 | } |
4300 | } |
4301 | |
4302 | bool CGameContext::IsClientReady(int ClientId) const |
4303 | { |
4304 | return m_apPlayers[ClientId] && m_apPlayers[ClientId]->m_IsReady; |
4305 | } |
4306 | |
4307 | bool CGameContext::IsClientPlayer(int ClientId) const |
4308 | { |
4309 | return m_apPlayers[ClientId] && m_apPlayers[ClientId]->GetTeam() != TEAM_SPECTATORS; |
4310 | } |
4311 | |
4312 | CUuid CGameContext::GameUuid() const { return m_GameUuid; } |
4313 | const char *CGameContext::GameType() const { return m_pController && m_pController->m_pGameType ? m_pController->m_pGameType : "" ; } |
4314 | const char *CGameContext::Version() const { return GAME_VERSION; } |
4315 | const char *CGameContext::NetVersion() const { return GAME_NETVERSION; } |
4316 | |
4317 | IGameServer *CreateGameServer() { return new CGameContext; } |
4318 | |
4319 | void CGameContext::OnSetAuthed(int ClientId, int Level) |
4320 | { |
4321 | if(m_apPlayers[ClientId]) |
4322 | { |
4323 | char aBuf[512], aIp[NETADDR_MAXSTRSIZE]; |
4324 | Server()->GetClientAddr(ClientId, pAddrStr: aIp, Size: sizeof(aIp)); |
4325 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban %s %d Banned by vote" , aIp, g_Config.m_SvVoteKickBantime); |
4326 | if(!str_comp_nocase(a: m_aVoteCommand, b: aBuf) && Level > Server()->GetAuthedState(ClientId: m_VoteCreator)) |
4327 | { |
4328 | m_VoteEnforce = CGameContext::VOTE_ENFORCE_NO_ADMIN; |
4329 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "CGameContext" , pStr: "Vote aborted by authorized login." ); |
4330 | } |
4331 | } |
4332 | if(m_TeeHistorianActive) |
4333 | { |
4334 | if(Level) |
4335 | { |
4336 | m_TeeHistorian.RecordAuthLogin(ClientId, Level, pAuthName: Server()->GetAuthName(ClientId)); |
4337 | } |
4338 | else |
4339 | { |
4340 | m_TeeHistorian.RecordAuthLogout(ClientId); |
4341 | } |
4342 | } |
4343 | } |
4344 | |
4345 | void CGameContext::SendRecord(int ClientId) |
4346 | { |
4347 | CNetMsg_Sv_Record Msg; |
4348 | CNetMsg_Sv_RecordLegacy MsgLegacy; |
4349 | MsgLegacy.m_PlayerTimeBest = Msg.m_PlayerTimeBest = Score()->PlayerData(Id: ClientId)->m_BestTime * 100.0f; |
4350 | MsgLegacy.m_ServerTimeBest = Msg.m_ServerTimeBest = m_pController->m_CurrentRecord * 100.0f; //TODO: finish this |
4351 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
4352 | if(!Server()->IsSixup(ClientId) && GetClientVersion(ClientId) < VERSION_DDNET_MSG_LEGACY) |
4353 | { |
4354 | Server()->SendPackMsg(pMsg: &MsgLegacy, Flags: MSGFLAG_VITAL, ClientId); |
4355 | } |
4356 | } |
4357 | |
4358 | bool CGameContext::ProcessSpamProtection(int ClientId, bool RespectChatInitialDelay) |
4359 | { |
4360 | if(!m_apPlayers[ClientId]) |
4361 | return false; |
4362 | if(g_Config.m_SvSpamprotection && m_apPlayers[ClientId]->m_LastChat && m_apPlayers[ClientId]->m_LastChat + Server()->TickSpeed() * g_Config.m_SvChatDelay > Server()->Tick()) |
4363 | return true; |
4364 | else if(g_Config.m_SvDnsblChat && Server()->DnsblBlack(ClientId)) |
4365 | { |
4366 | SendChatTarget(To: ClientId, pText: "Players are not allowed to chat from VPNs at this time" ); |
4367 | return true; |
4368 | } |
4369 | else |
4370 | m_apPlayers[ClientId]->m_LastChat = Server()->Tick(); |
4371 | |
4372 | NETADDR Addr; |
4373 | Server()->GetClientAddr(ClientId, pAddr: &Addr); |
4374 | |
4375 | CMute Muted; |
4376 | int Expires = 0; |
4377 | for(int i = 0; i < m_NumMutes && Expires <= 0; i++) |
4378 | { |
4379 | if(!net_addr_comp_noport(a: &Addr, b: &m_aMutes[i].m_Addr)) |
4380 | { |
4381 | if(RespectChatInitialDelay || m_aMutes[i].m_InitialChatDelay) |
4382 | { |
4383 | Muted = m_aMutes[i]; |
4384 | Expires = (m_aMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); |
4385 | } |
4386 | } |
4387 | } |
4388 | |
4389 | if(Expires > 0) |
4390 | { |
4391 | char aBuf[128]; |
4392 | if(Muted.m_InitialChatDelay) |
4393 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "This server has an initial chat delay, you will be able to talk in %d seconds." , Expires); |
4394 | else |
4395 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You are not permitted to talk for the next %d seconds." , Expires); |
4396 | SendChatTarget(To: ClientId, pText: aBuf); |
4397 | return true; |
4398 | } |
4399 | |
4400 | if(g_Config.m_SvSpamMuteDuration && (m_apPlayers[ClientId]->m_ChatScore += g_Config.m_SvChatPenalty) > g_Config.m_SvChatThreshold) |
4401 | { |
4402 | Mute(pAddr: &Addr, Secs: g_Config.m_SvSpamMuteDuration, pDisplayName: Server()->ClientName(ClientId)); |
4403 | m_apPlayers[ClientId]->m_ChatScore = 0; |
4404 | return true; |
4405 | } |
4406 | |
4407 | return false; |
4408 | } |
4409 | |
4410 | int CGameContext::GetDDRaceTeam(int ClientId) const |
4411 | { |
4412 | return m_pController->Teams().m_Core.Team(ClientId); |
4413 | } |
4414 | |
4415 | void CGameContext::ResetTuning() |
4416 | { |
4417 | CTuningParams TuningParams; |
4418 | m_Tuning = TuningParams; |
4419 | Tuning()->Set(pName: "gun_speed" , Value: 1400); |
4420 | Tuning()->Set(pName: "gun_curvature" , Value: 0); |
4421 | Tuning()->Set(pName: "shotgun_speed" , Value: 500); |
4422 | Tuning()->Set(pName: "shotgun_speeddiff" , Value: 0); |
4423 | Tuning()->Set(pName: "shotgun_curvature" , Value: 0); |
4424 | SendTuningParams(ClientId: -1); |
4425 | } |
4426 | |
4427 | bool CheckClientId2(int ClientId) |
4428 | { |
4429 | return ClientId >= 0 && ClientId < MAX_CLIENTS; |
4430 | } |
4431 | |
4432 | void CGameContext::Whisper(int ClientId, char *pStr) |
4433 | { |
4434 | if(ProcessSpamProtection(ClientId)) |
4435 | return; |
4436 | |
4437 | pStr = str_skip_whitespaces(str: pStr); |
4438 | |
4439 | char *pName; |
4440 | int Victim; |
4441 | bool Error = false; |
4442 | |
4443 | // add token |
4444 | if(*pStr == '"') |
4445 | { |
4446 | pStr++; |
4447 | |
4448 | pName = pStr; |
4449 | char *pDst = pStr; // we might have to process escape data |
4450 | while(true) |
4451 | { |
4452 | if(pStr[0] == '"') |
4453 | { |
4454 | break; |
4455 | } |
4456 | else if(pStr[0] == '\\') |
4457 | { |
4458 | if(pStr[1] == '\\') |
4459 | pStr++; // skip due to escape |
4460 | else if(pStr[1] == '"') |
4461 | pStr++; // skip due to escape |
4462 | } |
4463 | else if(pStr[0] == 0) |
4464 | { |
4465 | Error = true; |
4466 | break; |
4467 | } |
4468 | |
4469 | *pDst = *pStr; |
4470 | pDst++; |
4471 | pStr++; |
4472 | } |
4473 | |
4474 | if(!Error) |
4475 | { |
4476 | // write null termination |
4477 | *pDst = 0; |
4478 | |
4479 | pStr++; |
4480 | |
4481 | for(Victim = 0; Victim < MAX_CLIENTS; Victim++) |
4482 | if(str_comp(a: pName, b: Server()->ClientName(ClientId: Victim)) == 0) |
4483 | break; |
4484 | } |
4485 | } |
4486 | else |
4487 | { |
4488 | pName = pStr; |
4489 | while(true) |
4490 | { |
4491 | if(pStr[0] == 0) |
4492 | { |
4493 | Error = true; |
4494 | break; |
4495 | } |
4496 | if(pStr[0] == ' ') |
4497 | { |
4498 | pStr[0] = 0; |
4499 | for(Victim = 0; Victim < MAX_CLIENTS; Victim++) |
4500 | if(str_comp(a: pName, b: Server()->ClientName(ClientId: Victim)) == 0) |
4501 | break; |
4502 | |
4503 | pStr[0] = ' '; |
4504 | |
4505 | if(Victim < MAX_CLIENTS) |
4506 | break; |
4507 | } |
4508 | pStr++; |
4509 | } |
4510 | } |
4511 | |
4512 | if(pStr[0] != ' ') |
4513 | { |
4514 | Error = true; |
4515 | } |
4516 | |
4517 | *pStr = 0; |
4518 | pStr++; |
4519 | |
4520 | if(Error) |
4521 | { |
4522 | SendChatTarget(To: ClientId, pText: "Invalid whisper" ); |
4523 | return; |
4524 | } |
4525 | |
4526 | if(Victim >= MAX_CLIENTS || !CheckClientId2(ClientId: Victim)) |
4527 | { |
4528 | char aBuf[256]; |
4529 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "No player with name \"%s\" found" , pName); |
4530 | SendChatTarget(To: ClientId, pText: aBuf); |
4531 | return; |
4532 | } |
4533 | |
4534 | WhisperId(ClientId, VictimId: Victim, pMessage: pStr); |
4535 | } |
4536 | |
4537 | void CGameContext::WhisperId(int ClientId, int VictimId, const char *pMessage) |
4538 | { |
4539 | if(!CheckClientId2(ClientId)) |
4540 | return; |
4541 | |
4542 | if(!CheckClientId2(ClientId: VictimId)) |
4543 | return; |
4544 | |
4545 | if(m_apPlayers[ClientId]) |
4546 | m_apPlayers[ClientId]->m_LastWhisperTo = VictimId; |
4547 | |
4548 | char aCensoredMessage[256]; |
4549 | CensorMessage(pCensoredMessage: aCensoredMessage, pMessage, Size: sizeof(aCensoredMessage)); |
4550 | |
4551 | char aBuf[256]; |
4552 | |
4553 | if(Server()->IsSixup(ClientId)) |
4554 | { |
4555 | protocol7::CNetMsg_Sv_Chat Msg; |
4556 | Msg.m_ClientId = ClientId; |
4557 | Msg.m_Mode = protocol7::CHAT_WHISPER; |
4558 | Msg.m_pMessage = aCensoredMessage; |
4559 | Msg.m_TargetId = VictimId; |
4560 | |
4561 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
4562 | } |
4563 | else if(GetClientVersion(ClientId) >= VERSION_DDNET_WHISPER) |
4564 | { |
4565 | CNetMsg_Sv_Chat Msg; |
4566 | Msg.m_Team = CHAT_WHISPER_SEND; |
4567 | Msg.m_ClientId = VictimId; |
4568 | Msg.m_pMessage = aCensoredMessage; |
4569 | if(g_Config.m_SvDemoChat) |
4570 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
4571 | else |
4572 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId); |
4573 | } |
4574 | else |
4575 | { |
4576 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "[→ %s] %s" , Server()->ClientName(ClientId: VictimId), aCensoredMessage); |
4577 | SendChatTarget(To: ClientId, pText: aBuf); |
4578 | } |
4579 | |
4580 | if(Server()->IsSixup(ClientId: VictimId)) |
4581 | { |
4582 | protocol7::CNetMsg_Sv_Chat Msg; |
4583 | Msg.m_ClientId = ClientId; |
4584 | Msg.m_Mode = protocol7::CHAT_WHISPER; |
4585 | Msg.m_pMessage = aCensoredMessage; |
4586 | Msg.m_TargetId = VictimId; |
4587 | |
4588 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: VictimId); |
4589 | } |
4590 | else if(GetClientVersion(ClientId: VictimId) >= VERSION_DDNET_WHISPER) |
4591 | { |
4592 | CNetMsg_Sv_Chat Msg2; |
4593 | Msg2.m_Team = CHAT_WHISPER_RECV; |
4594 | Msg2.m_ClientId = ClientId; |
4595 | Msg2.m_pMessage = aCensoredMessage; |
4596 | if(g_Config.m_SvDemoChat) |
4597 | Server()->SendPackMsg(pMsg: &Msg2, Flags: MSGFLAG_VITAL, ClientId: VictimId); |
4598 | else |
4599 | Server()->SendPackMsg(pMsg: &Msg2, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: VictimId); |
4600 | } |
4601 | else |
4602 | { |
4603 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "[← %s] %s" , Server()->ClientName(ClientId), aCensoredMessage); |
4604 | SendChatTarget(To: VictimId, pText: aBuf); |
4605 | } |
4606 | } |
4607 | |
4608 | void CGameContext::Converse(int ClientId, char *pStr) |
4609 | { |
4610 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
4611 | if(!pPlayer) |
4612 | return; |
4613 | |
4614 | if(ProcessSpamProtection(ClientId)) |
4615 | return; |
4616 | |
4617 | if(pPlayer->m_LastWhisperTo < 0) |
4618 | SendChatTarget(To: ClientId, pText: "You do not have an ongoing conversation. Whisper to someone to start one" ); |
4619 | else |
4620 | { |
4621 | WhisperId(ClientId, VictimId: pPlayer->m_LastWhisperTo, pMessage: pStr); |
4622 | } |
4623 | } |
4624 | |
4625 | bool CGameContext::IsVersionBanned(int Version) |
4626 | { |
4627 | char aVersion[16]; |
4628 | str_from_int(value: Version, dst&: aVersion); |
4629 | |
4630 | return str_in_list(list: g_Config.m_SvBannedVersions, delim: "," , needle: aVersion); |
4631 | } |
4632 | |
4633 | void CGameContext::List(int ClientId, const char *pFilter) |
4634 | { |
4635 | int Total = 0; |
4636 | char aBuf[256]; |
4637 | int Bufcnt = 0; |
4638 | if(pFilter[0]) |
4639 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Listing players with \"%s\" in name:" , pFilter); |
4640 | else |
4641 | str_copy(dst&: aBuf, src: "Listing all players:" ); |
4642 | SendChatTarget(To: ClientId, pText: aBuf); |
4643 | for(int i = 0; i < MAX_CLIENTS; i++) |
4644 | { |
4645 | if(m_apPlayers[i]) |
4646 | { |
4647 | Total++; |
4648 | const char *pName = Server()->ClientName(ClientId: i); |
4649 | if(str_utf8_find_nocase(haystack: pName, needle: pFilter) == NULL) |
4650 | continue; |
4651 | if(Bufcnt + str_length(str: pName) + 4 > 256) |
4652 | { |
4653 | SendChatTarget(To: ClientId, pText: aBuf); |
4654 | Bufcnt = 0; |
4655 | } |
4656 | if(Bufcnt != 0) |
4657 | { |
4658 | str_format(buffer: &aBuf[Bufcnt], buffer_size: sizeof(aBuf) - Bufcnt, format: ", %s" , pName); |
4659 | Bufcnt += 2 + str_length(str: pName); |
4660 | } |
4661 | else |
4662 | { |
4663 | str_copy(dst: &aBuf[Bufcnt], src: pName, dst_size: sizeof(aBuf) - Bufcnt); |
4664 | Bufcnt += str_length(str: pName); |
4665 | } |
4666 | } |
4667 | } |
4668 | if(Bufcnt != 0) |
4669 | SendChatTarget(To: ClientId, pText: aBuf); |
4670 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d players online" , Total); |
4671 | SendChatTarget(To: ClientId, pText: aBuf); |
4672 | } |
4673 | |
4674 | int CGameContext::GetClientVersion(int ClientId) const |
4675 | { |
4676 | return Server()->GetClientVersion(ClientId); |
4677 | } |
4678 | |
4679 | CClientMask CGameContext::ClientsMaskExcludeClientVersionAndHigher(int Version) const |
4680 | { |
4681 | CClientMask Mask; |
4682 | for(int i = 0; i < MAX_CLIENTS; ++i) |
4683 | { |
4684 | if(GetClientVersion(ClientId: i) >= Version) |
4685 | continue; |
4686 | Mask.set(position: i); |
4687 | } |
4688 | return Mask; |
4689 | } |
4690 | |
4691 | bool CGameContext::PlayerModerating() const |
4692 | { |
4693 | return std::any_of(first: std::begin(arr: m_apPlayers), last: std::end(arr: m_apPlayers), pred: [](const CPlayer *pPlayer) { return pPlayer && pPlayer->m_Moderating; }); |
4694 | } |
4695 | |
4696 | void CGameContext::ForceVote(int EnforcerId, bool Success) |
4697 | { |
4698 | // check if there is a vote running |
4699 | if(!m_VoteCloseTime) |
4700 | return; |
4701 | |
4702 | m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN; |
4703 | m_VoteEnforcer = EnforcerId; |
4704 | |
4705 | char aBuf[256]; |
4706 | const char *pOption = Success ? "yes" : "no" ; |
4707 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "authorized player forced vote %s" , pOption); |
4708 | SendChatTarget(To: -1, pText: aBuf); |
4709 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "forcing vote %s" , pOption); |
4710 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "server" , pStr: aBuf); |
4711 | } |
4712 | |
4713 | bool CGameContext::RateLimitPlayerVote(int ClientId) |
4714 | { |
4715 | int64_t Now = Server()->Tick(); |
4716 | int64_t TickSpeed = Server()->TickSpeed(); |
4717 | CPlayer *pPlayer = m_apPlayers[ClientId]; |
4718 | |
4719 | if(g_Config.m_SvRconVote && !Server()->GetAuthedState(ClientId)) |
4720 | { |
4721 | SendChatTarget(To: ClientId, pText: "You can only vote after logging in." ); |
4722 | return true; |
4723 | } |
4724 | |
4725 | if(g_Config.m_SvDnsblVote && Server()->DistinctClientCount() > 1) |
4726 | { |
4727 | if(m_pServer->DnsblPending(ClientId)) |
4728 | { |
4729 | SendChatTarget(To: ClientId, pText: "You are not allowed to vote because we're currently checking for VPNs. Try again in ~30 seconds." ); |
4730 | return true; |
4731 | } |
4732 | else if(m_pServer->DnsblBlack(ClientId)) |
4733 | { |
4734 | SendChatTarget(To: ClientId, pText: "You are not allowed to vote because you appear to be using a VPN. Try connecting without a VPN or contacting an admin if you think this is a mistake." ); |
4735 | return true; |
4736 | } |
4737 | } |
4738 | |
4739 | if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + TickSpeed * 3 > Now) |
4740 | return true; |
4741 | |
4742 | pPlayer->m_LastVoteTry = Now; |
4743 | if(m_VoteCloseTime) |
4744 | { |
4745 | SendChatTarget(To: ClientId, pText: "Wait for current vote to end before calling a new one." ); |
4746 | return true; |
4747 | } |
4748 | |
4749 | if(Now < pPlayer->m_FirstVoteTick) |
4750 | { |
4751 | char aBuf[64]; |
4752 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You must wait %d seconds before making your first vote." , (int)((pPlayer->m_FirstVoteTick - Now) / TickSpeed) + 1); |
4753 | SendChatTarget(To: ClientId, pText: aBuf); |
4754 | return true; |
4755 | } |
4756 | |
4757 | int TimeLeft = pPlayer->m_LastVoteCall + TickSpeed * g_Config.m_SvVoteDelay - Now; |
4758 | if(pPlayer->m_LastVoteCall && TimeLeft > 0) |
4759 | { |
4760 | char aChatmsg[64]; |
4761 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "You must wait %d seconds before making another vote." , (int)(TimeLeft / TickSpeed) + 1); |
4762 | SendChatTarget(To: ClientId, pText: aChatmsg); |
4763 | return true; |
4764 | } |
4765 | |
4766 | NETADDR Addr; |
4767 | Server()->GetClientAddr(ClientId, pAddr: &Addr); |
4768 | int VoteMuted = 0; |
4769 | for(int i = 0; i < m_NumVoteMutes && !VoteMuted; i++) |
4770 | if(!net_addr_comp_noport(a: &Addr, b: &m_aVoteMutes[i].m_Addr)) |
4771 | VoteMuted = (m_aVoteMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); |
4772 | for(int i = 0; i < m_NumMutes && VoteMuted == 0; i++) |
4773 | { |
4774 | if(!net_addr_comp_noport(a: &Addr, b: &m_aMutes[i].m_Addr)) |
4775 | VoteMuted = (m_aMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); |
4776 | } |
4777 | if(VoteMuted > 0) |
4778 | { |
4779 | char aChatmsg[64]; |
4780 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "You are not permitted to vote for the next %d seconds." , VoteMuted); |
4781 | SendChatTarget(To: ClientId, pText: aChatmsg); |
4782 | return true; |
4783 | } |
4784 | return false; |
4785 | } |
4786 | |
4787 | bool CGameContext::RateLimitPlayerMapVote(int ClientId) const |
4788 | { |
4789 | if(!Server()->GetAuthedState(ClientId) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) |
4790 | { |
4791 | char aChatmsg[512] = {0}; |
4792 | str_format(buffer: aChatmsg, buffer_size: sizeof(aChatmsg), format: "There's a %d second delay between map-votes, please wait %d seconds." , |
4793 | g_Config.m_SvVoteMapTimeDelay, (int)((m_LastMapVote + g_Config.m_SvVoteMapTimeDelay * time_freq() - time_get()) / time_freq())); |
4794 | SendChatTarget(To: ClientId, pText: aChatmsg); |
4795 | return true; |
4796 | } |
4797 | return false; |
4798 | } |
4799 | |
4800 | void CGameContext::OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int Id) |
4801 | { |
4802 | if(BufSize <= 0) |
4803 | return; |
4804 | |
4805 | aBuf[0] = '\0'; |
4806 | |
4807 | if(!m_apPlayers[Id]) |
4808 | return; |
4809 | |
4810 | char aCSkinName[64]; |
4811 | |
4812 | CTeeInfo &TeeInfo = m_apPlayers[Id]->m_TeeInfos; |
4813 | |
4814 | char aJsonSkin[400]; |
4815 | aJsonSkin[0] = '\0'; |
4816 | |
4817 | if(!Server()->IsSixup(ClientId: Id)) |
4818 | { |
4819 | // 0.6 |
4820 | if(TeeInfo.m_UseCustomColor) |
4821 | { |
4822 | str_format(buffer: aJsonSkin, buffer_size: sizeof(aJsonSkin), |
4823 | format: "\"name\":\"%s\"," |
4824 | "\"color_body\":%d," |
4825 | "\"color_feet\":%d" , |
4826 | EscapeJson(pBuffer: aCSkinName, BufferSize: sizeof(aCSkinName), pString: TeeInfo.m_aSkinName), |
4827 | TeeInfo.m_ColorBody, |
4828 | TeeInfo.m_ColorFeet); |
4829 | } |
4830 | else |
4831 | { |
4832 | str_format(buffer: aJsonSkin, buffer_size: sizeof(aJsonSkin), |
4833 | format: "\"name\":\"%s\"" , |
4834 | EscapeJson(pBuffer: aCSkinName, BufferSize: sizeof(aCSkinName), pString: TeeInfo.m_aSkinName)); |
4835 | } |
4836 | } |
4837 | else |
4838 | { |
4839 | const char *apPartNames[protocol7::NUM_SKINPARTS] = {"body" , "marking" , "decoration" , "hands" , "feet" , "eyes" }; |
4840 | char aPartBuf[64]; |
4841 | |
4842 | for(int i = 0; i < protocol7::NUM_SKINPARTS; ++i) |
4843 | { |
4844 | str_format(buffer: aPartBuf, buffer_size: sizeof(aPartBuf), |
4845 | format: "%s\"%s\":{" |
4846 | "\"name\":\"%s\"" , |
4847 | i == 0 ? "" : "," , |
4848 | apPartNames[i], |
4849 | EscapeJson(pBuffer: aCSkinName, BufferSize: sizeof(aCSkinName), pString: TeeInfo.m_apSkinPartNames[i])); |
4850 | |
4851 | str_append(dst&: aJsonSkin, src: aPartBuf); |
4852 | |
4853 | if(TeeInfo.m_aUseCustomColors[i]) |
4854 | { |
4855 | str_format(buffer: aPartBuf, buffer_size: sizeof(aPartBuf), |
4856 | format: ",\"color\":%d" , |
4857 | TeeInfo.m_aSkinPartColors[i]); |
4858 | str_append(dst&: aJsonSkin, src: aPartBuf); |
4859 | } |
4860 | str_append(dst&: aJsonSkin, src: "}" ); |
4861 | } |
4862 | } |
4863 | |
4864 | const int Team = m_pController->IsTeamPlay() ? m_apPlayers[Id]->GetTeam() : m_apPlayers[Id]->GetTeam() == TEAM_SPECTATORS ? -1 : GetDDRaceTeam(ClientId: Id); |
4865 | str_format(buffer: aBuf, buffer_size: BufSize, |
4866 | format: ",\"skin\":{" |
4867 | "%s" |
4868 | "}," |
4869 | "\"afk\":%s," |
4870 | "\"team\":%d" , |
4871 | aJsonSkin, |
4872 | JsonBool(Bool: m_apPlayers[Id]->IsAfk()), |
4873 | Team); |
4874 | } |
4875 | |