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. */
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"
23 void CGameContext::Construct(int Resetting
)
28 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
33 m_pVoteOptionFirst
= 0;
34 m_pVoteOptionLast
= 0;
37 if(Resetting
==NO_RESET
)
38 m_pVoteOptionHeap
= new CHeap();
41 CGameContext::CGameContext(int Resetting
)
46 CGameContext::CGameContext()
51 CGameContext::~CGameContext()
53 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
54 delete m_apPlayers
[i
];
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
;
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
;
80 class CCharacter
*CGameContext::GetPlayerChar(int ClientID
)
82 if(ClientID
< 0 || ClientID
>= MAX_CLIENTS
|| !m_apPlayers
[ClientID
])
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);
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
));
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
)
109 CNetEvent_HammerHit
*pEvent
= (CNetEvent_HammerHit
*)m_Events
.Create(NETEVENTTYPE_HAMMERHIT
, sizeof(CNetEvent_HammerHit
));
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
)
121 CNetEvent_Explosion
*pEvent
= (CNetEvent_Explosion
*)m_Events
.Create(NETEVENTTYPE_EXPLOSION
, sizeof(CNetEvent_Explosion
));
124 pEvent
->m_X
= (int)Pos
.x
;
125 pEvent
->m_Y
= (int)Pos
.y
;
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
;
139 float l
= length(Diff
);
141 ForceDir
= normalize(Diff
);
142 l
= 1-clamp((l
-InnerRadius
)/(Radius
-InnerRadius
), 0.0f
, 1.0f
);
145 apEnts
[i
]->TakeDamage(ForceDir
*Dmg
*2, (int)Dmg
, Owner
, Weapon
);
151 void create_smoke(vec2 Pos)
154 EV_EXPLOSION *pEvent = (EV_EXPLOSION *)events.create(EVENT_SMOKE, sizeof(EV_EXPLOSION));
157 pEvent->x = (int)Pos.x;
158 pEvent->y = (int)Pos.y;
162 void CGameContext::CreatePlayerSpawn(vec2 Pos
)
165 CNetEvent_Spawn
*ev
= (CNetEvent_Spawn
*)m_Events
.Create(NETEVENTTYPE_SPAWN
, sizeof(CNetEvent_Spawn
));
168 ev
->m_X
= (int)Pos
.x
;
169 ev
->m_Y
= (int)Pos
.y
;
173 void CGameContext::CreateDeath(vec2 Pos
, int ClientID
)
176 CNetEvent_Death
*pEvent
= (CNetEvent_Death
*)m_Events
.Create(NETEVENTTYPE_DEATH
, sizeof(CNetEvent_Death
));
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
)
191 CNetEvent_SoundWorld
*pEvent
= (CNetEvent_SoundWorld
*)m_Events
.Create(NETEVENTTYPE_SOUNDWORLD
, sizeof(CNetEvent_SoundWorld
), Mask
);
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
)
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
)
216 Msg
.m_pMessage
= pText
;
217 Server()->SendPackMsg(&Msg
, MSGFLAG_VITAL
, To
);
221 void CGameContext::SendChat(int ChatterClientID
, int Team
, const char *pText
)
224 if(ChatterClientID
>= 0 && ChatterClientID
< MAX_CLIENTS
)
225 str_format(aBuf
, sizeof(aBuf
), "%d:%d:%s: %s", ChatterClientID
, Team
, Server()->ClientName(ChatterClientID
), pText
);
227 str_format(aBuf
, sizeof(aBuf
), "*** %s", pText
);
228 Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, Team
!=CHAT_ALL
?"teamchat":"chat", aBuf
);
234 Msg
.m_ClientID
= ChatterClientID
;
235 Msg
.m_pMessage
= pText
;
236 Server()->SendPackMsg(&Msg
, MSGFLAG_VITAL
, -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
288 m_VoteEnforce
= VOTE_ENFORCE_UNKNOWN
;
289 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
293 m_apPlayers
[i
]->m_Vote
= 0;
294 m_apPlayers
[i
]->m_VotePos
= 0;
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
));
308 void CGameContext::EndVote()
314 void CGameContext::SendVoteSet(int ClientID
)
316 CNetMsg_Sv_VoteSet Msg
;
319 Msg
.m_Timeout
= (m_VoteCloseTime
-time_get())/time_freq();
320 Msg
.m_pDescription
= m_aVoteDescription
;
321 Msg
.m_pReason
= m_aVoteReason
;
326 Msg
.m_pDescription
= "";
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};
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
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)
363 if(mem_comp(&p
, &m_Tuning
, sizeof(p
)) != 0)
365 Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "server", "resetting tuning due to pure server");
371 void CGameContext::SendTuningParams(int ClientID
)
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()
388 m_World
.m_Core
.m_Tuning
= m_Tuning
;
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
++)
398 m_apPlayers
[i
]->Tick();
399 m_apPlayers
[i
]->PostTick();
406 // abort the kick-vote on player-leave
407 if(m_VoteCloseTime
== -1)
409 SendChat(-1, CGameContext::CHAT_ALL
, "Vote aborted");
414 int Total
= 0, Yes
= 0, No
= 0;
418 char aaBuf
[MAX_CLIENTS
][NETADDR_MAXSTRSIZE
] = {{0}};
419 for(int i
= 0; i
< MAX_CLIENTS
; 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
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
]))
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
;
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
);
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
)
470 SendChat(-1, CGameContext::CHAT_ALL
, "Vote failed");
472 else if(m_VoteUpdate
)
474 m_VoteUpdate
= false;
475 SendVoteStatus(-1, Total
, Yes
, No
);
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
);
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();
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
);
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();
533 if(g_Config
.m_DbgDummies
)
535 if(ClientID
>= MAX_CLIENTS
-g_Config
.m_DbgDummies
)
542 SendVoteSet(ClientID
);
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();
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
];
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
);
581 if(MsgID
== NETMSGTYPE_CL_SAY
)
583 CNetMsg_Cl_Say
*pMsg
= (CNetMsg_Cl_Say
*)pRawMsg
;
584 int Team
= pMsg
->m_Team
;
586 Team
= pPlayer
->GetTeam();
588 Team
= CGameContext::CHAT_ALL
;
590 if(g_Config
.m_SvSpamprotection
&& pPlayer
->m_LastChat
&& pPlayer
->m_LastChat
+Server()->TickSpeed() > Server()->Tick())
593 pPlayer
->m_LastChat
= Server()->Tick();
595 // check for invalid chars
596 unsigned char *pMessage
= (unsigned char *)pMsg
->m_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())
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.");
621 SendChatTarget(ClientID
, "Wait for current vote to end before calling a new one.");
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
);
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
;
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
);
654 pOption
= pOption
->m_pNext
;
659 str_format(aChatmsg
, sizeof(aChatmsg
), "'%s' isn't an option on this server", pMsg
->m_Value
);
660 SendChatTarget(ClientID
, aChatmsg
);
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");
672 if(g_Config
.m_SvVoteKickMin
)
675 for(int i
= 0; i
< MAX_CLIENTS
; ++i
)
676 if(m_apPlayers
[i
] && m_apPlayers
[i
]->GetTeam() != TEAM_SPECTATORS
)
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
);
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");
693 if(KickID
== ClientID
)
695 SendChatTarget(ClientID
, "You can't kick yourself");
698 if(Server()->IsAuthed(KickID
))
700 SendChatTarget(ClientID
, "You can't kick admins");
702 str_format(aBufKick
, sizeof(aBufKick
), "'%s' called for vote to kick you", Server()->ClientName(ClientID
));
703 SendChatTarget(KickID
, aBufKick
);
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
);
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");
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");
733 if(SpectateID
== ClientID
)
735 SendChatTarget(ClientID
, "You can't move yourself");
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
);
746 SendChat(-1, CGameContext::CHAT_ALL
, aChatmsg
);
747 StartVote(aDesc
, aCmd
, pReason
);
749 pPlayer
->m_VotePos
= m_VotePos
= 1;
750 m_VoteCreator
= ClientID
;
751 pPlayer
->m_LastVoteCall
= Now
;
754 else if(MsgID
== NETMSGTYPE_CL_VOTE
)
759 if(pPlayer
->m_Vote
== 0)
761 CNetMsg_Cl_Vote
*pMsg
= (CNetMsg_Cl_Vote
*)pRawMsg
;
765 pPlayer
->m_Vote
= pMsg
->m_Vote
;
766 pPlayer
->m_VotePos
= ++m_VotePos
;
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()))
777 if(pPlayer
->m_TeamChangeTick
> Server()->Tick())
779 pPlayer
->m_LastSetTeam
= Server()->Tick();
780 int TimeLeft
= (pPlayer
->m_TeamChangeTick
- Server()->Tick())/Server()->TickSpeed();
782 str_format(aBuf
, sizeof(aBuf
), "Time to wait before changing team: %02d:%02d", TimeLeft
/60, TimeLeft
%60);
783 SendBroadcast(aBuf
, ClientID
);
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
)
795 pPlayer
->SetTeam(pMsg
->m_Team
);
796 (void)m_pController
->CheckTeamBalance();
797 pPlayer
->m_TeamChangeTick
= Server()->Tick();
800 SendBroadcast("Teams must be balanced, please join other team", ClientID
);
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()))
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");
821 pPlayer
->m_SpectatorID
= pMsg
->m_SpectatorID
;
823 else if (MsgID
== NETMSGTYPE_CL_STARTINFO
)
825 if(pPlayer
->m_IsReady
)
828 CNetMsg_Cl_StartInfo
*pMsg
= (CNetMsg_Cl_StartInfo
*)pRawMsg
;
829 pPlayer
->m_LastChangeInfo
= Server()->Tick();
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
);
842 CNetMsg_Sv_VoteClearOptions ClearMsg
;
843 Server()->SendPackMsg(&ClearMsg
, MSGFLAG_VITAL
, ClientID
);
845 CNetMsg_Sv_VoteOptionListAdd OptionMsg
;
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
;
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;
883 OptionMsg
.m_pDescription14
= pCurrent
->m_aDescription
;
884 OptionMsg
.m_NumOptions
= NumOptions
;
885 Server()->SendPackMsg(&OptionMsg
, MSGFLAG_VITAL
, ClientID
);
886 OptionMsg
= CNetMsg_Sv_VoteOptionListAdd();
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
;
908 OptionMsg
.m_NumOptions
= NumOptions
;
909 Server()->SendPackMsg(&OptionMsg
, MSGFLAG_VITAL
, ClientID
);
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())
926 CNetMsg_Cl_ChangeInfo
*pMsg
= (CNetMsg_Cl_ChangeInfo
*)pRawMsg
;
927 pPlayer
->m_LastChangeInfo
= Server()->Tick();
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)
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())
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())
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
))
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);
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
;
998 for(int i
= 0; i
< pSelf
->Tuning()->Num(); i
++)
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));
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);
1040 if(pResult
->NumArguments() > 2)
1041 Delay
= pResult
->GetInteger(2);
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
])
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);
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");
1083 // check for valid option
1084 if(!pSelf
->Console()->LineIsValid(pCommand
) || str_length(pCommand
) >= VOTE_CMD_LENGTH
)
1087 str_format(aBuf
, sizeof(aBuf
), "skipped invalid command '%s'", pCommand
);
1088 pSelf
->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "server", aBuf
);
1091 while(*pDescription
&& *pDescription
== ' ')
1093 if(str_length(pDescription
) >= VOTE_DESC_LENGTH
|| *pDescription
== 0)
1096 str_format(aBuf
, sizeof(aBuf
), "skipped invalid option '%s'", pDescription
);
1097 pSelf
->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "server", aBuf
);
1101 // check for duplicate entry
1102 CVoteOptionServer
*pOption
= pSelf
->m_pVoteOptionFirst
;
1105 if(str_comp_nocase(pDescription
, pOption
->m_aDescription
) == 0)
1108 str_format(aBuf
, sizeof(aBuf
), "option '%s' already exists", pDescription
);
1109 pSelf
->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "server", aBuf
);
1112 pOption
= pOption
->m_pNext
;
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);
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
;
1149 if(str_comp_nocase(pDescription
, pOption
->m_aDescription
) == 0)
1151 pOption
= pOption
->m_pNext
;
1156 str_format(aBuf
, sizeof(aBuf
), "option '%s' does not exist", pDescription
);
1157 pSelf
->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "server", aBuf
);
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
;
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
)
1183 int Len
= str_length(pSrc
->m_aCommand
);
1184 CVoteOptionServer
*pDst
= (CVoteOptionServer
*)pVoteOptionHeap
->Allocate(sizeof(CVoteOptionServer
) + Len
);
1186 pDst
->m_pPrev
= pVoteOptionLast
;
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);
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
;
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
);
1226 pOption
= pOption
->m_pNext
;
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
);
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");
1245 if (!g_Config
.m_SvVoteKickBantime
)
1247 str_format(aBuf
, sizeof(aBuf
), "kick %d %s", KickID
, pReason
);
1248 pSelf
->Console()->ExecuteLine(aBuf
);
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");
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
;
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];
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);
1368 m_pController
= new CGameControllerDM(this);
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);
1404 if(g_Config
.m_DbgDummies
)
1406 for(int i
= 0; i
< g_Config
.m_DbgDummies
; i
++)
1408 OnClientConnected(MAX_CLIENTS
-i
-1);
1414 void CGameContext::OnShutdown()
1416 delete m_pController
;
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
++)
1430 m_apPlayers
[i
]->Snap(ClientID
);
1433 void CGameContext::OnPreSnap() {}
1434 void CGameContext::OnPostSnap()
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
; }