2010-11-20 10:37:14 +00:00
|
|
|
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
2008-08-14 18:46:52 +00:00
|
|
|
#include <new>
|
2011-01-18 18:09:53 +00:00
|
|
|
#include <engine/shared/config.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include "player.h"
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2010-07-29 05:21:18 +00:00
|
|
|
#include <engine/server.h>
|
|
|
|
#include "gamecontext.h"
|
|
|
|
#include <game/gamecore.h>
|
2014-10-12 10:51:54 +00:00
|
|
|
#include <game/version.h>
|
2013-11-15 23:44:49 +00:00
|
|
|
#include <game/server/teams.h>
|
2010-07-29 14:53:25 +00:00
|
|
|
#include "gamemodes/DDRace.h"
|
2012-02-14 20:38:06 +00:00
|
|
|
#include <time.h>
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2020-05-26 21:22:16 +00:00
|
|
|
#if defined(CONF_SQL)
|
|
|
|
#include "score/sql_score.h"
|
|
|
|
#endif
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS)
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
IServer *CPlayer::Server() const { return m_pGameServer->Server(); }
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-02-12 10:40:36 +00:00
|
|
|
CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
m_pGameServer = pGameServer;
|
2011-04-19 08:42:48 +00:00
|
|
|
m_ClientID = ClientID;
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Team = GameServer()->m_pController->ClampTeam(Team);
|
2014-11-25 19:33:21 +00:00
|
|
|
m_NumInputs = 0;
|
2014-09-18 10:14:00 +00:00
|
|
|
Reset();
|
2020-03-11 00:58:50 +00:00
|
|
|
GameServer()->Antibot()->OnPlayerInit(m_ClientID);
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
CPlayer::~CPlayer()
|
2008-09-24 09:03:49 +00:00
|
|
|
{
|
2020-03-11 00:58:50 +00:00
|
|
|
GameServer()->Antibot()->OnPlayerDestroy(m_ClientID);
|
2019-10-13 15:13:03 +00:00
|
|
|
delete m_pLastTarget;
|
2011-04-19 08:42:48 +00:00
|
|
|
delete m_pCharacter;
|
|
|
|
m_pCharacter = 0;
|
2008-09-24 09:03:49 +00:00
|
|
|
}
|
|
|
|
|
2014-08-09 12:50:51 +00:00
|
|
|
void CPlayer::Reset()
|
|
|
|
{
|
|
|
|
m_DieTick = Server()->Tick();
|
2019-06-07 09:44:19 +00:00
|
|
|
m_PreviousDieTick = m_DieTick;
|
2016-01-27 01:14:46 +00:00
|
|
|
m_JoinTick = Server()->Tick();
|
2018-08-24 18:02:23 +00:00
|
|
|
delete m_pCharacter;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_pCharacter = 0;
|
|
|
|
m_SpectatorID = SPEC_FREEVIEW;
|
|
|
|
m_LastActionTick = Server()->Tick();
|
|
|
|
m_TeamChangeTick = Server()->Tick();
|
2017-04-24 12:04:50 +00:00
|
|
|
m_LastInvited = 0;
|
2016-03-04 21:55:12 +00:00
|
|
|
m_WeakHookSpawn = false;
|
2014-08-09 12:50:51 +00:00
|
|
|
|
2018-06-19 12:28:53 +00:00
|
|
|
int *pIdMap = Server()->GetIdMap(m_ClientID);
|
2014-08-09 12:50:51 +00:00
|
|
|
for (int i = 1;i < VANILLA_MAX_CLIENTS;i++)
|
|
|
|
{
|
2018-06-19 12:28:53 +00:00
|
|
|
pIdMap[i] = -1;
|
2014-08-09 12:50:51 +00:00
|
|
|
}
|
2018-06-19 12:28:53 +00:00
|
|
|
pIdMap[0] = m_ClientID;
|
2014-08-09 12:50:51 +00:00
|
|
|
|
|
|
|
// DDRace
|
|
|
|
|
|
|
|
m_LastCommandPos = 0;
|
2019-10-13 15:13:03 +00:00
|
|
|
m_LastPlaytime = 0;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_Sent1stAfkWarning = 0;
|
|
|
|
m_Sent2ndAfkWarning = 0;
|
|
|
|
m_ChatScore = 0;
|
2018-01-05 11:04:06 +00:00
|
|
|
m_Moderating = false;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_EyeEmote = true;
|
2017-02-27 22:10:19 +00:00
|
|
|
m_TimerType = (g_Config.m_SvDefaultTimerType == CPlayer::TIMERTYPE_GAMETIMER || g_Config.m_SvDefaultTimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) ? CPlayer::TIMERTYPE_BROADCAST : g_Config.m_SvDefaultTimerType;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_DefEmote = EMOTE_NORMAL;
|
2019-10-13 15:13:03 +00:00
|
|
|
m_Afk = true;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_LastWhisperTo = -1;
|
|
|
|
m_LastSetSpectatorMode = 0;
|
2014-08-09 15:25:29 +00:00
|
|
|
m_TimeoutCode[0] = '\0';
|
2019-10-13 15:13:03 +00:00
|
|
|
delete m_pLastTarget;
|
|
|
|
m_pLastTarget = nullptr;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_TuneZone = 0;
|
|
|
|
m_TuneZoneOld = m_TuneZone;
|
2014-09-18 10:14:00 +00:00
|
|
|
m_Halloween = false;
|
2014-10-12 09:33:36 +00:00
|
|
|
m_FirstPacket = true;
|
2014-08-09 12:50:51 +00:00
|
|
|
|
2014-10-26 18:39:42 +00:00
|
|
|
m_SendVoteIndex = -1;
|
|
|
|
|
2018-12-23 21:53:10 +00:00
|
|
|
if(g_Config.m_Events)
|
2014-10-12 09:33:36 +00:00
|
|
|
{
|
2014-08-09 12:50:51 +00:00
|
|
|
time_t rawtime;
|
|
|
|
struct tm* timeinfo;
|
2017-09-03 06:48:21 +00:00
|
|
|
time(&rawtime);
|
|
|
|
timeinfo = localtime(&rawtime);
|
2018-02-01 19:22:18 +00:00
|
|
|
if ((timeinfo->tm_mon == 11 && timeinfo->tm_mday == 31) || (timeinfo->tm_mon == 0 && timeinfo->tm_mday == 1))
|
2014-09-18 10:14:00 +00:00
|
|
|
{ // New Year
|
|
|
|
m_DefEmote = EMOTE_HAPPY;
|
|
|
|
}
|
2017-09-03 06:48:21 +00:00
|
|
|
else if ((timeinfo->tm_mon == 9 && timeinfo->tm_mday == 31) || (timeinfo->tm_mon == 10 && timeinfo->tm_mday == 1))
|
2014-09-18 10:14:00 +00:00
|
|
|
{ // Halloween
|
|
|
|
m_DefEmote = EMOTE_ANGRY;
|
|
|
|
m_Halloween = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_DefEmote = EMOTE_NORMAL;
|
|
|
|
}
|
2014-08-09 12:50:51 +00:00
|
|
|
}
|
|
|
|
m_DefEmoteReset = -1;
|
|
|
|
|
|
|
|
GameServer()->Score()->PlayerData(m_ClientID)->Reset();
|
|
|
|
|
|
|
|
m_ShowOthers = g_Config.m_SvShowOthersDefault;
|
|
|
|
m_ShowAll = g_Config.m_SvShowAllDefault;
|
2014-08-09 17:53:38 +00:00
|
|
|
m_SpecTeam = 0;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_NinjaJetpack = false;
|
|
|
|
|
2017-04-08 22:20:41 +00:00
|
|
|
m_Paused = PAUSE_NONE;
|
2014-08-09 12:50:51 +00:00
|
|
|
m_DND = false;
|
|
|
|
|
2017-04-08 23:16:48 +00:00
|
|
|
m_LastPause = 0;
|
2017-07-23 19:33:55 +00:00
|
|
|
m_Score = -9999;
|
2017-07-23 19:34:36 +00:00
|
|
|
m_HasFinishScore = false;
|
2014-08-09 12:50:51 +00:00
|
|
|
|
|
|
|
// Variable initialized:
|
|
|
|
m_Last_Team = 0;
|
|
|
|
#if defined(CONF_SQL)
|
|
|
|
m_LastSQLQuery = 0;
|
2020-05-26 21:22:16 +00:00
|
|
|
m_SqlQueryResult = nullptr;
|
2020-06-08 15:11:41 +00:00
|
|
|
m_SqlFinishResult = nullptr;
|
2020-06-18 06:18:33 +00:00
|
|
|
m_SqlRandomMapResult = nullptr;
|
2014-08-09 12:50:51 +00:00
|
|
|
#endif
|
2016-01-19 22:52:28 +00:00
|
|
|
|
|
|
|
int64 Now = Server()->Tick();
|
|
|
|
int64 TickSpeed = Server()->TickSpeed();
|
2016-01-20 23:09:11 +00:00
|
|
|
// If the player joins within ten seconds of the server becoming
|
|
|
|
// non-empty, allow them to vote immediately. This allows players to
|
|
|
|
// vote after map changes or when they join an empty server.
|
2016-01-22 16:44:53 +00:00
|
|
|
//
|
2020-05-27 15:49:07 +00:00
|
|
|
// Otherwise, block voting in the beginning after joining.
|
2016-01-19 22:52:28 +00:00
|
|
|
if(Now > GameServer()->m_NonEmptySince + 10 * TickSpeed)
|
2016-05-20 18:05:47 +00:00
|
|
|
m_FirstVoteTick = Now + g_Config.m_SvJoinVoteDelay * TickSpeed;
|
2016-01-19 22:52:28 +00:00
|
|
|
else
|
|
|
|
m_FirstVoteTick = Now;
|
2018-12-17 13:55:58 +00:00
|
|
|
|
|
|
|
m_NotEligibleForFinish = false;
|
|
|
|
m_EligibleForFinishCheck = 0;
|
2020-05-22 21:59:47 +00:00
|
|
|
m_VotedForPractice = false;
|
2014-08-09 12:50:51 +00:00
|
|
|
}
|
|
|
|
|
2020-06-16 16:05:41 +00:00
|
|
|
static int PlayerFlags_SevenToSix(int Flags)
|
|
|
|
{
|
|
|
|
int Six = 0;
|
|
|
|
if(Flags&protocol7::PLAYERFLAG_CHATTING)
|
|
|
|
Six |= PLAYERFLAG_CHATTING;
|
|
|
|
if(Flags&protocol7::PLAYERFLAG_SCOREBOARD)
|
|
|
|
Six |= PLAYERFLAG_SCOREBOARD;
|
|
|
|
|
|
|
|
return Six;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PlayerFlags_SixToSeven(int Flags)
|
|
|
|
{
|
|
|
|
int Seven = 0;
|
|
|
|
if(Flags&PLAYERFLAG_CHATTING)
|
|
|
|
Seven |= protocol7::PLAYERFLAG_CHATTING;
|
|
|
|
if(Flags&PLAYERFLAG_SCOREBOARD)
|
|
|
|
Seven |= protocol7::PLAYERFLAG_SCOREBOARD;
|
|
|
|
|
|
|
|
return Seven;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::Tick()
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-01-18 18:09:53 +00:00
|
|
|
#ifdef CONF_DEBUG
|
|
|
|
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies)
|
2020-05-28 22:11:27 +00:00
|
|
|
#endif
|
|
|
|
#if defined(CONF_SQL)
|
2020-06-08 15:11:41 +00:00
|
|
|
if(m_SqlQueryResult != nullptr && m_SqlQueryResult.use_count() == 1)
|
|
|
|
{
|
|
|
|
ProcessSqlResult(*m_SqlQueryResult);
|
|
|
|
m_SqlQueryResult = nullptr;
|
|
|
|
}
|
|
|
|
if(m_SqlFinishResult != nullptr && m_SqlFinishResult.use_count() == 1)
|
|
|
|
{
|
|
|
|
ProcessSqlResult(*m_SqlFinishResult);
|
|
|
|
m_SqlFinishResult = nullptr;
|
|
|
|
}
|
2020-06-18 06:18:33 +00:00
|
|
|
if(m_SqlRandomMapResult != nullptr && m_SqlRandomMapResult.use_count() == 1)
|
2020-06-14 09:24:33 +00:00
|
|
|
{
|
|
|
|
if(m_SqlRandomMapResult->m_Done)
|
|
|
|
{
|
|
|
|
if(m_SqlRandomMapResult->m_aMessage[0] != '\0')
|
|
|
|
GameServer()->SendChatTarget(m_ClientID, m_SqlRandomMapResult->m_aMessage);
|
|
|
|
if(m_SqlRandomMapResult->m_Map[0] != '\0')
|
|
|
|
str_copy(g_Config.m_SvMap, m_SqlRandomMapResult->m_Map, sizeof(g_Config.m_SvMap));
|
2020-06-14 21:51:58 +00:00
|
|
|
else
|
|
|
|
GameServer()->m_LastMapVote = 0;
|
2020-06-14 09:24:33 +00:00
|
|
|
}
|
|
|
|
m_SqlRandomMapResult = nullptr;
|
|
|
|
}
|
2011-01-18 18:09:53 +00:00
|
|
|
#endif
|
2020-06-08 15:11:41 +00:00
|
|
|
|
2010-09-21 23:00:33 +00:00
|
|
|
if(!Server()->ClientIngame(m_ClientID))
|
|
|
|
return;
|
|
|
|
|
2011-02-03 13:39:00 +00:00
|
|
|
if (m_ChatScore > 0)
|
|
|
|
m_ChatScore--;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
Server()->SetClientScore(m_ClientID, m_Score);
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2018-01-05 11:04:06 +00:00
|
|
|
if (m_Moderating && m_Afk)
|
|
|
|
{
|
|
|
|
m_Moderating = false;
|
|
|
|
GameServer()->SendChatTarget(m_ClientID, "Active moderator mode disabled because you are afk.");
|
|
|
|
|
|
|
|
if (!GameServer()->PlayerModerating())
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, "Server kick/spec votes are no longer actively moderated.");
|
|
|
|
}
|
|
|
|
|
2008-08-14 18:46:52 +00:00
|
|
|
// do latency stuff
|
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
IServer::CClientInfo Info;
|
|
|
|
if(Server()->GetClientInfo(m_ClientID, &Info))
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Latency.m_Accum += Info.m_Latency;
|
2019-04-26 19:36:49 +00:00
|
|
|
m_Latency.m_AccumMax = maximum(m_Latency.m_AccumMax, Info.m_Latency);
|
|
|
|
m_Latency.m_AccumMin = minimum(m_Latency.m_AccumMin, Info.m_Latency);
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
// each second
|
|
|
|
if(Server()->Tick()%Server()->TickSpeed() == 0)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Latency.m_Avg = m_Latency.m_Accum/Server()->TickSpeed();
|
|
|
|
m_Latency.m_Max = m_Latency.m_AccumMax;
|
|
|
|
m_Latency.m_Min = m_Latency.m_AccumMin;
|
|
|
|
m_Latency.m_Accum = 0;
|
|
|
|
m_Latency.m_AccumMin = 1000;
|
|
|
|
m_Latency.m_AccumMax = 0;
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2017-06-06 03:51:12 +00:00
|
|
|
if(Server()->GetNetErrorString(m_ClientID)[0])
|
2014-08-17 03:04:37 +00:00
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' would have timed out, but can use timeout protection now", Server()->ClientName(m_ClientID));
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
|
2017-06-06 03:51:12 +00:00
|
|
|
Server()->ResetNetErrorString(m_ClientID);
|
2014-08-17 03:04:37 +00:00
|
|
|
}
|
|
|
|
|
2012-01-09 23:49:31 +00:00
|
|
|
if(!GameServer()->m_World.m_Paused)
|
2008-09-24 14:54:51 +00:00
|
|
|
{
|
2019-06-07 09:44:19 +00:00
|
|
|
int EarliestRespawnTick = m_PreviousDieTick+Server()->TickSpeed()*3;
|
2019-09-29 20:12:55 +00:00
|
|
|
int RespawnTick = maximum(m_DieTick, EarliestRespawnTick)+2;
|
2019-06-07 09:44:19 +00:00
|
|
|
if(!m_pCharacter && RespawnTick <= Server()->Tick())
|
2012-01-09 23:49:31 +00:00
|
|
|
m_Spawning = true;
|
2008-09-24 14:54:51 +00:00
|
|
|
|
2012-01-09 23:49:31 +00:00
|
|
|
if(m_pCharacter)
|
2008-09-24 14:54:51 +00:00
|
|
|
{
|
2012-01-09 23:49:31 +00:00
|
|
|
if(m_pCharacter->IsAlive())
|
2011-12-29 13:58:39 +00:00
|
|
|
{
|
2017-04-08 22:20:41 +00:00
|
|
|
ProcessPause();
|
2012-04-12 00:09:31 +00:00
|
|
|
if(!m_Paused)
|
|
|
|
m_ViewPos = m_pCharacter->m_Pos;
|
2011-12-29 13:58:39 +00:00
|
|
|
}
|
2012-04-12 00:09:31 +00:00
|
|
|
else if(!m_pCharacter->IsPaused())
|
2011-12-29 13:58:39 +00:00
|
|
|
{
|
2012-01-09 23:49:31 +00:00
|
|
|
delete m_pCharacter;
|
|
|
|
m_pCharacter = 0;
|
2011-12-29 13:58:39 +00:00
|
|
|
}
|
2008-09-24 14:54:51 +00:00
|
|
|
}
|
2016-03-04 21:55:12 +00:00
|
|
|
else if(m_Spawning && !m_WeakHookSpawn)
|
2012-01-09 23:49:31 +00:00
|
|
|
TryRespawn();
|
2008-09-24 14:54:51 +00:00
|
|
|
}
|
2012-01-09 23:49:31 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
++m_DieTick;
|
2019-06-07 09:44:19 +00:00
|
|
|
++m_PreviousDieTick;
|
2016-01-27 01:14:46 +00:00
|
|
|
++m_JoinTick;
|
2012-01-09 23:49:31 +00:00
|
|
|
++m_LastActionTick;
|
|
|
|
++m_TeamChangeTick;
|
2014-04-18 12:50:15 +00:00
|
|
|
}
|
|
|
|
|
2014-04-18 12:33:47 +00:00
|
|
|
m_TuneZoneOld = m_TuneZone; // determine needed tunings with viewpos
|
|
|
|
int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_ViewPos);
|
|
|
|
m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex);
|
2014-04-18 12:50:15 +00:00
|
|
|
|
2020-05-27 15:49:07 +00:00
|
|
|
if (m_TuneZone != m_TuneZoneOld) // don't send tunings all the time
|
2014-04-18 12:33:47 +00:00
|
|
|
{
|
|
|
|
GameServer()->SendTuningParams(m_ClientID, m_TuneZone);
|
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2011-03-10 09:08:14 +00:00
|
|
|
void CPlayer::PostTick()
|
|
|
|
{
|
|
|
|
// update latency value
|
|
|
|
if(m_PlayerFlags&PLAYERFLAG_SCOREBOARD)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < MAX_CLIENTS; ++i)
|
|
|
|
{
|
|
|
|
if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS)
|
|
|
|
m_aActLatency[i] = GameServer()->m_apPlayers[i]->m_Latency.m_Min;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update view pos for spectators
|
2011-12-31 09:47:45 +00:00
|
|
|
if((m_Team == TEAM_SPECTATORS || m_Paused) && m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[m_SpectatorID] && GameServer()->m_apPlayers[m_SpectatorID]->GetCharacter())
|
|
|
|
m_ViewPos = GameServer()->m_apPlayers[m_SpectatorID]->GetCharacter()->m_Pos;
|
2011-03-10 09:08:14 +00:00
|
|
|
}
|
|
|
|
|
2016-03-04 21:55:12 +00:00
|
|
|
void CPlayer::PostPostTick()
|
|
|
|
{
|
2020-05-27 15:49:07 +00:00
|
|
|
#ifdef CONF_DEBUG
|
|
|
|
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies)
|
|
|
|
#endif
|
|
|
|
if(!Server()->ClientIngame(m_ClientID))
|
|
|
|
return;
|
2016-03-04 21:55:12 +00:00
|
|
|
|
|
|
|
if(!GameServer()->m_World.m_Paused && !m_pCharacter && m_Spawning && m_WeakHookSpawn)
|
|
|
|
TryRespawn();
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::Snap(int SnappingClient)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-01-18 18:09:53 +00:00
|
|
|
#ifdef CONF_DEBUG
|
|
|
|
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies)
|
|
|
|
#endif
|
2010-09-21 23:00:33 +00:00
|
|
|
if(!Server()->ClientIngame(m_ClientID))
|
|
|
|
return;
|
|
|
|
|
2013-12-31 05:13:57 +00:00
|
|
|
int id = m_ClientID;
|
2020-06-12 19:22:54 +00:00
|
|
|
if(SnappingClient > -1 && !Server()->Translate(id, SnappingClient))
|
|
|
|
return;
|
2013-12-31 05:13:57 +00:00
|
|
|
|
|
|
|
CNetObj_ClientInfo *pClientInfo = static_cast<CNetObj_ClientInfo *>(Server()->SnapNewItem(NETOBJTYPE_CLIENTINFO, id, sizeof(CNetObj_ClientInfo)));
|
2010-12-16 02:29:08 +00:00
|
|
|
if(!pClientInfo)
|
|
|
|
return;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2011-03-15 10:23:49 +00:00
|
|
|
StrToInts(&pClientInfo->m_Name0, 4, Server()->ClientName(m_ClientID));
|
|
|
|
StrToInts(&pClientInfo->m_Clan0, 3, Server()->ClientClan(m_ClientID));
|
|
|
|
pClientInfo->m_Country = Server()->ClientCountry(m_ClientID);
|
2019-06-04 15:23:29 +00:00
|
|
|
StrToInts(&pClientInfo->m_Skin0, 6, m_TeeInfos.m_SkinName);
|
|
|
|
pClientInfo->m_UseCustomColor = m_TeeInfos.m_UseCustomColor;
|
|
|
|
pClientInfo->m_ColorBody = m_TeeInfos.m_ColorBody;
|
|
|
|
pClientInfo->m_ColorFeet = m_TeeInfos.m_ColorFeet;
|
2010-08-23 19:37:27 +00:00
|
|
|
|
2020-05-22 15:58:41 +00:00
|
|
|
int ClientVersion = GetClientVersion();
|
2020-03-29 02:36:38 +00:00
|
|
|
int Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID];
|
|
|
|
int Score = abs(m_Score) * -1;
|
2017-05-18 22:21:01 +00:00
|
|
|
|
2019-06-03 09:35:18 +00:00
|
|
|
// send 0 if times of others are not shown
|
|
|
|
if(SnappingClient != m_ClientID && g_Config.m_SvHideScore)
|
2020-03-29 02:36:38 +00:00
|
|
|
Score = -9999;
|
2019-06-03 09:35:18 +00:00
|
|
|
else
|
2020-03-29 02:36:38 +00:00
|
|
|
Score = abs(m_Score) * -1;
|
|
|
|
|
|
|
|
if(!Server()->IsSixup(SnappingClient))
|
|
|
|
{
|
2020-06-12 19:22:54 +00:00
|
|
|
CNetObj_PlayerInfo *pPlayerInfo = static_cast<CNetObj_PlayerInfo *>(Server()->SnapNewItem(NETOBJTYPE_PLAYERINFO, id, sizeof(CNetObj_PlayerInfo)));
|
|
|
|
if(!pPlayerInfo)
|
|
|
|
return;
|
|
|
|
|
2020-03-29 02:36:38 +00:00
|
|
|
pPlayerInfo->m_Latency = Latency;
|
|
|
|
pPlayerInfo->m_Score = Score;
|
|
|
|
pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || ClientVersion >= VERSION_DDNET_OLD));
|
|
|
|
pPlayerInfo->m_ClientID = id;
|
|
|
|
pPlayerInfo->m_Team = (ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS;
|
|
|
|
|
|
|
|
if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && ClientVersion < VERSION_DDNET_OLD)
|
|
|
|
pPlayerInfo->m_Team = TEAM_SPECTATORS;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-06-12 19:22:54 +00:00
|
|
|
protocol7::CNetObj_PlayerInfo *pPlayerInfo = static_cast<protocol7::CNetObj_PlayerInfo *>(Server()->SnapNewItem(NETOBJTYPE_PLAYERINFO, id, sizeof(protocol7::CNetObj_PlayerInfo)));
|
|
|
|
if(!pPlayerInfo)
|
|
|
|
return;
|
|
|
|
|
2020-06-16 16:05:41 +00:00
|
|
|
pPlayerInfo->m_PlayerFlags = PlayerFlags_SixToSeven(m_PlayerFlags);
|
|
|
|
if(Server()->ClientAuthed(m_ClientID))
|
|
|
|
pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_ADMIN;
|
2020-06-12 19:22:54 +00:00
|
|
|
pPlayerInfo->m_Score = Score;
|
|
|
|
pPlayerInfo->m_Latency = Latency;
|
2020-03-29 02:36:38 +00:00
|
|
|
}
|
2011-03-10 09:08:14 +00:00
|
|
|
|
2011-12-29 13:58:39 +00:00
|
|
|
if(m_ClientID == SnappingClient && (m_Team == TEAM_SPECTATORS || m_Paused))
|
2011-03-10 09:08:14 +00:00
|
|
|
{
|
2020-06-12 20:53:41 +00:00
|
|
|
if(!Server()->IsSixup(SnappingClient))
|
|
|
|
{
|
|
|
|
CNetObj_SpectatorInfo *pSpectatorInfo = static_cast<CNetObj_SpectatorInfo *>(Server()->SnapNewItem(NETOBJTYPE_SPECTATORINFO, m_ClientID, sizeof(CNetObj_SpectatorInfo)));
|
|
|
|
if(!pSpectatorInfo)
|
|
|
|
return;
|
2011-03-10 09:08:14 +00:00
|
|
|
|
2020-06-12 20:53:41 +00:00
|
|
|
pSpectatorInfo->m_SpectatorID = m_SpectatorID;
|
|
|
|
pSpectatorInfo->m_X = m_ViewPos.x;
|
|
|
|
pSpectatorInfo->m_Y = m_ViewPos.y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
protocol7::CNetObj_SpectatorInfo *pSpectatorInfo = static_cast<protocol7::CNetObj_SpectatorInfo *>(Server()->SnapNewItem(NETOBJTYPE_SPECTATORINFO, m_ClientID, sizeof(protocol7::CNetObj_SpectatorInfo)));
|
|
|
|
if(!pSpectatorInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pSpectatorInfo->m_SpecMode = m_SpectatorID == SPEC_FREEVIEW ? protocol7::SPEC_FREEVIEW : protocol7::SPEC_PLAYER;
|
|
|
|
pSpectatorInfo->m_SpectatorID = m_SpectatorID;
|
|
|
|
pSpectatorInfo->m_X = m_ViewPos.x;
|
|
|
|
pSpectatorInfo->m_Y = m_ViewPos.y;
|
|
|
|
}
|
2011-03-10 09:08:14 +00:00
|
|
|
}
|
2011-04-09 06:41:31 +00:00
|
|
|
|
2019-05-14 23:03:30 +00:00
|
|
|
CNetObj_DDNetPlayer *pDDNetPlayer = static_cast<CNetObj_DDNetPlayer *>(Server()->SnapNewItem(NETOBJTYPE_DDNETPLAYER, id, sizeof(CNetObj_DDNetPlayer)));
|
|
|
|
if(!pDDNetPlayer)
|
2019-03-02 10:50:33 +00:00
|
|
|
return;
|
|
|
|
|
2019-05-14 23:03:30 +00:00
|
|
|
pDDNetPlayer->m_AuthLevel = Server()->GetAuthedState(id);
|
2019-08-02 16:16:58 +00:00
|
|
|
pDDNetPlayer->m_Flags = 0;
|
|
|
|
if(m_Afk)
|
|
|
|
pDDNetPlayer->m_Flags |= EXPLAYERFLAG_AFK;
|
|
|
|
if(m_Paused == PAUSE_SPEC)
|
|
|
|
pDDNetPlayer->m_Flags |= EXPLAYERFLAG_SPEC;
|
|
|
|
if(m_Paused == PAUSE_PAUSED)
|
|
|
|
pDDNetPlayer->m_Flags |= EXPLAYERFLAG_PAUSED;
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2016-09-20 19:40:40 +00:00
|
|
|
void CPlayer::FakeSnap()
|
2013-12-31 05:13:57 +00:00
|
|
|
{
|
2020-05-22 15:58:41 +00:00
|
|
|
if(GetClientVersion() >= VERSION_DDNET_OLD)
|
2013-12-31 05:13:57 +00:00
|
|
|
return;
|
|
|
|
|
2020-06-12 20:53:41 +00:00
|
|
|
if(Server()->IsSixup(m_ClientID))
|
|
|
|
return;
|
|
|
|
|
2016-09-20 19:40:40 +00:00
|
|
|
int FakeID = VANILLA_MAX_CLIENTS - 1;
|
2013-12-31 05:13:57 +00:00
|
|
|
|
2016-09-20 19:40:40 +00:00
|
|
|
CNetObj_ClientInfo *pClientInfo = static_cast<CNetObj_ClientInfo *>(Server()->SnapNewItem(NETOBJTYPE_CLIENTINFO, FakeID, sizeof(CNetObj_ClientInfo)));
|
2013-12-31 05:13:57 +00:00
|
|
|
|
|
|
|
if(!pClientInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
StrToInts(&pClientInfo->m_Name0, 4, " ");
|
2016-09-20 19:40:40 +00:00
|
|
|
StrToInts(&pClientInfo->m_Clan0, 3, "");
|
|
|
|
StrToInts(&pClientInfo->m_Skin0, 6, "default");
|
|
|
|
|
2017-05-18 21:52:54 +00:00
|
|
|
if(m_Paused != PAUSE_PAUSED)
|
2016-09-20 19:40:40 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
CNetObj_PlayerInfo *pPlayerInfo = static_cast<CNetObj_PlayerInfo *>(Server()->SnapNewItem(NETOBJTYPE_PLAYERINFO, FakeID, sizeof(CNetObj_PlayerInfo)));
|
|
|
|
if(!pPlayerInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pPlayerInfo->m_Latency = m_Latency.m_Min;
|
|
|
|
pPlayerInfo->m_Local = 1;
|
|
|
|
pPlayerInfo->m_ClientID = FakeID;
|
|
|
|
pPlayerInfo->m_Score = -9999;
|
|
|
|
pPlayerInfo->m_Team = TEAM_SPECTATORS;
|
|
|
|
|
|
|
|
CNetObj_SpectatorInfo *pSpectatorInfo = static_cast<CNetObj_SpectatorInfo *>(Server()->SnapNewItem(NETOBJTYPE_SPECTATORINFO, FakeID, sizeof(CNetObj_SpectatorInfo)));
|
|
|
|
if(!pSpectatorInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pSpectatorInfo->m_SpectatorID = m_SpectatorID;
|
|
|
|
pSpectatorInfo->m_X = m_ViewPos.x;
|
|
|
|
pSpectatorInfo->m_Y = m_ViewPos.y;
|
2013-12-31 05:13:57 +00:00
|
|
|
}
|
|
|
|
|
2011-02-14 18:41:32 +00:00
|
|
|
void CPlayer::OnDisconnect(const char *pReason)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
KillCharacter();
|
|
|
|
|
|
|
|
if(Server()->ClientIngame(m_ClientID))
|
|
|
|
{
|
2010-12-23 05:57:26 +00:00
|
|
|
char aBuf[512];
|
2011-02-14 18:41:32 +00:00
|
|
|
if(pReason && *pReason)
|
2011-04-13 18:37:12 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' has left the game (%s)", Server()->ClientName(m_ClientID), pReason);
|
2011-02-14 18:41:32 +00:00
|
|
|
else
|
2011-04-13 18:37:12 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' has left the game", Server()->ClientName(m_ClientID));
|
2020-06-16 16:24:43 +00:00
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, -1, CGameContext::CHAT_SIX);
|
2011-01-29 00:59:50 +00:00
|
|
|
|
2010-12-23 05:57:26 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "leave player='%d:%s'", m_ClientID, Server()->ClientName(m_ClientID));
|
|
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "game", aBuf);
|
2018-01-05 11:04:06 +00:00
|
|
|
|
|
|
|
bool WasModerator = m_Moderating;
|
|
|
|
|
|
|
|
// Set this to false, otherwise PlayerModerating() will return true.
|
|
|
|
m_Moderating = false;
|
|
|
|
|
|
|
|
if (!GameServer()->PlayerModerating() && WasModerator)
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, "Server kick/spec votes are no longer actively moderated.");
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-04-09 06:41:31 +00:00
|
|
|
|
2011-02-14 21:34:46 +00:00
|
|
|
CGameControllerDDRace* Controller = (CGameControllerDDRace*)GameServer()->m_pController;
|
2014-02-02 10:10:00 +00:00
|
|
|
Controller->m_Teams.SetForceCharacterTeam(m_ClientID, 0);
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-05-05 00:13:32 +00:00
|
|
|
// skip the input if chat is active
|
|
|
|
if((m_PlayerFlags&PLAYERFLAG_CHATTING) && (NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING))
|
|
|
|
return;
|
|
|
|
|
2014-02-21 17:27:00 +00:00
|
|
|
AfkVoteTimer(NewInput);
|
|
|
|
|
2014-11-25 19:33:21 +00:00
|
|
|
m_NumInputs++;
|
2014-10-12 09:33:36 +00:00
|
|
|
|
2011-12-29 13:58:39 +00:00
|
|
|
if(m_pCharacter && !m_Paused)
|
2011-04-19 08:42:48 +00:00
|
|
|
m_pCharacter->OnPredictedInput(NewInput);
|
2014-11-25 19:33:21 +00:00
|
|
|
|
|
|
|
// Magic number when we can hope that client has successfully identified itself
|
2015-08-07 01:26:50 +00:00
|
|
|
if(m_NumInputs == 20)
|
2014-11-25 19:33:21 +00:00
|
|
|
{
|
2020-05-22 15:58:41 +00:00
|
|
|
if(g_Config.m_SvClientSuggestion[0] != '\0' && GetClientVersion() <= VERSION_DDNET_OLD)
|
2014-11-25 19:33:21 +00:00
|
|
|
GameServer()->SendBroadcast(g_Config.m_SvClientSuggestion, m_ClientID);
|
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2020-06-16 16:05:41 +00:00
|
|
|
if(Server()->IsSixup(m_ClientID))
|
|
|
|
NewInput->m_PlayerFlags = PlayerFlags_SevenToSix(NewInput->m_PlayerFlags);
|
|
|
|
|
2019-04-03 13:07:05 +00:00
|
|
|
if(NewInput->m_PlayerFlags)
|
|
|
|
Server()->SetClientFlags(m_ClientID, NewInput->m_PlayerFlags);
|
|
|
|
|
|
|
|
if(AfkTimer(NewInput->m_TargetX, NewInput->m_TargetY))
|
2011-08-20 10:53:52 +00:00
|
|
|
return; // we must return if kicked, as player struct is already deleted
|
2014-02-21 17:27:00 +00:00
|
|
|
AfkVoteTimer(NewInput);
|
2013-08-18 01:27:30 +00:00
|
|
|
|
2018-12-28 23:04:40 +00:00
|
|
|
if(((!m_pCharacter && m_Team == TEAM_SPECTATORS) || m_Paused) && m_SpectatorID == SPEC_FREEVIEW)
|
|
|
|
m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY);
|
|
|
|
|
2011-06-09 20:30:03 +00:00
|
|
|
if(NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING)
|
|
|
|
{
|
2011-08-13 00:11:06 +00:00
|
|
|
// skip the input if chat is active
|
2011-06-09 20:30:03 +00:00
|
|
|
if(m_PlayerFlags&PLAYERFLAG_CHATTING)
|
2018-12-28 23:04:40 +00:00
|
|
|
return;
|
2011-06-09 20:30:03 +00:00
|
|
|
|
|
|
|
// reset input
|
|
|
|
if(m_pCharacter)
|
|
|
|
m_pCharacter->ResetInput();
|
|
|
|
|
2014-11-25 19:33:21 +00:00
|
|
|
m_PlayerFlags = NewInput->m_PlayerFlags;
|
|
|
|
return;
|
2011-06-09 20:30:03 +00:00
|
|
|
}
|
2011-05-05 00:13:32 +00:00
|
|
|
|
2011-03-01 17:31:20 +00:00
|
|
|
m_PlayerFlags = NewInput->m_PlayerFlags;
|
|
|
|
|
2019-02-11 21:25:51 +00:00
|
|
|
if(m_pCharacter && m_Paused)
|
|
|
|
m_pCharacter->ResetInput();
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2011-04-19 08:42:48 +00:00
|
|
|
if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire&1))
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Spawning = true;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-10-09 17:14:42 +00:00
|
|
|
// check for activity
|
|
|
|
if(NewInput->m_Direction || m_LatestActivity.m_TargetX != NewInput->m_TargetX ||
|
|
|
|
m_LatestActivity.m_TargetY != NewInput->m_TargetY || NewInput->m_Jump ||
|
|
|
|
NewInput->m_Fire&1 || NewInput->m_Hook)
|
|
|
|
{
|
|
|
|
m_LatestActivity.m_TargetX = NewInput->m_TargetX;
|
|
|
|
m_LatestActivity.m_TargetY = NewInput->m_TargetY;
|
|
|
|
m_LastActionTick = Server()->Tick();
|
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-29 19:32:11 +00:00
|
|
|
void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput)
|
|
|
|
{
|
|
|
|
// skip the input if chat is active
|
|
|
|
if((m_PlayerFlags&PLAYERFLAG_CHATTING) && (NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(m_pCharacter && !m_Paused)
|
|
|
|
m_pCharacter->OnDirectInput(NewInput);
|
|
|
|
}
|
|
|
|
|
2020-05-22 15:58:41 +00:00
|
|
|
int CPlayer::GetClientVersion() const
|
|
|
|
{
|
|
|
|
return m_pGameServer->GetClientVersion(m_ClientID);
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
CCharacter *CPlayer::GetCharacter()
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-04-19 08:42:48 +00:00
|
|
|
if(m_pCharacter && m_pCharacter->IsAlive())
|
|
|
|
return m_pCharacter;
|
2008-08-14 18:46:52 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::KillCharacter(int Weapon)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-04-19 08:42:48 +00:00
|
|
|
if(m_pCharacter)
|
2008-09-23 18:08:19 +00:00
|
|
|
{
|
2011-04-19 08:42:48 +00:00
|
|
|
m_pCharacter->Die(m_ClientID, Weapon);
|
2013-11-15 23:44:49 +00:00
|
|
|
|
2011-04-19 08:42:48 +00:00
|
|
|
delete m_pCharacter;
|
|
|
|
m_pCharacter = 0;
|
2008-09-23 18:08:19 +00:00
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2016-03-04 21:55:12 +00:00
|
|
|
void CPlayer::Respawn(bool WeakHook)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-01-03 11:50:38 +00:00
|
|
|
if(m_Team != TEAM_SPECTATORS)
|
2016-03-04 21:55:12 +00:00
|
|
|
{
|
|
|
|
m_WeakHookSpawn = WeakHook;
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Spawning = true;
|
2016-03-04 21:55:12 +00:00
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2014-07-26 12:46:31 +00:00
|
|
|
CCharacter* CPlayer::ForceSpawn(vec2 Pos)
|
|
|
|
{
|
2014-08-31 12:12:03 +00:00
|
|
|
m_Spawning = false;
|
2014-07-26 12:46:31 +00:00
|
|
|
m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World);
|
|
|
|
m_pCharacter->Spawn(this, Pos);
|
2014-08-31 12:12:03 +00:00
|
|
|
m_Team = 0;
|
|
|
|
return m_pCharacter;
|
2014-07-26 12:46:31 +00:00
|
|
|
}
|
|
|
|
|
2011-12-30 21:30:28 +00:00
|
|
|
void CPlayer::SetTeam(int Team, bool DoChatMsg)
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
Team = GameServer()->m_pController->ClampTeam(Team);
|
|
|
|
if(m_Team == Team)
|
2008-08-14 18:46:52 +00:00
|
|
|
return;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-08-17 22:06:00 +00:00
|
|
|
char aBuf[512];
|
2018-12-17 13:55:58 +00:00
|
|
|
DoChatMsg = false;
|
2011-12-30 21:30:28 +00:00
|
|
|
if(DoChatMsg)
|
|
|
|
{
|
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(m_ClientID), GameServer()->m_pController->GetTeamName(Team));
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2013-11-15 23:44:49 +00:00
|
|
|
if(Team == TEAM_SPECTATORS)
|
2013-11-16 01:36:04 +00:00
|
|
|
{
|
|
|
|
CGameControllerDDRace* Controller = (CGameControllerDDRace*)GameServer()->m_pController;
|
2014-02-08 13:33:42 +00:00
|
|
|
Controller->m_Teams.SetForceCharacterTeam(m_ClientID, 0);
|
2013-11-16 01:36:04 +00:00
|
|
|
}
|
2013-11-15 23:44:49 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
KillCharacter();
|
2010-06-03 14:35:18 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Team = Team;
|
2011-06-01 14:08:45 +00:00
|
|
|
m_LastSetTeam = Server()->Tick();
|
2010-10-09 17:14:42 +00:00
|
|
|
m_LastActionTick = Server()->Tick();
|
2013-02-24 16:24:12 +00:00
|
|
|
m_SpectatorID = SPEC_FREEVIEW;
|
2011-08-13 00:11:06 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' m_Team=%d", m_ClientID, Server()->ClientName(m_ClientID), m_Team);
|
|
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-07-29 05:21:18 +00:00
|
|
|
//GameServer()->m_pController->OnPlayerInfoChange(GameServer()->m_apPlayers[m_ClientID]);
|
2011-03-28 21:45:47 +00:00
|
|
|
|
2020-06-15 13:37:09 +00:00
|
|
|
protocol7::CNetMsg_Sv_Team Msg;
|
|
|
|
Msg.m_ClientID = m_ClientID;
|
|
|
|
Msg.m_Team = m_Team;
|
|
|
|
Msg.m_Silent = !DoChatMsg;
|
|
|
|
Msg.m_CooldownTick = m_LastSetTeam + Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay;
|
|
|
|
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD, -1);
|
2020-06-12 20:53:41 +00:00
|
|
|
|
2011-03-28 21:45:47 +00:00
|
|
|
if(Team == TEAM_SPECTATORS)
|
|
|
|
{
|
|
|
|
// update spectator modes
|
|
|
|
for(int i = 0; i < MAX_CLIENTS; ++i)
|
|
|
|
{
|
|
|
|
if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->m_SpectatorID == m_ClientID)
|
|
|
|
GameServer()->m_apPlayers[i]->m_SpectatorID = SPEC_FREEVIEW;
|
|
|
|
}
|
|
|
|
}
|
2008-08-14 18:46:52 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CPlayer::TryRespawn()
|
2008-08-14 18:46:52 +00:00
|
|
|
{
|
2011-08-13 00:11:06 +00:00
|
|
|
vec2 SpawnPos;
|
2008-08-14 18:46:52 +00:00
|
|
|
|
2011-08-13 00:11:06 +00:00
|
|
|
if(!GameServer()->m_pController->CanSpawn(m_Team, &SpawnPos))
|
|
|
|
return;
|
2010-08-22 13:17:57 +00:00
|
|
|
|
2013-12-19 22:04:01 +00:00
|
|
|
CGameControllerDDRace* Controller = (CGameControllerDDRace*)GameServer()->m_pController;
|
|
|
|
|
2016-03-04 21:55:12 +00:00
|
|
|
m_WeakHookSpawn = false;
|
2011-08-13 00:11:06 +00:00
|
|
|
m_Spawning = false;
|
|
|
|
m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World);
|
|
|
|
m_pCharacter->Spawn(this, SpawnPos);
|
2012-02-05 13:48:09 +00:00
|
|
|
GameServer()->CreatePlayerSpawn(SpawnPos, m_pCharacter->Teams()->TeamMask(m_pCharacter->Team(), -1, m_ClientID));
|
2013-12-19 22:04:01 +00:00
|
|
|
|
|
|
|
if(g_Config.m_SvTeam == 3)
|
|
|
|
{
|
2020-05-30 12:47:34 +00:00
|
|
|
int NewTeam = 1;
|
2013-12-19 22:04:01 +00:00
|
|
|
for(; NewTeam < TEAM_SUPER; NewTeam++)
|
|
|
|
if(Controller->m_Teams.Count(NewTeam) == 0)
|
|
|
|
break;
|
|
|
|
|
2013-12-28 15:27:47 +00:00
|
|
|
if(NewTeam == TEAM_SUPER)
|
|
|
|
NewTeam = 0;
|
|
|
|
|
|
|
|
Controller->m_Teams.SetForceCharacterTeam(GetCID(), NewTeam);
|
2019-06-13 15:23:23 +00:00
|
|
|
m_pCharacter->SetSolo(true);
|
2013-12-19 22:04:01 +00:00
|
|
|
}
|
2010-08-22 13:17:57 +00:00
|
|
|
}
|
2011-03-22 19:49:12 +00:00
|
|
|
|
2011-05-06 19:16:53 +00:00
|
|
|
bool CPlayer::AfkTimer(int NewTargetX, int NewTargetY)
|
2011-03-22 19:49:12 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
afk timer (x, y = mouse coordinates)
|
|
|
|
Since a player has to move the mouse to play, this is a better method than checking
|
|
|
|
the player's position in the game world, because it can easily be bypassed by just locking a key.
|
|
|
|
Frozen players could be kicked as well, because they can't move.
|
|
|
|
It also works for spectators.
|
2011-05-06 19:16:53 +00:00
|
|
|
returns true if kicked
|
2011-03-22 19:49:12 +00:00
|
|
|
*/
|
|
|
|
|
2018-01-27 22:13:17 +00:00
|
|
|
if(Server()->GetAuthedState(m_ClientID))
|
2011-05-06 19:16:53 +00:00
|
|
|
return false; // don't kick admins
|
2011-04-20 14:32:41 +00:00
|
|
|
if(g_Config.m_SvMaxAfkTime == 0)
|
2011-05-06 19:16:53 +00:00
|
|
|
return false; // 0 = disabled
|
2011-03-22 19:49:12 +00:00
|
|
|
|
2011-04-20 14:32:41 +00:00
|
|
|
if(NewTargetX != m_LastTarget_x || NewTargetY != m_LastTarget_y)
|
2011-03-22 19:49:12 +00:00
|
|
|
{
|
|
|
|
m_LastPlaytime = time_get();
|
2011-04-20 14:32:41 +00:00
|
|
|
m_LastTarget_x = NewTargetX;
|
|
|
|
m_LastTarget_y = NewTargetY;
|
2011-04-23 17:59:25 +00:00
|
|
|
m_Sent1stAfkWarning = 0; // afk timer's 1st warning after 50% of sv_max_afk_time
|
|
|
|
m_Sent2ndAfkWarning = 0;
|
2011-03-22 19:49:12 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-04-06 20:37:16 +00:00
|
|
|
if(!m_Paused)
|
2011-03-22 19:49:12 +00:00
|
|
|
{
|
2012-04-06 20:37:16 +00:00
|
|
|
// not playing, check how long
|
|
|
|
if(m_Sent1stAfkWarning == 0 && m_LastPlaytime < time_get()-time_freq()*(int)(g_Config.m_SvMaxAfkTime*0.5))
|
|
|
|
{
|
2017-09-03 07:01:25 +00:00
|
|
|
str_format(m_pAfkMsg, sizeof(m_pAfkMsg),
|
2012-04-06 20:37:16 +00:00
|
|
|
"You have been afk for %d seconds now. Please note that you get kicked after not playing for %d seconds.",
|
|
|
|
(int)(g_Config.m_SvMaxAfkTime*0.5),
|
|
|
|
g_Config.m_SvMaxAfkTime
|
|
|
|
);
|
|
|
|
m_pGameServer->SendChatTarget(m_ClientID, m_pAfkMsg);
|
|
|
|
m_Sent1stAfkWarning = 1;
|
|
|
|
}
|
|
|
|
else if(m_Sent2ndAfkWarning == 0 && m_LastPlaytime < time_get()-time_freq()*(int)(g_Config.m_SvMaxAfkTime*0.9))
|
|
|
|
{
|
2017-09-03 07:01:25 +00:00
|
|
|
str_format(m_pAfkMsg, sizeof(m_pAfkMsg),
|
2012-04-06 20:37:16 +00:00
|
|
|
"You have been afk for %d seconds now. Please note that you get kicked after not playing for %d seconds.",
|
|
|
|
(int)(g_Config.m_SvMaxAfkTime*0.9),
|
|
|
|
g_Config.m_SvMaxAfkTime
|
|
|
|
);
|
|
|
|
m_pGameServer->SendChatTarget(m_ClientID, m_pAfkMsg);
|
|
|
|
m_Sent2ndAfkWarning = 1;
|
|
|
|
}
|
|
|
|
else if(m_LastPlaytime < time_get()-time_freq()*g_Config.m_SvMaxAfkTime)
|
|
|
|
{
|
2017-06-06 03:51:12 +00:00
|
|
|
m_pGameServer->Server()->Kick(m_ClientID, "Away from keyboard");
|
2012-04-06 20:37:16 +00:00
|
|
|
return true;
|
|
|
|
}
|
2011-03-22 19:49:12 +00:00
|
|
|
}
|
|
|
|
}
|
2011-05-06 19:16:53 +00:00
|
|
|
return false;
|
2011-03-22 19:49:12 +00:00
|
|
|
}
|
2011-06-06 19:24:27 +00:00
|
|
|
|
2014-02-21 17:27:00 +00:00
|
|
|
void CPlayer::AfkVoteTimer(CNetObj_PlayerInput *NewTarget)
|
2013-08-18 01:27:30 +00:00
|
|
|
{
|
|
|
|
if(g_Config.m_SvMaxAfkVoteTime == 0)
|
|
|
|
return;
|
|
|
|
|
2019-10-13 15:13:03 +00:00
|
|
|
if(!m_pLastTarget)
|
|
|
|
{
|
|
|
|
m_pLastTarget = new CNetObj_PlayerInput(*NewTarget);
|
|
|
|
m_LastPlaytime = 0;
|
|
|
|
m_Afk = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(mem_comp(NewTarget, m_pLastTarget, sizeof(CNetObj_PlayerInput)) != 0)
|
2013-08-18 01:27:30 +00:00
|
|
|
{
|
|
|
|
m_LastPlaytime = time_get();
|
2019-10-13 15:13:03 +00:00
|
|
|
mem_copy(m_pLastTarget, NewTarget, sizeof(CNetObj_PlayerInput));
|
2013-08-18 01:27:30 +00:00
|
|
|
}
|
|
|
|
else if(m_LastPlaytime < time_get()-time_freq()*g_Config.m_SvMaxAfkVoteTime)
|
|
|
|
{
|
|
|
|
m_Afk = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_Afk = false;
|
|
|
|
}
|
|
|
|
|
2011-12-29 13:58:39 +00:00
|
|
|
void CPlayer::ProcessPause()
|
|
|
|
{
|
2017-04-08 22:20:41 +00:00
|
|
|
if(m_ForcePauseTime && m_ForcePauseTime < Server()->Tick())
|
|
|
|
{
|
|
|
|
m_ForcePauseTime = 0;
|
2017-04-08 23:16:48 +00:00
|
|
|
Pause(PAUSE_NONE, true);
|
2017-04-08 22:20:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(m_Paused == PAUSE_SPEC && !m_pCharacter->IsPaused() && m_pCharacter->IsGrounded() && m_pCharacter->m_Pos == m_pCharacter->m_PrevPos)
|
|
|
|
{
|
|
|
|
m_pCharacter->Pause(true);
|
|
|
|
GameServer()->CreateDeath(m_pCharacter->m_Pos, m_ClientID, m_pCharacter->Teams()->TeamMask(m_pCharacter->Team(), -1, m_ClientID));
|
|
|
|
GameServer()->CreateSound(m_pCharacter->m_Pos, SOUND_PLAYER_DIE, m_pCharacter->Teams()->TeamMask(m_pCharacter->Team(), -1, m_ClientID));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-08 23:16:48 +00:00
|
|
|
int CPlayer::Pause(int State, bool Force)
|
2017-04-08 22:20:41 +00:00
|
|
|
{
|
2017-05-16 15:52:29 +00:00
|
|
|
if(State < PAUSE_NONE || State > PAUSE_SPEC) // Invalid pause state passed
|
2017-05-16 15:02:28 +00:00
|
|
|
return 0;
|
|
|
|
|
2014-05-18 23:57:52 +00:00
|
|
|
if(!m_pCharacter)
|
2017-04-08 22:20:41 +00:00
|
|
|
return 0;
|
2014-05-18 23:57:52 +00:00
|
|
|
|
2011-12-29 13:58:39 +00:00
|
|
|
char aBuf[128];
|
2017-04-08 22:20:41 +00:00
|
|
|
if(State != m_Paused)
|
2011-12-29 13:58:39 +00:00
|
|
|
{
|
2017-04-08 22:20:41 +00:00
|
|
|
// Get to wanted state
|
|
|
|
switch(State){
|
|
|
|
case PAUSE_PAUSED:
|
|
|
|
case PAUSE_NONE:
|
2017-04-08 23:16:48 +00:00
|
|
|
if(m_pCharacter->IsPaused()) // First condition might be unnecessary
|
2014-03-26 13:21:49 +00:00
|
|
|
{
|
2017-07-09 08:27:58 +00:00
|
|
|
if(!Force && m_LastPause && m_LastPause + g_Config.m_SvSpecFrequency * Server()->TickSpeed() > Server()->Tick())
|
2017-04-08 22:20:41 +00:00
|
|
|
{
|
2017-07-09 08:27:58 +00:00
|
|
|
GameServer()->SendChatTarget(m_ClientID, "Can't /spec that quickly.");
|
2017-04-08 23:16:48 +00:00
|
|
|
return m_Paused; // Do not update state. Do not collect $200
|
2017-04-08 22:20:41 +00:00
|
|
|
}
|
2017-04-08 23:16:48 +00:00
|
|
|
m_pCharacter->Pause(false);
|
2017-04-08 22:20:41 +00:00
|
|
|
GameServer()->CreatePlayerSpawn(m_pCharacter->m_Pos, m_pCharacter->Teams()->TeamMask(m_pCharacter->Team(), -1, m_ClientID));
|
2014-03-26 13:21:49 +00:00
|
|
|
}
|
2017-07-09 08:27:58 +00:00
|
|
|
// fall-thru
|
2017-04-08 23:16:48 +00:00
|
|
|
case PAUSE_SPEC:
|
2014-03-26 13:21:49 +00:00
|
|
|
if(g_Config.m_SvPauseMessages)
|
|
|
|
{
|
2018-06-14 21:41:28 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), (State > PAUSE_NONE) ? "'%s' speced" : "'%s' resumed", Server()->ClientName(m_ClientID));
|
2014-03-26 13:21:49 +00:00
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
|
|
|
|
}
|
2017-04-08 22:20:41 +00:00
|
|
|
break;
|
2011-12-29 13:58:39 +00:00
|
|
|
}
|
2017-04-08 23:16:48 +00:00
|
|
|
|
2017-04-08 22:20:41 +00:00
|
|
|
// Update state
|
|
|
|
m_Paused = State;
|
2017-04-08 23:16:48 +00:00
|
|
|
m_LastPause = Server()->Tick();
|
2011-12-29 13:58:39 +00:00
|
|
|
}
|
2017-04-08 22:20:41 +00:00
|
|
|
|
|
|
|
return m_Paused;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CPlayer::ForcePause(int Time)
|
|
|
|
{
|
|
|
|
m_ForcePauseTime = Server()->Tick() + Server()->TickSpeed() * Time;
|
|
|
|
|
2017-04-08 23:16:48 +00:00
|
|
|
if(g_Config.m_SvPauseMessages)
|
|
|
|
{
|
|
|
|
char aBuf[128];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' was force-paused for %ds", Server()->ClientName(m_ClientID), Time);
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Pause(PAUSE_SPEC, true);
|
2017-04-08 22:20:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CPlayer::IsPaused()
|
|
|
|
{
|
|
|
|
return m_ForcePauseTime ? m_ForcePauseTime : -1 * m_Paused;
|
2011-12-29 13:58:39 +00:00
|
|
|
}
|
|
|
|
|
2011-06-06 19:24:27 +00:00
|
|
|
bool CPlayer::IsPlaying()
|
|
|
|
{
|
2011-12-29 13:58:39 +00:00
|
|
|
if(m_pCharacter && m_pCharacter->IsAlive())
|
2011-06-06 19:24:27 +00:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
2013-12-31 05:13:57 +00:00
|
|
|
|
2017-09-18 17:12:01 +00:00
|
|
|
void CPlayer::SpectatePlayerName(const char *pName)
|
|
|
|
{
|
|
|
|
if(!pName)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for(int i = 0; i < MAX_CLIENTS; ++i)
|
|
|
|
{
|
|
|
|
if(i != m_ClientID && Server()->ClientIngame(i) && !str_comp(pName, Server()->ClientName(i)))
|
|
|
|
{
|
|
|
|
m_SpectatorID = i;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:22:16 +00:00
|
|
|
|
|
|
|
#if defined(CONF_SQL)
|
2020-06-08 15:11:41 +00:00
|
|
|
void CPlayer::ProcessSqlResult(CSqlPlayerResult &Result)
|
2020-05-26 21:22:16 +00:00
|
|
|
{
|
2020-06-08 15:11:41 +00:00
|
|
|
if(Result.m_Done) // SQL request was successful
|
2020-05-26 21:22:16 +00:00
|
|
|
{
|
2020-06-08 15:11:41 +00:00
|
|
|
int NumMessages = (int)(sizeof(Result.m_aaMessages)/sizeof(Result.m_aaMessages[0]));
|
|
|
|
switch(Result.m_MessageKind)
|
2020-05-26 21:22:16 +00:00
|
|
|
{
|
2020-06-02 14:27:31 +00:00
|
|
|
case CSqlPlayerResult::DIRECT:
|
|
|
|
for(int i = 0; i < NumMessages; i++)
|
2020-05-30 15:41:34 +00:00
|
|
|
{
|
2020-06-08 15:11:41 +00:00
|
|
|
if(Result.m_aaMessages[i][0] == 0)
|
2020-06-02 14:27:31 +00:00
|
|
|
break;
|
2020-06-08 15:11:41 +00:00
|
|
|
GameServer()->SendChatTarget(m_ClientID, Result.m_aaMessages[i]);
|
2020-06-02 14:27:31 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CSqlPlayerResult::ALL:
|
|
|
|
for(int i = 0; i < NumMessages; i++)
|
|
|
|
{
|
2020-06-08 15:11:41 +00:00
|
|
|
if(Result.m_aaMessages[i][0] == 0)
|
2020-06-02 14:27:31 +00:00
|
|
|
break;
|
2020-06-08 15:11:41 +00:00
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, Result.m_aaMessages[i]);
|
2020-05-30 15:41:34 +00:00
|
|
|
}
|
2020-06-02 14:27:31 +00:00
|
|
|
break;
|
2020-06-08 15:11:41 +00:00
|
|
|
case CSqlPlayerResult::BROADCAST:
|
|
|
|
if(Result.m_Data.m_Broadcast[0] != 0)
|
|
|
|
GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1);
|
|
|
|
break;
|
2020-06-02 14:27:31 +00:00
|
|
|
case CSqlPlayerResult::MAP_VOTE:
|
2020-06-14 08:44:14 +00:00
|
|
|
GameServer()->m_VoteKick = false;
|
|
|
|
GameServer()->m_VoteSpec = false;
|
|
|
|
GameServer()->m_LastMapVote = time_get();
|
|
|
|
|
|
|
|
char aCmd[256];
|
|
|
|
str_format(aCmd, sizeof(aCmd),
|
|
|
|
"sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"",
|
|
|
|
Result.m_Data.m_MapVote.m_Server, Result.m_Data.m_MapVote.m_Map);
|
|
|
|
|
|
|
|
char aChatmsg[512];
|
|
|
|
str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)",
|
|
|
|
Server()->ClientName(m_ClientID), Result.m_Data.m_MapVote.m_Map, "/map");
|
|
|
|
|
|
|
|
GameServer()->CallVote(m_ClientID, Result.m_Data.m_MapVote.m_Map, aCmd, "/map", aChatmsg);
|
2020-06-02 14:27:31 +00:00
|
|
|
break;
|
2020-06-04 15:33:10 +00:00
|
|
|
case CSqlPlayerResult::PLAYER_INFO:
|
|
|
|
GameServer()->Score()->PlayerData(m_ClientID)->Set(
|
2020-06-08 15:11:41 +00:00
|
|
|
Result.m_Data.m_Info.m_Time,
|
|
|
|
Result.m_Data.m_Info.m_CpTime
|
2020-06-04 15:33:10 +00:00
|
|
|
);
|
2020-06-08 15:11:41 +00:00
|
|
|
m_Score = Result.m_Data.m_Info.m_Score;
|
|
|
|
m_HasFinishScore = Result.m_Data.m_Info.m_HasFinishScore;
|
2020-06-17 19:15:07 +00:00
|
|
|
// -9999 stands for no time and isn't displayed in scoreboard, so
|
|
|
|
// shift the time by a second if the player actually took 9999
|
|
|
|
// seconds to finish the map.
|
|
|
|
if(m_HasFinishScore && m_Score == -9999)
|
|
|
|
m_Score = -10000;
|
2020-06-04 15:33:10 +00:00
|
|
|
Server()->ExpireServerInfo();
|
2020-06-08 15:11:41 +00:00
|
|
|
int Birthday = Result.m_Data.m_Info.m_Birthday;
|
2020-06-04 15:33:10 +00:00
|
|
|
if(Birthday != 0)
|
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
|
|
|
"Happy DDNet birthday to %s for finishing their first map %d year%s ago!",
|
|
|
|
Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : "");
|
|
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, m_ClientID);
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
|
|
|
"Happy DDNet birthday, %s!\nYou have finished your first map exactly %d year%s ago!",
|
|
|
|
Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : "");
|
|
|
|
GameServer()->SendBroadcast(aBuf, m_ClientID);
|
|
|
|
}
|
|
|
|
break;
|
2020-05-26 21:22:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|