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