made rcon auto completion serverside. Closes #97
[twcon.git] / src / game / server / gamecontext.cpp
blob6b029bbdfb162fb62509a9b172e7b1d18f49a6db
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 <new>
4 #include <base/math.h>
5 #include <engine/shared/config.h>
6 #include <engine/map.h>
7 #include <engine/console.h>
8 #include "gamecontext.h"
9 #include <game/version.h>
10 #include <game/collision.h>
11 #include <game/gamecore.h>
12 #include "gamemodes/dm.h"
13 #include "gamemodes/tdm.h"
14 #include "gamemodes/ctf.h"
15 #include "gamemodes/mod.h"
17 enum
19 RESET,
20 NO_RESET
23 void CGameContext::Construct(int Resetting)
25 m_Resetting = 0;
26 m_pServer = 0;
28 for(int i = 0; i < MAX_CLIENTS; i++)
29 m_apPlayers[i] = 0;
31 m_pController = 0;
32 m_VoteCloseTime = 0;
33 m_pVoteOptionFirst = 0;
34 m_pVoteOptionLast = 0;
35 m_NumVoteOptions = 0;
37 if(Resetting==NO_RESET)
38 m_pVoteOptionHeap = new CHeap();
41 CGameContext::CGameContext(int Resetting)
43 Construct(Resetting);
46 CGameContext::CGameContext()
48 Construct(NO_RESET);
51 CGameContext::~CGameContext()
53 for(int i = 0; i < MAX_CLIENTS; i++)
54 delete m_apPlayers[i];
55 if(!m_Resetting)
56 delete m_pVoteOptionHeap;
59 void CGameContext::Clear()
61 CHeap *pVoteOptionHeap = m_pVoteOptionHeap;
62 CVoteOptionServer *pVoteOptionFirst = m_pVoteOptionFirst;
63 CVoteOptionServer *pVoteOptionLast = m_pVoteOptionLast;
64 int NumVoteOptions = m_NumVoteOptions;
65 CTuningParams Tuning = m_Tuning;
67 m_Resetting = true;
68 this->~CGameContext();
69 mem_zero(this, sizeof(*this));
70 new (this) CGameContext(RESET);
72 m_pVoteOptionHeap = pVoteOptionHeap;
73 m_pVoteOptionFirst = pVoteOptionFirst;
74 m_pVoteOptionLast = pVoteOptionLast;
75 m_NumVoteOptions = NumVoteOptions;
76 m_Tuning = Tuning;
80 class CCharacter *CGameContext::GetPlayerChar(int ClientID)
82 if(ClientID < 0 || ClientID >= MAX_CLIENTS || !m_apPlayers[ClientID])
83 return 0;
84 return m_apPlayers[ClientID]->GetCharacter();
87 void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount)
89 float a = 3 * 3.14159f / 2 + Angle;
90 //float a = get_angle(dir);
91 float s = a-pi/3;
92 float e = a+pi/3;
93 for(int i = 0; i < Amount; i++)
95 float f = mix(s, e, float(i+1)/float(Amount+2));
96 CNetEvent_DamageInd *pEvent = (CNetEvent_DamageInd *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(CNetEvent_DamageInd));
97 if(pEvent)
99 pEvent->m_X = (int)Pos.x;
100 pEvent->m_Y = (int)Pos.y;
101 pEvent->m_Angle = (int)(f*256.0f);
106 void CGameContext::CreateHammerHit(vec2 Pos)
108 // create the event
109 CNetEvent_HammerHit *pEvent = (CNetEvent_HammerHit *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(CNetEvent_HammerHit));
110 if(pEvent)
112 pEvent->m_X = (int)Pos.x;
113 pEvent->m_Y = (int)Pos.y;
118 void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamage)
120 // create the event
121 CNetEvent_Explosion *pEvent = (CNetEvent_Explosion *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(CNetEvent_Explosion));
122 if(pEvent)
124 pEvent->m_X = (int)Pos.x;
125 pEvent->m_Y = (int)Pos.y;
128 if (!NoDamage)
130 // deal damage
131 CCharacter *apEnts[MAX_CLIENTS];
132 float Radius = 135.0f;
133 float InnerRadius = 48.0f;
134 int Num = m_World.FindEntities(Pos, Radius, (CEntity**)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
135 for(int i = 0; i < Num; i++)
137 vec2 Diff = apEnts[i]->m_Pos - Pos;
138 vec2 ForceDir(0,1);
139 float l = length(Diff);
140 if(l)
141 ForceDir = normalize(Diff);
142 l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f);
143 float Dmg = 6 * l;
144 if((int)Dmg)
145 apEnts[i]->TakeDamage(ForceDir*Dmg*2, (int)Dmg, Owner, Weapon);
151 void create_smoke(vec2 Pos)
153 // create the event
154 EV_EXPLOSION *pEvent = (EV_EXPLOSION *)events.create(EVENT_SMOKE, sizeof(EV_EXPLOSION));
155 if(pEvent)
157 pEvent->x = (int)Pos.x;
158 pEvent->y = (int)Pos.y;
162 void CGameContext::CreatePlayerSpawn(vec2 Pos)
164 // create the event
165 CNetEvent_Spawn *ev = (CNetEvent_Spawn *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(CNetEvent_Spawn));
166 if(ev)
168 ev->m_X = (int)Pos.x;
169 ev->m_Y = (int)Pos.y;
173 void CGameContext::CreateDeath(vec2 Pos, int ClientID)
175 // create the event
176 CNetEvent_Death *pEvent = (CNetEvent_Death *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(CNetEvent_Death));
177 if(pEvent)
179 pEvent->m_X = (int)Pos.x;
180 pEvent->m_Y = (int)Pos.y;
181 pEvent->m_ClientID = ClientID;
185 void CGameContext::CreateSound(vec2 Pos, int Sound, int Mask)
187 if (Sound < 0)
188 return;
190 // create a sound
191 CNetEvent_SoundWorld *pEvent = (CNetEvent_SoundWorld *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(CNetEvent_SoundWorld), Mask);
192 if(pEvent)
194 pEvent->m_X = (int)Pos.x;
195 pEvent->m_Y = (int)Pos.y;
196 pEvent->m_SoundID = Sound;
200 void CGameContext::CreateSoundGlobal(int Sound, int Target)
202 if (Sound < 0)
203 return;
205 CNetMsg_Sv_SoundGlobal Msg;
206 Msg.m_SoundID = Sound;
207 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, Target);
211 void CGameContext::SendChatTarget(int To, const char *pText)
213 CNetMsg_Sv_Chat Msg;
214 Msg.m_Team = 0;
215 Msg.m_ClientID = -1;
216 Msg.m_pMessage = pText;
217 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, To);
221 void CGameContext::SendChat(int ChatterClientID, int Team, const char *pText)
223 char aBuf[256];
224 if(ChatterClientID >= 0 && ChatterClientID < MAX_CLIENTS)
225 str_format(aBuf, sizeof(aBuf), "%d:%d:%s: %s", ChatterClientID, Team, Server()->ClientName(ChatterClientID), pText);
226 else
227 str_format(aBuf, sizeof(aBuf), "*** %s", pText);
228 Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, Team!=CHAT_ALL?"teamchat":"chat", aBuf);
230 if(Team == CHAT_ALL)
232 CNetMsg_Sv_Chat Msg;
233 Msg.m_Team = 0;
234 Msg.m_ClientID = ChatterClientID;
235 Msg.m_pMessage = pText;
236 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
238 else
240 CNetMsg_Sv_Chat Msg;
241 Msg.m_Team = 1;
242 Msg.m_ClientID = ChatterClientID;
243 Msg.m_pMessage = pText;
245 // pack one for the recording only
246 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NOSEND, -1);
248 // send to the clients
249 for(int i = 0; i < MAX_CLIENTS; i++)
251 if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() == Team)
252 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD, i);
257 void CGameContext::SendEmoticon(int ClientID, int Emoticon)
259 CNetMsg_Sv_Emoticon Msg;
260 Msg.m_ClientID = ClientID;
261 Msg.m_Emoticon = Emoticon;
262 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
265 void CGameContext::SendWeaponPickup(int ClientID, int Weapon)
267 CNetMsg_Sv_WeaponPickup Msg;
268 Msg.m_Weapon = Weapon;
269 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
273 void CGameContext::SendBroadcast(const char *pText, int ClientID)
275 CNetMsg_Sv_Broadcast Msg;
276 Msg.m_pMessage = pText;
277 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
281 void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char *pReason)
283 // check if a vote is already running
284 if(m_VoteCloseTime)
285 return;
287 // reset votes
288 m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
289 for(int i = 0; i < MAX_CLIENTS; i++)
291 if(m_apPlayers[i])
293 m_apPlayers[i]->m_Vote = 0;
294 m_apPlayers[i]->m_VotePos = 0;
298 // start vote
299 m_VoteCloseTime = time_get() + time_freq()*25;
300 str_copy(m_aVoteDescription, pDesc, sizeof(m_aVoteDescription));
301 str_copy(m_aVoteCommand, pCommand, sizeof(m_aVoteCommand));
302 str_copy(m_aVoteReason, pReason, sizeof(m_aVoteReason));
303 SendVoteSet(-1);
304 m_VoteUpdate = true;
308 void CGameContext::EndVote()
310 m_VoteCloseTime = 0;
311 SendVoteSet(-1);
314 void CGameContext::SendVoteSet(int ClientID)
316 CNetMsg_Sv_VoteSet Msg;
317 if(m_VoteCloseTime)
319 Msg.m_Timeout = (m_VoteCloseTime-time_get())/time_freq();
320 Msg.m_pDescription = m_aVoteDescription;
321 Msg.m_pReason = m_aVoteReason;
323 else
325 Msg.m_Timeout = 0;
326 Msg.m_pDescription = "";
327 Msg.m_pReason = "";
329 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
332 void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No)
334 CNetMsg_Sv_VoteStatus Msg = {0};
335 Msg.m_Total = Total;
336 Msg.m_Yes = Yes;
337 Msg.m_No = No;
338 Msg.m_Pass = Total - (Yes+No);
340 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
344 void CGameContext::AbortVoteKickOnDisconnect(int ClientID)
346 if(m_VoteCloseTime && ((!str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientID) ||
347 (!str_comp_num(m_aVoteCommand, "set_team ", 9) && str_toint(&m_aVoteCommand[9]) == ClientID)))
348 m_VoteCloseTime = -1;
352 void CGameContext::CheckPureTuning()
354 // might not be created yet during start up
355 if(!m_pController)
356 return;
358 if( str_comp(m_pController->m_pGameType, "DM")==0 ||
359 str_comp(m_pController->m_pGameType, "TDM")==0 ||
360 str_comp(m_pController->m_pGameType, "CTF")==0)
362 CTuningParams p;
363 if(mem_comp(&p, &m_Tuning, sizeof(p)) != 0)
365 Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "resetting tuning due to pure server");
366 m_Tuning = p;
371 void CGameContext::SendTuningParams(int ClientID)
373 CheckPureTuning();
375 CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
376 int *pParams = (int *)&m_Tuning;
377 for(unsigned i = 0; i < sizeof(m_Tuning)/sizeof(int); i++)
378 Msg.AddInt(pParams[i]);
379 Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
382 void CGameContext::OnTick()
384 // check tuning
385 CheckPureTuning();
387 // copy tuning
388 m_World.m_Core.m_Tuning = m_Tuning;
389 m_World.Tick();
391 //if(world.paused) // make sure that the game object always updates
392 m_pController->Tick();
394 for(int i = 0; i < MAX_CLIENTS; i++)
396 if(m_apPlayers[i])
398 m_apPlayers[i]->Tick();
399 m_apPlayers[i]->PostTick();
403 // update voting
404 if(m_VoteCloseTime)
406 // abort the kick-vote on player-leave
407 if(m_VoteCloseTime == -1)
409 SendChat(-1, CGameContext::CHAT_ALL, "Vote aborted");
410 EndVote();
412 else
414 int Total = 0, Yes = 0, No = 0;
415 if(m_VoteUpdate)
417 // count votes
418 char aaBuf[MAX_CLIENTS][NETADDR_MAXSTRSIZE] = {{0}};
419 for(int i = 0; i < MAX_CLIENTS; i++)
420 if(m_apPlayers[i])
421 Server()->GetClientAddr(i, aaBuf[i], NETADDR_MAXSTRSIZE);
422 bool aVoteChecked[MAX_CLIENTS] = {0};
423 for(int i = 0; i < MAX_CLIENTS; i++)
425 if(!m_apPlayers[i] || m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS || aVoteChecked[i]) // don't count in votes by spectators
426 continue;
428 int ActVote = m_apPlayers[i]->m_Vote;
429 int ActVotePos = m_apPlayers[i]->m_VotePos;
431 // check for more players with the same ip (only use the vote of the one who voted first)
432 for(int j = i+1; j < MAX_CLIENTS; ++j)
434 if(!m_apPlayers[j] || aVoteChecked[j] || str_comp(aaBuf[j], aaBuf[i]))
435 continue;
437 aVoteChecked[j] = true;
438 if(m_apPlayers[j]->m_Vote && (!ActVote || ActVotePos > m_apPlayers[j]->m_VotePos))
440 ActVote = m_apPlayers[j]->m_Vote;
441 ActVotePos = m_apPlayers[j]->m_VotePos;
445 Total++;
446 if(ActVote > 0)
447 Yes++;
448 else if(ActVote < 0)
449 No++;
452 if(Yes >= Total/2+1)
453 m_VoteEnforce = VOTE_ENFORCE_YES;
454 else if(No >= (Total+1)/2)
455 m_VoteEnforce = VOTE_ENFORCE_NO;
458 if(m_VoteEnforce == VOTE_ENFORCE_YES)
460 Console()->ExecuteLine(m_aVoteCommand);
461 EndVote();
462 SendChat(-1, CGameContext::CHAT_ALL, "Vote passed");
464 if(m_apPlayers[m_VoteCreator])
465 m_apPlayers[m_VoteCreator]->m_LastVoteCall = 0;
467 else if(m_VoteEnforce == VOTE_ENFORCE_NO || time_get() > m_VoteCloseTime)
469 EndVote();
470 SendChat(-1, CGameContext::CHAT_ALL, "Vote failed");
472 else if(m_VoteUpdate)
474 m_VoteUpdate = false;
475 SendVoteStatus(-1, Total, Yes, No);
481 #ifdef CONF_DEBUG
482 if(g_Config.m_DbgDummies)
484 for(int i = 0; i < g_Config.m_DbgDummies ; i++)
486 CNetObj_PlayerInput Input = {0};
487 Input.m_Direction = (i&1)?-1:1;
488 m_apPlayers[MAX_CLIENTS-i-1]->OnPredictedInput(&Input);
491 #endif
494 // Server hooks
495 void CGameContext::OnClientDirectInput(int ClientID, void *pInput)
497 if(!m_World.m_Paused)
498 m_apPlayers[ClientID]->OnDirectInput((CNetObj_PlayerInput *)pInput);
501 void CGameContext::OnClientPredictedInput(int ClientID, void *pInput)
503 if(!m_World.m_Paused)
504 m_apPlayers[ClientID]->OnPredictedInput((CNetObj_PlayerInput *)pInput);
507 void CGameContext::OnClientEnter(int ClientID)
509 //world.insert_entity(&players[client_id]);
510 m_apPlayers[ClientID]->Respawn();
511 char aBuf[512];
512 str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam()));
513 SendChat(-1, CGameContext::CHAT_ALL, aBuf);
515 str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' team=%d", ClientID, Server()->ClientName(ClientID), m_apPlayers[ClientID]->GetTeam());
516 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
518 m_VoteUpdate = true;
521 void CGameContext::OnClientConnected(int ClientID)
523 // Check which team the player should be on
524 const int StartTeam = g_Config.m_SvTournamentMode ? TEAM_SPECTATORS : m_pController->GetAutoTeam(ClientID);
526 m_apPlayers[ClientID] = new(ClientID) CPlayer(this, ClientID, StartTeam);
527 //players[client_id].init(client_id);
528 //players[client_id].client_id = client_id;
530 (void)m_pController->CheckTeamBalance();
532 #ifdef CONF_DEBUG
533 if(g_Config.m_DbgDummies)
535 if(ClientID >= MAX_CLIENTS-g_Config.m_DbgDummies)
536 return;
538 #endif
540 // send active vote
541 if(m_VoteCloseTime)
542 SendVoteSet(ClientID);
544 // send motd
545 CNetMsg_Sv_Motd Msg;
546 Msg.m_pMessage = g_Config.m_SvMotd;
547 Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID);
550 void CGameContext::OnClientDrop(int ClientID, const char *pReason)
552 AbortVoteKickOnDisconnect(ClientID);
553 m_apPlayers[ClientID]->OnDisconnect(pReason);
554 delete m_apPlayers[ClientID];
555 m_apPlayers[ClientID] = 0;
557 (void)m_pController->CheckTeamBalance();
558 m_VoteUpdate = true;
560 // update spectator modes
561 for(int i = 0; i < MAX_CLIENTS; ++i)
563 if(m_apPlayers[i] && m_apPlayers[i]->m_SpectatorID == ClientID)
564 m_apPlayers[i]->m_SpectatorID = SPEC_FREEVIEW;
568 void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID)
570 void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgID, pUnpacker);
571 CPlayer *pPlayer = m_apPlayers[ClientID];
573 if(!pRawMsg)
575 char aBuf[256];
576 str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgID), MsgID, m_NetObjHandler.FailedMsgOn());
577 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf);
578 return;
581 if(MsgID == NETMSGTYPE_CL_SAY)
583 CNetMsg_Cl_Say *pMsg = (CNetMsg_Cl_Say *)pRawMsg;
584 int Team = pMsg->m_Team;
585 if(Team)
586 Team = pPlayer->GetTeam();
587 else
588 Team = CGameContext::CHAT_ALL;
590 if(g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat+Server()->TickSpeed() > Server()->Tick())
591 return;
593 pPlayer->m_LastChat = Server()->Tick();
595 // check for invalid chars
596 unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage;
597 while (*pMessage)
599 if(*pMessage < 32)
600 *pMessage = ' ';
601 pMessage++;
604 SendChat(ClientID, Team, pMsg->m_pMessage);
606 else if(MsgID == NETMSGTYPE_CL_CALLVOTE)
608 if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry+Server()->TickSpeed()*3 > Server()->Tick())
609 return;
611 int64 Now = Server()->Tick();
612 pPlayer->m_LastVoteTry = Now;
613 if(pPlayer->GetTeam() == TEAM_SPECTATORS)
615 SendChatTarget(ClientID, "Spectators aren't allowed to start a vote.");
616 return;
619 if(m_VoteCloseTime)
621 SendChatTarget(ClientID, "Wait for current vote to end before calling a new one.");
622 return;
625 int Timeleft = pPlayer->m_LastVoteCall + Server()->TickSpeed()*60 - Now;
626 if(pPlayer->m_LastVoteCall && Timeleft > 0)
628 char aChatmsg[512] = {0};
629 str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote", (Timeleft/Server()->TickSpeed())+1);
630 SendChatTarget(ClientID, aChatmsg);
631 return;
634 char aChatmsg[512] = {0};
635 char aDesc[VOTE_DESC_LENGTH] = {0};
636 char aCmd[VOTE_CMD_LENGTH] = {0};
637 CNetMsg_Cl_CallVote *pMsg = (CNetMsg_Cl_CallVote *)pRawMsg;
638 const char *pReason = pMsg->m_Reason[0] ? pMsg->m_Reason : "No reason given";
640 if(str_comp_nocase(pMsg->m_Type, "option") == 0)
642 CVoteOptionServer *pOption = m_pVoteOptionFirst;
643 while(pOption)
645 if(str_comp_nocase(pMsg->m_Value, pOption->m_aDescription) == 0)
647 str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", Server()->ClientName(ClientID),
648 pOption->m_aDescription, pReason);
649 str_format(aDesc, sizeof(aDesc), "%s", pOption->m_aDescription);
650 str_format(aCmd, sizeof(aCmd), "%s", pOption->m_aCommand);
651 break;
654 pOption = pOption->m_pNext;
657 if(!pOption)
659 str_format(aChatmsg, sizeof(aChatmsg), "'%s' isn't an option on this server", pMsg->m_Value);
660 SendChatTarget(ClientID, aChatmsg);
661 return;
664 else if(str_comp_nocase(pMsg->m_Type, "kick") == 0)
666 if(!g_Config.m_SvVoteKick)
668 SendChatTarget(ClientID, "Server does not allow voting to kick players");
669 return;
672 if(g_Config.m_SvVoteKickMin)
674 int PlayerNum = 0;
675 for(int i = 0; i < MAX_CLIENTS; ++i)
676 if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
677 ++PlayerNum;
679 if(PlayerNum < g_Config.m_SvVoteKickMin)
681 str_format(aChatmsg, sizeof(aChatmsg), "Kick voting requires %d players on the server", g_Config.m_SvVoteKickMin);
682 SendChatTarget(ClientID, aChatmsg);
683 return;
687 int KickID = str_toint(pMsg->m_Value);
688 if(KickID < 0 || KickID >= MAX_CLIENTS || !m_apPlayers[KickID])
690 SendChatTarget(ClientID, "Invalid client id to kick");
691 return;
693 if(KickID == ClientID)
695 SendChatTarget(ClientID, "You can't kick yourself");
696 return;
698 if(Server()->IsAuthed(KickID))
700 SendChatTarget(ClientID, "You can't kick admins");
701 char aBufKick[128];
702 str_format(aBufKick, sizeof(aBufKick), "'%s' called for vote to kick you", Server()->ClientName(ClientID));
703 SendChatTarget(KickID, aBufKick);
704 return;
707 str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to kick '%s' (%s)", Server()->ClientName(ClientID), Server()->ClientName(KickID), pReason);
708 str_format(aDesc, sizeof(aDesc), "Kick '%s'", Server()->ClientName(KickID));
709 if (!g_Config.m_SvVoteKickBantime)
710 str_format(aCmd, sizeof(aCmd), "kick %d Kicked by vote", KickID);
711 else
713 char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
714 Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr));
715 str_format(aCmd, sizeof(aCmd), "ban %s %d Banned by vote", aAddrStr, g_Config.m_SvVoteKickBantime);
716 Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aCmd);
719 else if(str_comp_nocase(pMsg->m_Type, "spectate") == 0)
721 if(!g_Config.m_SvVoteSpectate)
723 SendChatTarget(ClientID, "Server does not allow voting to move players to spectators");
724 return;
727 int SpectateID = str_toint(pMsg->m_Value);
728 if(SpectateID < 0 || SpectateID >= MAX_CLIENTS || !m_apPlayers[SpectateID] || m_apPlayers[SpectateID]->GetTeam() == TEAM_SPECTATORS)
730 SendChatTarget(ClientID, "Invalid client id to move");
731 return;
733 if(SpectateID == ClientID)
735 SendChatTarget(ClientID, "You can't move yourself");
736 return;
739 str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to move '%s' to spectators (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), pReason);
740 str_format(aDesc, sizeof(aDesc), "move '%s' to spectators", Server()->ClientName(SpectateID));
741 str_format(aCmd, sizeof(aCmd), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay);
744 if(aCmd[0])
746 SendChat(-1, CGameContext::CHAT_ALL, aChatmsg);
747 StartVote(aDesc, aCmd, pReason);
748 pPlayer->m_Vote = 1;
749 pPlayer->m_VotePos = m_VotePos = 1;
750 m_VoteCreator = ClientID;
751 pPlayer->m_LastVoteCall = Now;
754 else if(MsgID == NETMSGTYPE_CL_VOTE)
756 if(!m_VoteCloseTime)
757 return;
759 if(pPlayer->m_Vote == 0)
761 CNetMsg_Cl_Vote *pMsg = (CNetMsg_Cl_Vote *)pRawMsg;
762 if(!pMsg->m_Vote)
763 return;
765 pPlayer->m_Vote = pMsg->m_Vote;
766 pPlayer->m_VotePos = ++m_VotePos;
767 m_VoteUpdate = true;
770 else if (MsgID == NETMSGTYPE_CL_SETTEAM && !m_World.m_Paused)
772 CNetMsg_Cl_SetTeam *pMsg = (CNetMsg_Cl_SetTeam *)pRawMsg;
774 if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam+Server()->TickSpeed()*3 > Server()->Tick()))
775 return;
777 if(pPlayer->m_TeamChangeTick > Server()->Tick())
779 pPlayer->m_LastSetTeam = Server()->Tick();
780 int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick())/Server()->TickSpeed();
781 char aBuf[128];
782 str_format(aBuf, sizeof(aBuf), "Time to wait before changing team: %02d:%02d", TimeLeft/60, TimeLeft%60);
783 SendBroadcast(aBuf, ClientID);
784 return;
787 // Switch team on given client and kill/respawn him
788 if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID))
790 if(m_pController->CanChangeTeam(pPlayer, pMsg->m_Team))
792 pPlayer->m_LastSetTeam = Server()->Tick();
793 if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS)
794 m_VoteUpdate = true;
795 pPlayer->SetTeam(pMsg->m_Team);
796 (void)m_pController->CheckTeamBalance();
797 pPlayer->m_TeamChangeTick = Server()->Tick();
799 else
800 SendBroadcast("Teams must be balanced, please join other team", ClientID);
802 else
804 char aBuf[128];
805 str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", g_Config.m_SvMaxClients-g_Config.m_SvSpectatorSlots);
806 SendBroadcast(aBuf, ClientID);
809 else if (MsgID == NETMSGTYPE_CL_SETSPECTATORMODE && !m_World.m_Paused)
811 CNetMsg_Cl_SetSpectatorMode *pMsg = (CNetMsg_Cl_SetSpectatorMode *)pRawMsg;
813 if(pPlayer->GetTeam() != TEAM_SPECTATORS || pPlayer->m_SpectatorID == pMsg->m_SpectatorID || ClientID == pMsg->m_SpectatorID ||
814 (g_Config.m_SvSpamprotection && pPlayer->m_LastSetSpectatorMode && pPlayer->m_LastSetSpectatorMode+Server()->TickSpeed()*3 > Server()->Tick()))
815 return;
817 pPlayer->m_LastSetSpectatorMode = Server()->Tick();
818 if(pMsg->m_SpectatorID != SPEC_FREEVIEW && (!m_apPlayers[pMsg->m_SpectatorID] || m_apPlayers[pMsg->m_SpectatorID]->GetTeam() == TEAM_SPECTATORS))
819 SendChatTarget(ClientID, "Invalid spectator id used");
820 else
821 pPlayer->m_SpectatorID = pMsg->m_SpectatorID;
823 else if (MsgID == NETMSGTYPE_CL_STARTINFO)
825 if(pPlayer->m_IsReady)
826 return;
828 CNetMsg_Cl_StartInfo *pMsg = (CNetMsg_Cl_StartInfo *)pRawMsg;
829 pPlayer->m_LastChangeInfo = Server()->Tick();
831 // set start infos
832 Server()->SetClientName(ClientID, pMsg->m_pName);
833 Server()->SetClientClan(ClientID, pMsg->m_pClan);
834 Server()->SetClientCountry(ClientID, pMsg->m_Country);
835 str_copy(pPlayer->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_SkinName));
836 pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor;
837 pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody;
838 pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet;
839 m_pController->OnPlayerInfoChange(pPlayer);
841 // send vote options
842 CNetMsg_Sv_VoteClearOptions ClearMsg;
843 Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientID);
845 CNetMsg_Sv_VoteOptionListAdd OptionMsg;
846 int NumOptions = 0;
847 OptionMsg.m_pDescription0 = "";
848 OptionMsg.m_pDescription1 = "";
849 OptionMsg.m_pDescription2 = "";
850 OptionMsg.m_pDescription3 = "";
851 OptionMsg.m_pDescription4 = "";
852 OptionMsg.m_pDescription5 = "";
853 OptionMsg.m_pDescription6 = "";
854 OptionMsg.m_pDescription7 = "";
855 OptionMsg.m_pDescription8 = "";
856 OptionMsg.m_pDescription9 = "";
857 OptionMsg.m_pDescription10 = "";
858 OptionMsg.m_pDescription11 = "";
859 OptionMsg.m_pDescription12 = "";
860 OptionMsg.m_pDescription13 = "";
861 OptionMsg.m_pDescription14 = "";
862 CVoteOptionServer *pCurrent = m_pVoteOptionFirst;
863 while(pCurrent)
865 switch(NumOptions++)
867 case 0: OptionMsg.m_pDescription0 = pCurrent->m_aDescription; break;
868 case 1: OptionMsg.m_pDescription1 = pCurrent->m_aDescription; break;
869 case 2: OptionMsg.m_pDescription2 = pCurrent->m_aDescription; break;
870 case 3: OptionMsg.m_pDescription3 = pCurrent->m_aDescription; break;
871 case 4: OptionMsg.m_pDescription4 = pCurrent->m_aDescription; break;
872 case 5: OptionMsg.m_pDescription5 = pCurrent->m_aDescription; break;
873 case 6: OptionMsg.m_pDescription6 = pCurrent->m_aDescription; break;
874 case 7: OptionMsg.m_pDescription7 = pCurrent->m_aDescription; break;
875 case 8: OptionMsg.m_pDescription8 = pCurrent->m_aDescription; break;
876 case 9: OptionMsg.m_pDescription9 = pCurrent->m_aDescription; break;
877 case 10: OptionMsg.m_pDescription10 = pCurrent->m_aDescription; break;
878 case 11: OptionMsg.m_pDescription11 = pCurrent->m_aDescription; break;
879 case 12: OptionMsg.m_pDescription12 = pCurrent->m_aDescription; break;
880 case 13: OptionMsg.m_pDescription13 = pCurrent->m_aDescription; break;
881 case 14:
883 OptionMsg.m_pDescription14 = pCurrent->m_aDescription;
884 OptionMsg.m_NumOptions = NumOptions;
885 Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, ClientID);
886 OptionMsg = CNetMsg_Sv_VoteOptionListAdd();
887 NumOptions = 0;
888 OptionMsg.m_pDescription1 = "";
889 OptionMsg.m_pDescription2 = "";
890 OptionMsg.m_pDescription3 = "";
891 OptionMsg.m_pDescription4 = "";
892 OptionMsg.m_pDescription5 = "";
893 OptionMsg.m_pDescription6 = "";
894 OptionMsg.m_pDescription7 = "";
895 OptionMsg.m_pDescription8 = "";
896 OptionMsg.m_pDescription9 = "";
897 OptionMsg.m_pDescription10 = "";
898 OptionMsg.m_pDescription11 = "";
899 OptionMsg.m_pDescription12 = "";
900 OptionMsg.m_pDescription13 = "";
901 OptionMsg.m_pDescription14 = "";
904 pCurrent = pCurrent->m_pNext;
906 if(NumOptions > 0)
908 OptionMsg.m_NumOptions = NumOptions;
909 Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, ClientID);
910 NumOptions = 0;
913 // send tuning parameters to client
914 SendTuningParams(ClientID);
916 // client is ready to enter
917 pPlayer->m_IsReady = true;
918 CNetMsg_Sv_ReadyToEnter m;
919 Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID);
921 else if (MsgID == NETMSGTYPE_CL_CHANGEINFO)
923 if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo+Server()->TickSpeed()*5 > Server()->Tick())
924 return;
926 CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg;
927 pPlayer->m_LastChangeInfo = Server()->Tick();
929 // set infos
930 char aOldName[MAX_NAME_LENGTH];
931 str_copy(aOldName, Server()->ClientName(ClientID), sizeof(aOldName));
932 Server()->SetClientName(ClientID, pMsg->m_pName);
933 if(str_comp(aOldName, Server()->ClientName(ClientID)) != 0)
935 char aChatText[256];
936 str_format(aChatText, sizeof(aChatText), "'%s' changed name to '%s'", aOldName, Server()->ClientName(ClientID));
937 SendChat(-1, CGameContext::CHAT_ALL, aChatText);
939 Server()->SetClientClan(ClientID, pMsg->m_pClan);
940 Server()->SetClientCountry(ClientID, pMsg->m_Country);
941 str_copy(pPlayer->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_SkinName));
942 pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor;
943 pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody;
944 pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet;
945 m_pController->OnPlayerInfoChange(pPlayer);
947 else if (MsgID == NETMSGTYPE_CL_EMOTICON && !m_World.m_Paused)
949 CNetMsg_Cl_Emoticon *pMsg = (CNetMsg_Cl_Emoticon *)pRawMsg;
951 if(g_Config.m_SvSpamprotection && pPlayer->m_LastEmote && pPlayer->m_LastEmote+Server()->TickSpeed()*3 > Server()->Tick())
952 return;
954 pPlayer->m_LastEmote = Server()->Tick();
956 SendEmoticon(ClientID, pMsg->m_Emoticon);
958 else if (MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused)
960 if(pPlayer->m_LastKill && pPlayer->m_LastKill+Server()->TickSpeed()*3 > Server()->Tick())
961 return;
963 pPlayer->m_LastKill = Server()->Tick();
964 pPlayer->KillCharacter(WEAPON_SELF);
968 void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData)
970 CGameContext *pSelf = (CGameContext *)pUserData;
971 const char *pParamName = pResult->GetString(0);
972 float NewValue = pResult->GetFloat(1);
974 if(pSelf->Tuning()->Set(pParamName, NewValue))
976 char aBuf[256];
977 str_format(aBuf, sizeof(aBuf), "%s changed to %.2f", pParamName, NewValue);
978 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf);
979 pSelf->SendTuningParams(-1);
981 else
982 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "No such tuning parameter");
985 void CGameContext::ConTuneReset(IConsole::IResult *pResult, void *pUserData)
987 CGameContext *pSelf = (CGameContext *)pUserData;
988 CTuningParams TuningParams;
989 *pSelf->Tuning() = TuningParams;
990 pSelf->SendTuningParams(-1);
991 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", "Tuning reset");
994 void CGameContext::ConTuneDump(IConsole::IResult *pResult, void *pUserData)
996 CGameContext *pSelf = (CGameContext *)pUserData;
997 char aBuf[256];
998 for(int i = 0; i < pSelf->Tuning()->Num(); i++)
1000 float v;
1001 pSelf->Tuning()->Get(i, &v);
1002 str_format(aBuf, sizeof(aBuf), "%s %.2f", pSelf->Tuning()->m_apNames[i], v);
1003 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "tuning", aBuf);
1007 void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData)
1009 CGameContext *pSelf = (CGameContext *)pUserData;
1010 pSelf->m_pController->ChangeMap(pResult->NumArguments() ? pResult->GetString(0) : "");
1013 void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData)
1015 CGameContext *pSelf = (CGameContext *)pUserData;
1016 if(pResult->NumArguments())
1017 pSelf->m_pController->DoWarmup(pResult->GetInteger(0));
1018 else
1019 pSelf->m_pController->StartRound();
1022 void CGameContext::ConBroadcast(IConsole::IResult *pResult, void *pUserData)
1024 CGameContext *pSelf = (CGameContext *)pUserData;
1025 pSelf->SendBroadcast(pResult->GetString(0), -1);
1028 void CGameContext::ConSay(IConsole::IResult *pResult, void *pUserData)
1030 CGameContext *pSelf = (CGameContext *)pUserData;
1031 pSelf->SendChat(-1, CGameContext::CHAT_ALL, pResult->GetString(0));
1034 void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData)
1036 CGameContext *pSelf = (CGameContext *)pUserData;
1037 int ClientID = clamp(pResult->GetInteger(0), 0, (int)MAX_CLIENTS-1);
1038 int Team = clamp(pResult->GetInteger(1), -1, 1);
1039 int Delay = 0;
1040 if(pResult->NumArguments() > 2)
1041 Delay = pResult->GetInteger(2);
1043 char aBuf[256];
1044 str_format(aBuf, sizeof(aBuf), "moved client %d to team %d", ClientID, Team);
1045 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1047 if(!pSelf->m_apPlayers[ClientID])
1048 return;
1050 pSelf->m_apPlayers[ClientID]->m_TeamChangeTick = pSelf->Server()->Tick()+pSelf->Server()->TickSpeed()*Delay*60;
1051 pSelf->m_apPlayers[ClientID]->SetTeam(Team);
1052 (void)pSelf->m_pController->CheckTeamBalance();
1055 void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData)
1057 CGameContext *pSelf = (CGameContext *)pUserData;
1058 int Team = clamp(pResult->GetInteger(0), -1, 1);
1060 char aBuf[256];
1061 str_format(aBuf, sizeof(aBuf), "moved all clients to team %d", Team);
1062 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1064 for(int i = 0; i < MAX_CLIENTS; ++i)
1065 if(pSelf->m_apPlayers[i])
1066 pSelf->m_apPlayers[i]->SetTeam(Team);
1068 (void)pSelf->m_pController->CheckTeamBalance();
1071 void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData)
1073 CGameContext *pSelf = (CGameContext *)pUserData;
1074 const char *pDescription = pResult->GetString(0);
1075 const char *pCommand = pResult->GetString(1);
1077 if(pSelf->m_NumVoteOptions == MAX_VOTE_OPTIONS)
1079 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "maximum number of vote options reached");
1080 return;
1083 // check for valid option
1084 if(!pSelf->Console()->LineIsValid(pCommand) || str_length(pCommand) >= VOTE_CMD_LENGTH)
1086 char aBuf[256];
1087 str_format(aBuf, sizeof(aBuf), "skipped invalid command '%s'", pCommand);
1088 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1089 return;
1091 while(*pDescription && *pDescription == ' ')
1092 pDescription++;
1093 if(str_length(pDescription) >= VOTE_DESC_LENGTH || *pDescription == 0)
1095 char aBuf[256];
1096 str_format(aBuf, sizeof(aBuf), "skipped invalid option '%s'", pDescription);
1097 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1098 return;
1101 // check for duplicate entry
1102 CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst;
1103 while(pOption)
1105 if(str_comp_nocase(pDescription, pOption->m_aDescription) == 0)
1107 char aBuf[256];
1108 str_format(aBuf, sizeof(aBuf), "option '%s' already exists", pDescription);
1109 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1110 return;
1112 pOption = pOption->m_pNext;
1115 // add the option
1116 ++pSelf->m_NumVoteOptions;
1117 int Len = str_length(pCommand);
1119 pOption = (CVoteOptionServer *)pSelf->m_pVoteOptionHeap->Allocate(sizeof(CVoteOptionServer) + Len);
1120 pOption->m_pNext = 0;
1121 pOption->m_pPrev = pSelf->m_pVoteOptionLast;
1122 if(pOption->m_pPrev)
1123 pOption->m_pPrev->m_pNext = pOption;
1124 pSelf->m_pVoteOptionLast = pOption;
1125 if(!pSelf->m_pVoteOptionFirst)
1126 pSelf->m_pVoteOptionFirst = pOption;
1128 str_copy(pOption->m_aDescription, pDescription, sizeof(pOption->m_aDescription));
1129 mem_copy(pOption->m_aCommand, pCommand, Len+1);
1130 char aBuf[256];
1131 str_format(aBuf, sizeof(aBuf), "added option '%s' '%s'", pOption->m_aDescription, pOption->m_aCommand);
1132 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1134 // inform clients about added option
1135 CNetMsg_Sv_VoteOptionAdd OptionMsg;
1136 OptionMsg.m_pDescription = pOption->m_aDescription;
1137 pSelf->Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, -1);
1140 void CGameContext::ConRemoveVote(IConsole::IResult *pResult, void *pUserData)
1142 CGameContext *pSelf = (CGameContext *)pUserData;
1143 const char *pDescription = pResult->GetString(0);
1145 // check for valid option
1146 CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst;
1147 while(pOption)
1149 if(str_comp_nocase(pDescription, pOption->m_aDescription) == 0)
1150 break;
1151 pOption = pOption->m_pNext;
1153 if(!pOption)
1155 char aBuf[256];
1156 str_format(aBuf, sizeof(aBuf), "option '%s' does not exist", pDescription);
1157 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1158 return;
1161 // inform clients about removed option
1162 CNetMsg_Sv_VoteOptionRemove OptionMsg;
1163 OptionMsg.m_pDescription = pOption->m_aDescription;
1164 pSelf->Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, -1);
1166 // TODO: improve this
1167 // remove the option
1168 --pSelf->m_NumVoteOptions;
1169 char aBuf[256];
1170 str_format(aBuf, sizeof(aBuf), "removed option '%s' '%s'", pOption->m_aDescription, pOption->m_aCommand);
1171 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1173 CHeap *pVoteOptionHeap = new CHeap();
1174 CVoteOptionServer *pVoteOptionFirst = 0;
1175 CVoteOptionServer *pVoteOptionLast = 0;
1176 int NumVoteOptions = pSelf->m_NumVoteOptions;
1177 for(CVoteOptionServer *pSrc = pSelf->m_pVoteOptionFirst; pSrc; pSrc = pSrc->m_pNext)
1179 if(pSrc == pOption)
1180 continue;
1182 // copy option
1183 int Len = str_length(pSrc->m_aCommand);
1184 CVoteOptionServer *pDst = (CVoteOptionServer *)pVoteOptionHeap->Allocate(sizeof(CVoteOptionServer) + Len);
1185 pDst->m_pNext = 0;
1186 pDst->m_pPrev = pVoteOptionLast;
1187 if(pDst->m_pPrev)
1188 pDst->m_pPrev->m_pNext = pDst;
1189 pVoteOptionLast = pDst;
1190 if(!pVoteOptionFirst)
1191 pVoteOptionFirst = pDst;
1193 str_copy(pDst->m_aDescription, pSrc->m_aDescription, sizeof(pDst->m_aDescription));
1194 mem_copy(pDst->m_aCommand, pSrc->m_aCommand, Len+1);
1197 // clean up
1198 delete pSelf->m_pVoteOptionHeap;
1199 pSelf->m_pVoteOptionHeap = pVoteOptionHeap;
1200 pSelf->m_pVoteOptionFirst = pVoteOptionFirst;
1201 pSelf->m_pVoteOptionLast = pVoteOptionLast;
1202 pSelf->m_NumVoteOptions = NumVoteOptions;
1205 void CGameContext::ConForceVote(IConsole::IResult *pResult, void *pUserData)
1207 CGameContext *pSelf = (CGameContext *)pUserData;
1208 const char *pType = pResult->GetString(0);
1209 const char *pValue = pResult->GetString(1);
1210 const char *pReason = pResult->NumArguments() > 2 && pResult->GetString(2)[0] ? pResult->GetString(2) : "No reason given";
1211 char aBuf[128] = {0};
1213 if(str_comp_nocase(pType, "option") == 0)
1215 CVoteOptionServer *pOption = pSelf->m_pVoteOptionFirst;
1216 while(pOption)
1218 if(str_comp_nocase(pValue, pOption->m_aDescription) == 0)
1220 str_format(aBuf, sizeof(aBuf), "admin forced server option '%s' (%s)", pValue, pReason);
1221 pSelf->SendChatTarget(-1, aBuf);
1222 pSelf->Console()->ExecuteLine(pOption->m_aCommand);
1223 break;
1226 pOption = pOption->m_pNext;
1229 if(!pOption)
1231 str_format(aBuf, sizeof(aBuf), "'%s' isn't an option on this server", pValue);
1232 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1233 return;
1236 else if(str_comp_nocase(pType, "kick") == 0)
1238 int KickID = str_toint(pValue);
1239 if(KickID < 0 || KickID >= MAX_CLIENTS || !pSelf->m_apPlayers[KickID])
1241 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "Invalid client id to kick");
1242 return;
1245 if (!g_Config.m_SvVoteKickBantime)
1247 str_format(aBuf, sizeof(aBuf), "kick %d %s", KickID, pReason);
1248 pSelf->Console()->ExecuteLine(aBuf);
1250 else
1252 char aAddrStr[NETADDR_MAXSTRSIZE] = {0};
1253 pSelf->Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr));
1254 str_format(aBuf, sizeof(aBuf), "ban %s %d %s", aAddrStr, g_Config.m_SvVoteKickBantime, pReason);
1255 pSelf->Console()->ExecuteLine(aBuf);
1258 else if(str_comp_nocase(pType, "spectate") == 0)
1260 int SpectateID = str_toint(pValue);
1261 if(SpectateID < 0 || SpectateID >= MAX_CLIENTS || !pSelf->m_apPlayers[SpectateID] || pSelf->m_apPlayers[SpectateID]->GetTeam() == TEAM_SPECTATORS)
1263 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "Invalid client id to move");
1264 return;
1267 str_format(aBuf, sizeof(aBuf), "admin moved '%s' to spectator (%s)", pSelf->Server()->ClientName(SpectateID), pReason);
1268 pSelf->SendChatTarget(-1, aBuf);
1269 str_format(aBuf, sizeof(aBuf), "set_team %d -1 %d", SpectateID, g_Config.m_SvVoteSpectateRejoindelay);
1270 pSelf->Console()->ExecuteLine(aBuf);
1274 void CGameContext::ConClearVotes(IConsole::IResult *pResult, void *pUserData)
1276 CGameContext *pSelf = (CGameContext *)pUserData;
1278 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "cleared votes");
1279 CNetMsg_Sv_VoteClearOptions VoteClearOptionsMsg;
1280 pSelf->Server()->SendPackMsg(&VoteClearOptionsMsg, MSGFLAG_VITAL, -1);
1281 pSelf->m_pVoteOptionHeap->Reset();
1282 pSelf->m_pVoteOptionFirst = 0;
1283 pSelf->m_pVoteOptionLast = 0;
1284 pSelf->m_NumVoteOptions = 0;
1287 void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData)
1289 CGameContext *pSelf = (CGameContext *)pUserData;
1290 if(str_comp_nocase(pResult->GetString(0), "yes") == 0)
1291 pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_YES;
1292 else if(str_comp_nocase(pResult->GetString(0), "no") == 0)
1293 pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_NO;
1294 char aBuf[256];
1295 str_format(aBuf, sizeof(aBuf), "admin forced vote %s", pResult->GetString(0));
1296 pSelf->SendChatTarget(-1, aBuf);
1297 str_format(aBuf, sizeof(aBuf), "forcing vote %s", pResult->GetString(0));
1298 pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
1301 void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
1303 pfnCallback(pResult, pCallbackUserData);
1304 if(pResult->NumArguments())
1306 CNetMsg_Sv_Motd Msg;
1307 Msg.m_pMessage = g_Config.m_SvMotd;
1308 CGameContext *pSelf = (CGameContext *)pUserData;
1309 for(int i = 0; i < MAX_CLIENTS; ++i)
1310 if(pSelf->m_apPlayers[i])
1311 pSelf->Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, i);
1315 void CGameContext::OnConsoleInit()
1317 m_pServer = Kernel()->RequestInterface<IServer>();
1318 m_pConsole = Kernel()->RequestInterface<IConsole>();
1320 Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, "Tune variable to value");
1321 Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning");
1322 Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning");
1324 Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, "Change map");
1325 Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "Restart in x seconds");
1326 Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "Broadcast message");
1327 Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "Say in chat");
1328 Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team");
1329 Console()->Register("set_team_all", "i", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team");
1331 Console()->Register("add_vote", "sr", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option");
1332 Console()->Register("remove_vote", "s", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option");
1333 Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, ConForceVote, this, "Force a voting option");
1334 Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "Clears the voting options");
1335 Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "Force a vote to yes/no");
1337 Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this);
1340 void CGameContext::OnInit(/*class IKernel *pKernel*/)
1342 m_pServer = Kernel()->RequestInterface<IServer>();
1343 m_pConsole = Kernel()->RequestInterface<IConsole>();
1344 m_World.SetGameServer(this);
1345 m_Events.SetGameServer(this);
1347 //if(!data) // only load once
1348 //data = load_data_from_memory(internal_data);
1350 for(int i = 0; i < NUM_NETOBJTYPES; i++)
1351 Server()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
1353 m_Layers.Init(Kernel());
1354 m_Collision.Init(&m_Layers);
1356 // reset everything here
1357 //world = new GAMEWORLD;
1358 //players = new CPlayer[MAX_CLIENTS];
1360 // select gametype
1361 if(str_comp(g_Config.m_SvGametype, "mod") == 0)
1362 m_pController = new CGameControllerMOD(this);
1363 else if(str_comp(g_Config.m_SvGametype, "ctf") == 0)
1364 m_pController = new CGameControllerCTF(this);
1365 else if(str_comp(g_Config.m_SvGametype, "tdm") == 0)
1366 m_pController = new CGameControllerTDM(this);
1367 else
1368 m_pController = new CGameControllerDM(this);
1370 // setup core world
1371 //for(int i = 0; i < MAX_CLIENTS; i++)
1372 // game.players[i].core.world = &game.world.core;
1374 // create all entities from the game layer
1375 CMapItemLayerTilemap *pTileMap = m_Layers.GameLayer();
1376 CTile *pTiles = (CTile *)Kernel()->RequestInterface<IMap>()->GetData(pTileMap->m_Data);
1382 num_spawn_points[0] = 0;
1383 num_spawn_points[1] = 0;
1384 num_spawn_points[2] = 0;
1387 for(int y = 0; y < pTileMap->m_Height; y++)
1389 for(int x = 0; x < pTileMap->m_Width; x++)
1391 int Index = pTiles[y*pTileMap->m_Width+x].m_Index;
1393 if(Index >= ENTITY_OFFSET)
1395 vec2 Pos(x*32.0f+16.0f, y*32.0f+16.0f);
1396 m_pController->OnEntity(Index-ENTITY_OFFSET, Pos);
1401 //game.world.insert_entity(game.Controller);
1403 #ifdef CONF_DEBUG
1404 if(g_Config.m_DbgDummies)
1406 for(int i = 0; i < g_Config.m_DbgDummies ; i++)
1408 OnClientConnected(MAX_CLIENTS-i-1);
1411 #endif
1414 void CGameContext::OnShutdown()
1416 delete m_pController;
1417 m_pController = 0;
1418 Clear();
1421 void CGameContext::OnSnap(int ClientID)
1423 m_World.Snap(ClientID);
1424 m_pController->Snap(ClientID);
1425 m_Events.Snap(ClientID);
1427 for(int i = 0; i < MAX_CLIENTS; i++)
1429 if(m_apPlayers[i])
1430 m_apPlayers[i]->Snap(ClientID);
1433 void CGameContext::OnPreSnap() {}
1434 void CGameContext::OnPostSnap()
1436 m_Events.Clear();
1439 bool CGameContext::IsClientReady(int ClientID)
1441 return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReady ? true : false;
1444 bool CGameContext::IsClientPlayer(int ClientID)
1446 return m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetTeam() == TEAM_SPECTATORS ? false : true;
1449 const char *CGameContext::GameType() { return m_pController && m_pController->m_pGameType ? m_pController->m_pGameType : ""; }
1450 const char *CGameContext::Version() { return GAME_VERSION; }
1451 const char *CGameContext::NetVersion() { return GAME_NETVERSION; }
1453 IGameServer *CreateGameServer() { return new CGameContext; }