ddnet/src/game/server/player.cpp

923 lines
26 KiB
C++
Raw Normal View History

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. */
2010-05-29 07:25:38 +00:00
#include "player.h"
#include <engine/shared/config.h>
2022-06-17 17:54:10 +00:00
#include <engine/antibot.h>
#include <engine/server.h>
#include "base/system.h"
#include "entities/character.h"
#include "gamecontext.h"
2022-06-17 17:54:10 +00:00
#include "gamecontroller.h"
#include "score.h"
2022-06-16 16:06:35 +00:00
#include <game/gamecore.h>
2022-06-16 16:06:35 +00:00
#include <game/teamscore.h>
#include <game/version.h>
2010-05-29 07:25:38 +00:00
MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS)
2010-05-29 07:25:38 +00:00
IServer *CPlayer::Server() const { return m_pGameServer->Server(); }
CPlayer::CPlayer(CGameContext *pGameServer, uint32_t UniqueClientID, int ClientID, int Team) :
m_UniqueClientID(UniqueClientID)
{
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);
m_NumInputs = 0;
2014-09-18 10:14:00 +00:00
Reset();
GameServer()->Antibot()->OnPlayerInit(m_ClientID);
}
2010-05-29 07:25:38 +00:00
CPlayer::~CPlayer()
{
GameServer()->Antibot()->OnPlayerDestroy(m_ClientID);
delete m_pLastTarget;
2011-04-19 08:42:48 +00:00
delete m_pCharacter;
m_pCharacter = 0;
}
2014-08-09 12:50:51 +00:00
void CPlayer::Reset()
{
m_DieTick = Server()->Tick();
m_PreviousDieTick = m_DieTick;
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();
m_LastInvited = 0;
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);
for(int i = 1; i < VANILLA_MAX_CLIENTS; i++)
2014-08-09 12:50:51 +00:00
{
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;
m_LastPlaytime = 0;
2014-08-09 12:50:51 +00:00
m_ChatScore = 0;
2018-01-05 11:04:06 +00:00
m_Moderating = false;
m_EyeEmoteEnabled = true;
if(Server()->IsSixup(m_ClientID))
m_TimerType = TIMERTYPE_SIXUP;
else
m_TimerType = (g_Config.m_SvDefaultTimerType == TIMERTYPE_GAMETIMER || g_Config.m_SvDefaultTimerType == TIMERTYPE_GAMETIMER_AND_BROADCAST) ? TIMERTYPE_BROADCAST : g_Config.m_SvDefaultTimerType;
2014-08-09 12:50:51 +00:00
m_DefEmote = EMOTE_NORMAL;
m_Afk = true;
2014-08-09 12:50:51 +00:00
m_LastWhisperTo = -1;
m_LastSetSpectatorMode = 0;
2021-09-13 09:47:47 +00:00
m_aTimeoutCode[0] = '\0';
delete m_pLastTarget;
2021-08-15 19:02:23 +00:00
m_pLastTarget = new CNetObj_PlayerInput({0});
m_LastTargetInit = false;
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;
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-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);
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;
}
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_OverrideEmoteReset = -1;
2014-08-09 12:50:51 +00:00
GameServer()->Score()->PlayerData(m_ClientID)->Reset();
m_ShowOthers = g_Config.m_SvShowOthersDefault;
m_ShowAll = g_Config.m_SvShowAllDefault;
2021-01-21 16:07:07 +00:00
m_ShowDistance = vec2(1200, 800);
2022-02-14 23:12:52 +00:00
m_SpecTeam = false;
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;
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;
m_LastSQLQuery = 0;
m_ScoreQueryResult = nullptr;
m_ScoreFinishResult = nullptr;
2021-06-23 05:05:49 +00:00
int64_t Now = Server()->Tick();
int64_t TickSpeed = Server()->TickSpeed();
// 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.
if(Now > GameServer()->m_NonEmptySince + 10 * TickSpeed)
m_FirstVoteTick = Now + g_Config.m_SvJoinVoteDelay * TickSpeed;
else
m_FirstVoteTick = Now;
2018-12-17 13:55:58 +00:00
m_NotEligibleForFinish = false;
m_EligibleForFinishCheck = 0;
m_VotedForPractice = false;
m_SwapTargetsClientID = -1;
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)
2020-06-16 16:05:41 +00:00
Six |= PLAYERFLAG_CHATTING;
if(Flags & protocol7::PLAYERFLAG_SCOREBOARD)
2020-06-16 16:05:41 +00:00
Six |= PLAYERFLAG_SCOREBOARD;
if(Flags & protocol7::PLAYERFLAG_AIM)
Six |= PLAYERFLAG_AIM;
2020-06-16 16:05:41 +00:00
return Six;
}
static int PlayerFlags_SixToSeven(int Flags)
{
int Seven = 0;
if(Flags & PLAYERFLAG_CHATTING)
2020-06-16 16:05:41 +00:00
Seven |= protocol7::PLAYERFLAG_CHATTING;
if(Flags & PLAYERFLAG_SCOREBOARD)
2020-06-16 16:05:41 +00:00
Seven |= protocol7::PLAYERFLAG_SCOREBOARD;
return Seven;
}
2010-05-29 07:25:38 +00:00
void CPlayer::Tick()
{
if(m_ScoreQueryResult != nullptr && m_ScoreQueryResult->m_Completed)
{
ProcessScoreResult(*m_ScoreQueryResult);
m_ScoreQueryResult = nullptr;
}
if(m_ScoreFinishResult != nullptr && m_ScoreFinishResult->m_Completed)
2020-06-08 15:11:41 +00:00
{
ProcessScoreResult(*m_ScoreFinishResult);
m_ScoreFinishResult = nullptr;
2020-06-08 15:11:41 +00:00
}
bool ClientIngame = Server()->ClientIngame(m_ClientID);
#ifdef CONF_DEBUG
if(g_Config.m_DbgDummies && m_ClientID >= MAX_CLIENTS - g_Config.m_DbgDummies)
{
ClientIngame = true;
}
#endif
if(!ClientIngame)
return;
if(m_ChatScore > 0)
2011-02-03 13:39:00 +00:00
m_ChatScore--;
2010-05-29 07:25:38 +00:00
Server()->SetClientScore(m_ClientID, m_Score);
if(m_Moderating && m_Afk)
2018-01-05 11:04:06 +00:00
{
m_Moderating = false;
GameServer()->SendChatTarget(m_ClientID, "Active moderator mode disabled because you are afk.");
if(!GameServer()->PlayerModerating())
2018-01-05 11:04:06 +00:00
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, "Server kick/spec votes are no longer actively moderated.");
}
// do latency stuff
{
2010-05-29 07:25:38 +00:00
IServer::CClientInfo Info;
if(Server()->GetClientInfo(m_ClientID, &Info))
{
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);
}
2010-05-29 07:25:38 +00:00
// each second
if(Server()->Tick() % Server()->TickSpeed() == 0)
{
m_Latency.m_Avg = m_Latency.m_Accum / Server()->TickSpeed();
2010-05-29 07:25:38 +00:00
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;
}
}
if(Server()->GetNetErrorString(m_ClientID)[0])
2014-08-17 03:04:37 +00:00
{
2020-08-10 18:56:08 +00:00
m_Afk = true;
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);
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
{
int EarliestRespawnTick = m_PreviousDieTick + Server()->TickSpeed() * 3;
int RespawnTick = maximum(m_DieTick, EarliestRespawnTick) + 2;
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())
{
2017-04-08 22:20:41 +00:00
ProcessPause();
if(!m_Paused)
m_ViewPos = m_pCharacter->m_Pos;
}
else if(!m_pCharacter->IsPaused())
{
2012-01-09 23:49:31 +00:00
delete m_pCharacter;
m_pCharacter = 0;
}
2008-09-24 14:54:51 +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;
++m_PreviousDieTick;
++m_JoinTick;
2012-01-09 23:49:31 +00:00
++m_LastActionTick;
++m_TeamChangeTick;
2014-04-18 12:50:15 +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
if(m_TuneZone != m_TuneZoneOld) // don't send tunings all the time
{
GameServer()->SendTuningParams(m_ClientID, m_TuneZone);
}
if(m_OverrideEmoteReset >= 0 && m_OverrideEmoteReset <= Server()->Tick())
{
m_OverrideEmoteReset = -1;
}
if(m_Halloween && m_pCharacter && !m_pCharacter->IsPaused())
{
if(1200 - ((Server()->Tick() - m_pCharacter->GetLastAction()) % (1200)) < 5)
{
GameServer()->SendEmoticon(GetCID(), EMOTICON_GHOST);
}
}
}
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_aCurLatency[i] = GameServer()->m_apPlayers[i]->m_Latency.m_Min;
}
}
// update view pos for spectators
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;
}
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)
2020-05-27 15:49:07 +00:00
#endif
if(!Server()->ClientIngame(m_ClientID))
return;
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)
{
2011-01-18 18:09:53 +00:00
#ifdef CONF_DEBUG
if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS - g_Config.m_DbgDummies)
2011-01-18 18:09:53 +00:00
#endif
if(!Server()->ClientIngame(m_ClientID))
return;
2013-12-31 05:13:57 +00:00
int id = m_ClientID;
if(!Server()->Translate(id, SnappingClient))
2020-06-12 19:22:54 +00:00
return;
2013-12-31 05:13:57 +00:00
CNetObj_ClientInfo *pClientInfo = static_cast<CNetObj_ClientInfo *>(Server()->SnapNewItem(NETOBJTYPE_CLIENTINFO, id, sizeof(CNetObj_ClientInfo)));
if(!pClientInfo)
return;
2010-05-29 07:25:38 +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);
StrToInts(&pClientInfo->m_Skin0, 6, m_TeeInfos.m_SkinName);
2019-06-04 15:23:29 +00:00
pClientInfo->m_UseCustomColor = m_TeeInfos.m_UseCustomColor;
pClientInfo->m_ColorBody = m_TeeInfos.m_ColorBody;
pClientInfo->m_ColorFeet = m_TeeInfos.m_ColorFeet;
int SnappingClientVersion = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
int Latency = SnappingClient == SERVER_DEMO_CLIENT ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aCurLatency[m_ClientID];
2020-03-29 02:36:38 +00:00
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;
if(!Server()->IsSixup(SnappingClient))
2020-03-29 02:36:38 +00:00
{
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 || SnappingClientVersion >= VERSION_DDNET_OLD));
2020-03-29 02:36:38 +00:00
pPlayerInfo->m_ClientID = id;
2022-03-14 12:25:47 +00:00
pPlayerInfo->m_Team = m_Team;
if(SnappingClientVersion < VERSION_DDNET_INDEPENDENT_SPECTATORS_TEAM)
{
// In older versions the SPECTATORS TEAM was also used if the own player is in PAUSE_PAUSED or if any player is in PAUSE_SPEC.
pPlayerInfo->m_Team = (m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS;
}
2020-03-29 02:36:38 +00:00
}
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(SnappingClientVersion >= VERSION_DDRACE && (m_PlayerFlags & PLAYERFLAG_AIM))
pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_AIM;
2020-06-16 16:05:41 +00:00
if(Server()->ClientAuthed(m_ClientID))
pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_ADMIN;
2020-06-20 14:30:58 +00:00
// Times are in milliseconds for 0.7
pPlayerInfo->m_Score = Score == -9999 ? -1 : -Score * 1000;
2020-06-12 19:22:54 +00:00
pPlayerInfo->m_Latency = Latency;
2020-03-29 02:36:38 +00:00
}
if(m_ClientID == SnappingClient && (m_Team == TEAM_SPECTATORS || m_Paused))
{
if(!Server()->IsSixup(SnappingClient))
2020-06-12 20:53:41 +00:00
{
CNetObj_SpectatorInfo *pSpectatorInfo = static_cast<CNetObj_SpectatorInfo *>(Server()->SnapNewItem(NETOBJTYPE_SPECTATORINFO, m_ClientID, sizeof(CNetObj_SpectatorInfo)));
if(!pSpectatorInfo)
return;
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;
}
}
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)
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;
if(Server()->IsSixup(SnappingClient) && m_pCharacter && m_pCharacter->m_DDRaceState == DDRACE_STARTED &&
GameServer()->m_apPlayers[SnappingClient]->m_TimerType == TIMERTYPE_SIXUP)
{
protocol7::CNetObj_PlayerInfoRace *pRaceInfo = static_cast<protocol7::CNetObj_PlayerInfoRace *>(Server()->SnapNewItem(-protocol7::NETOBJTYPE_PLAYERINFORACE, id, sizeof(protocol7::CNetObj_PlayerInfoRace)));
if(!pRaceInfo)
return;
pRaceInfo->m_RaceStartTick = m_pCharacter->m_StartTime;
}
2021-09-02 00:34:32 +00:00
bool ShowSpec = m_pCharacter && m_pCharacter->IsPaused() && m_pCharacter->CanSnapCharacter(SnappingClient);
if(SnappingClient != SERVER_DEMO_CLIENT)
2021-09-02 00:34:32 +00:00
{
CPlayer *pSnapPlayer = GameServer()->m_apPlayers[SnappingClient];
2022-03-03 21:56:32 +00:00
ShowSpec = ShowSpec && (GameServer()->GetDDRaceTeam(m_ClientID) == GameServer()->GetDDRaceTeam(SnappingClient) || pSnapPlayer->m_ShowOthers == SHOW_OTHERS_ON || (pSnapPlayer->GetTeam() == TEAM_SPECTATORS || pSnapPlayer->IsPaused()));
2021-09-02 00:34:32 +00:00
}
if(ShowSpec)
{
CNetObj_SpecChar *pSpecChar = static_cast<CNetObj_SpecChar *>(Server()->SnapNewItem(NETOBJTYPE_SPECCHAR, id, sizeof(CNetObj_SpecChar)));
if(!pSpecChar)
return;
pSpecChar->m_X = m_pCharacter->Core()->m_Pos.x;
pSpecChar->m_Y = m_pCharacter->Core()->m_Pos.y;
}
}
void CPlayer::FakeSnap()
2013-12-31 05:13:57 +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;
int FakeID = VANILLA_MAX_CLIENTS - 1;
2013-12-31 05:13:57 +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, " ");
StrToInts(&pClientInfo->m_Clan0, 3, "");
StrToInts(&pClientInfo->m_Skin0, 6, "default");
2017-05-18 21:52:54 +00:00
if(m_Paused != PAUSE_PAUSED)
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
}
void CPlayer::OnDisconnect()
{
2010-05-29 07:25:38 +00:00
KillCharacter();
m_Moderating = false;
}
2010-05-29 07:25:38 +00:00
void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput)
{
// skip the input if chat is active
if((m_PlayerFlags & PLAYERFLAG_CHATTING) && (NewInput->m_PlayerFlags & PLAYERFLAG_CHATTING))
return;
2022-06-11 17:32:06 +00:00
AfkTimer();
2014-02-21 17:27:00 +00:00
m_NumInputs++;
if(m_pCharacter && !m_Paused)
2011-04-19 08:42:48 +00:00
m_pCharacter->OnPredictedInput(NewInput);
// Magic number when we can hope that client has successfully identified itself
if(m_NumInputs == 20 && g_Config.m_SvClientSuggestion[0] != '\0' && GetClientVersion() <= VERSION_DDNET_OLD)
GameServer()->SendBroadcast(g_Config.m_SvClientSuggestion, m_ClientID);
else if(m_NumInputs == 200 && Server()->IsSixup(m_ClientID))
GameServer()->SendBroadcast("This server uses an experimental translation from Teeworlds 0.7 to 0.6. Please report bugs on ddnet.tw/discord", m_ClientID);
}
2010-05-29 07:25:38 +00:00
void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
{
2020-06-16 16:05:41 +00:00
if(Server()->IsSixup(m_ClientID))
NewInput->m_PlayerFlags = PlayerFlags_SevenToSix(NewInput->m_PlayerFlags);
if(NewInput->m_PlayerFlags)
Server()->SetClientFlags(m_ClientID, NewInput->m_PlayerFlags);
2022-06-11 17:32:06 +00:00
AfkTimer();
2013-08-18 01:27:30 +00:00
if(((!m_pCharacter && m_Team == TEAM_SPECTATORS) || m_Paused) && m_SpectatorID == SPEC_FREEVIEW)
m_ViewPos = vec2(NewInput->m_TargetX, NewInput->m_TargetY);
// check for activity
2021-08-15 19:02:23 +00:00
if(mem_comp(NewInput, m_pLastTarget, sizeof(CNetObj_PlayerInput)))
{
2021-08-15 19:02:23 +00:00
mem_copy(m_pLastTarget, NewInput, sizeof(CNetObj_PlayerInput));
// Ignore the first direct input and keep the player afk as it is sent automatically
if(m_LastTargetInit)
UpdatePlaytime();
m_LastActionTick = Server()->Tick();
m_LastTargetInit = true;
}
}
2019-01-29 19:32:11 +00:00
void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput)
{
m_PlayerFlags = NewInput->m_PlayerFlags;
if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (NewInput->m_Fire & 1))
m_Spawning = true;
2019-01-29 19:32:11 +00:00
// skip the input if chat is active
if(m_PlayerFlags & PLAYERFLAG_CHATTING)
2019-01-29 19:32:11 +00:00
return;
if(m_pCharacter && !m_Paused)
m_pCharacter->OnDirectInput(NewInput);
}
int CPlayer::GetClientVersion() const
{
return m_pGameServer->GetClientVersion(m_ClientID);
}
2010-05-29 07:25:38 +00:00
CCharacter *CPlayer::GetCharacter()
{
2011-04-19 08:42:48 +00:00
if(m_pCharacter && m_pCharacter->IsAlive())
return m_pCharacter;
return 0;
}
2010-05-29 07:25:38 +00:00
void CPlayer::KillCharacter(int Weapon)
{
2011-04-19 08:42:48 +00:00
if(m_pCharacter)
{
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;
}
}
void CPlayer::Respawn(bool WeakHook)
{
2011-01-03 11:50:38 +00:00
if(m_Team != TEAM_SPECTATORS)
{
m_WeakHookSpawn = WeakHook;
2010-05-29 07:25:38 +00:00
m_Spawning = true;
}
}
CCharacter *CPlayer::ForceSpawn(vec2 Pos)
2014-07-26 12:46:31 +00:00
{
2014-08-31 12:12:03 +00:00
m_Spawning = false;
m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World, GameServer()->GetLastPlayerInput(m_ClientID));
2014-07-26 12:46:31 +00:00
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
}
void CPlayer::SetTeam(int Team, bool DoChatMsg)
{
2010-05-29 07:25:38 +00:00
KillCharacter();
2010-05-29 07:25:38 +00:00
m_Team = Team;
m_LastSetTeam = Server()->Tick();
m_LastActionTick = Server()->Tick();
2013-02-24 16:24:12 +00:00
m_SpectatorID = SPEC_FREEVIEW;
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
if(Team == TEAM_SPECTATORS)
{
// update spectator modes
2020-10-26 14:14:07 +00:00
for(auto &pPlayer : GameServer()->m_apPlayers)
{
2020-10-26 14:14:07 +00:00
if(pPlayer && pPlayer->m_SpectatorID == m_ClientID)
pPlayer->m_SpectatorID = SPEC_FREEVIEW;
}
}
}
bool CPlayer::SetTimerType(int TimerType)
{
if(TimerType == TIMERTYPE_DEFAULT)
{
if(Server()->IsSixup(m_ClientID))
m_TimerType = TIMERTYPE_SIXUP;
else
SetTimerType(g_Config.m_SvDefaultTimerType);
return true;
}
if(Server()->IsSixup(m_ClientID))
{
if(TimerType == TIMERTYPE_SIXUP || TimerType == TIMERTYPE_NONE)
{
m_TimerType = TimerType;
return true;
}
else
return false;
}
if(TimerType == TIMERTYPE_GAMETIMER)
{
if(GetClientVersion() >= VERSION_DDNET_GAMETICK)
m_TimerType = TimerType;
else
return false;
}
else if(TimerType == TIMERTYPE_GAMETIMER_AND_BROADCAST)
{
if(GetClientVersion() >= VERSION_DDNET_GAMETICK)
m_TimerType = TimerType;
else
{
m_TimerType = TIMERTYPE_BROADCAST;
return false;
}
}
else
m_TimerType = TimerType;
return true;
}
2010-05-29 07:25:38 +00:00
void CPlayer::TryRespawn()
{
vec2 SpawnPos;
if(!GameServer()->m_pController->CanSpawn(m_Team, &SpawnPos, GameServer()->GetDDRaceTeam(m_ClientID)))
return;
m_WeakHookSpawn = false;
m_Spawning = false;
m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World, GameServer()->GetLastPlayerInput(m_ClientID));
2021-04-30 07:37:20 +00:00
m_ViewPos = SpawnPos;
m_pCharacter->Spawn(this, SpawnPos);
GameServer()->CreatePlayerSpawn(SpawnPos, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID));
if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
m_pCharacter->SetSolo(true);
}
void CPlayer::UpdatePlaytime()
{
m_LastPlaytime = time_get();
}
2022-06-11 17:32:06 +00:00
void CPlayer::AfkTimer()
2013-08-18 01:27:30 +00:00
{
2022-06-11 17:32:06 +00:00
if(g_Config.m_SvMaxAfkTime == 0)
2013-08-18 01:27:30 +00:00
return;
2022-06-11 17:32:06 +00:00
if(m_LastPlaytime < time_get() - time_freq() * g_Config.m_SvMaxAfkTime)
2013-08-18 01:27:30 +00:00
{
m_Afk = true;
return;
}
m_Afk = false;
}
2021-01-12 19:15:10 +00:00
int CPlayer::GetDefaultEmote() const
{
if(m_OverrideEmoteReset >= 0)
return m_OverrideEmote;
2021-01-12 19:15:10 +00:00
return m_DefEmote;
}
void CPlayer::OverrideDefaultEmote(int Emote, int Tick)
{
m_OverrideEmote = Emote;
m_OverrideEmoteReset = Tick;
2021-01-12 19:15:10 +00:00
m_LastEyeEmote = Server()->Tick();
}
bool CPlayer::CanOverrideDefaultEmote() const
{
2021-06-23 05:05:49 +00:00
return m_LastEyeEmote == 0 || m_LastEyeEmote + (int64_t)g_Config.m_SvEyeEmoteChangeDelay * Server()->TickSpeed() < Server()->Tick();
2021-01-12 19:15:10 +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, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID));
GameServer()->CreateSound(m_pCharacter->m_Pos, SOUND_PLAYER_DIE, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID));
2017-04-08 22:20:41 +00:00
}
}
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
return 0;
if(!m_pCharacter)
2017-04-08 22:20:41 +00:00
return 0;
char aBuf[128];
2017-04-08 22:20:41 +00:00
if(State != m_Paused)
{
2017-04-08 22:20:41 +00:00
// Get to wanted state
switch(State)
{
2017-04-08 22:20:41 +00:00
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
{
2021-06-23 05:05:49 +00:00
if(!Force && m_LastPause && m_LastPause + (int64_t)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);
2021-04-30 07:37:20 +00:00
m_ViewPos = m_pCharacter->m_Pos;
GameServer()->CreatePlayerSpawn(m_pCharacter->m_Pos, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID));
2014-03-26 13:21:49 +00:00
}
2022-06-25 13:30:39 +00:00
[[fallthrough]];
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;
}
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();
2020-07-12 08:04:41 +00:00
// Sixup needs a teamchange
protocol7::CNetMsg_Sv_Team Msg;
Msg.m_ClientID = m_ClientID;
Msg.m_CooldownTick = Server()->Tick();
Msg.m_Silent = true;
Msg.m_Team = m_Paused ? protocol7::TEAM_SPECTATORS : m_Team;
GameServer()->Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, m_ClientID);
}
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;
}
bool CPlayer::IsPlaying()
{
2022-01-22 16:34:23 +00:00
return m_pCharacter && m_pCharacter->IsAlive();
}
2013-12-31 05:13:57 +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;
}
}
}
void CPlayer::ProcessScoreResult(CScorePlayerResult &Result)
{
if(Result.m_Success) // SQL request was successful
{
2020-06-08 15:11:41 +00:00
switch(Result.m_MessageKind)
{
case CScorePlayerResult::DIRECT:
2020-10-26 14:14:07 +00:00
for(auto &aMessage : Result.m_Data.m_aaMessages)
{
2020-10-26 14:14:07 +00:00
if(aMessage[0] == 0)
2020-06-02 14:27:31 +00:00
break;
2020-10-26 14:14:07 +00:00
GameServer()->SendChatTarget(m_ClientID, aMessage);
2020-06-02 14:27:31 +00:00
}
break;
case CScorePlayerResult::ALL:
{
bool PrimaryMessage = true;
2020-10-26 14:14:07 +00:00
for(auto &aMessage : Result.m_Data.m_aaMessages)
2020-06-02 14:27:31 +00:00
{
2020-10-26 14:14:07 +00:00
if(aMessage[0] == 0)
2020-06-02 14:27:31 +00:00
break;
if(GameServer()->ProcessSpamProtection(m_ClientID) && PrimaryMessage)
break;
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aMessage, -1);
PrimaryMessage = false;
}
2020-06-02 14:27:31 +00:00
break;
}
case CScorePlayerResult::BROADCAST:
2021-09-13 09:47:47 +00:00
if(Result.m_Data.m_aBroadcast[0] != 0)
GameServer()->SendBroadcast(Result.m_Data.m_aBroadcast, -1);
2020-06-08 15:11:41 +00:00
break;
case CScorePlayerResult::MAP_VOTE:
2020-07-01 12:02:47 +00:00
GameServer()->m_VoteType = CGameContext::VOTE_TYPE_OPTION;
2020-06-14 08:44:14 +00:00
GameServer()->m_LastMapVote = time_get();
char aCmd[256];
str_format(aCmd, sizeof(aCmd),
"sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"",
2021-09-13 09:47:47 +00:00
Result.m_Data.m_MapVote.m_aServer, Result.m_Data.m_MapVote.m_aMap);
2020-06-14 08:44:14 +00:00
char aChatmsg[512];
str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)",
2021-09-13 09:47:47 +00:00
Server()->ClientName(m_ClientID), Result.m_Data.m_MapVote.m_aMap, "/map");
2020-06-14 08:44:14 +00:00
2021-09-13 09:47:47 +00:00
GameServer()->CallVote(m_ClientID, Result.m_Data.m_MapVote.m_aMap, aCmd, "/map", aChatmsg);
2020-06-02 14:27:31 +00:00
break;
case CScorePlayerResult::PLAYER_INFO:
GameServer()->Score()->PlayerData(m_ClientID)->Set(Result.m_Data.m_Info.m_Time, Result.m_Data.m_Info.m_CpTime);
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;
Server()->ExpireServerInfo();
2020-06-08 15:11:41 +00:00
int Birthday = Result.m_Data.m_Info.m_Birthday;
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;
}
}
}