Compare commits

...

10 commits

Author SHA1 Message Date
Freddie W b8eee4c25f
Merge 7c45688fd2 into fc058fa432 2024-09-07 12:03:01 +02:00
Dennis Felsing fc058fa432
Merge pull request #8906 from Robyt3/Client-Download-Gameserver-Fix
Fix map download fallback from game server
2024-09-06 22:11:02 +00:00
Dennis Felsing 3b22a3e02f
Merge pull request #8905 from furo321/hot-reload-super-crash
Fix crash with `hot_reload` while in `super`
2024-09-06 22:08:14 +00:00
Robert Müller ed1ef4e694 Fix map download fallback from game server
Do not reset the active map download's information before using the fallback map download.

Remove redundant calls of `ResetMapDownload` before disconnecting, as this already resets the map download.

Closes #8885. Regression from #8848.
2024-09-06 20:55:02 +02:00
furo 9103332e36 Fix crash with hot_reload while in super 2024-09-06 20:12:07 +02:00
Dennis Felsing 0948a53648
Merge pull request #8904 from furo321/fix-force-yes
Fix `random_unfinished_map` not working with `vote yes`
2024-09-06 17:34:21 +00:00
furo bbd34c9452 Fix random_unfinished_map not working with vote yes 2024-09-06 18:56:58 +02:00
Dennis Felsing e4282f100a
Merge pull request #8902 from KebsCS/pr-hotreload-lasttp
Add /lasttp to hot reload
2024-09-06 16:24:48 +00:00
KebsCS eb9e73f68b
Add /lasttp to hot reload 2024-09-06 16:31:46 +02:00
TsFreddie 7c45688fd2 Add spectator cursor 2024-08-28 15:48:52 +08:00
12 changed files with 186 additions and 30 deletions

View file

@ -378,6 +378,15 @@ Objects = [
NetEventEx("MapSoundWorld:Common", "map-sound-world@netevent.ddnet.org", [ NetEventEx("MapSoundWorld:Common", "map-sound-world@netevent.ddnet.org", [
NetIntAny("m_SoundId"), NetIntAny("m_SoundId"),
]), ]),
# Spectating cursor
NetObjectEx("SpecCursor", "spec-cursor@netobj.ddnet.org", [
NetIntAny("m_Weapon"),
NetIntAny("m_TargetX"),
NetIntAny("m_TargetY"),
NetIntAny("m_DeltaPrevTargetX"),
NetIntAny("m_DeltaPrevTargetY"),
]),
] ]
Messages = [ Messages = [

View file

@ -668,10 +668,7 @@ void CClient::DisconnectWithReason(const char *pReason)
m_CurrentServerCurrentPingTime = -1; m_CurrentServerCurrentPingTime = -1;
m_CurrentServerNextPingTime = -1; m_CurrentServerNextPingTime = -1;
ResetMapDownload(); ResetMapDownload(true);
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
// clear the current server info // clear the current server info
mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
@ -1528,7 +1525,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
DummyDisconnect(0); DummyDisconnect(0);
} }
ResetMapDownload(); ResetMapDownload(true);
SHA256_DIGEST *pMapSha256 = nullptr; SHA256_DIGEST *pMapSha256 = nullptr;
const char *pMapUrl = nullptr; const char *pMapUrl = nullptr;
@ -2196,7 +2193,7 @@ int CClient::UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo)
return Builder.Finish(pTo); return Builder.Finish(pTo);
} }
void CClient::ResetMapDownload() void CClient::ResetMapDownload(bool ResetActive)
{ {
if(m_pMapdownloadTask) if(m_pMapdownloadTask)
{ {
@ -2215,19 +2212,24 @@ void CClient::ResetMapDownload()
Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE); Storage()->RemoveFile(m_aMapdownloadFilenameTemp, IStorage::TYPE_SAVE);
} }
if(ResetActive)
{
m_MapdownloadChunk = 0; m_MapdownloadChunk = 0;
m_MapdownloadSha256Present = false; m_MapdownloadSha256Present = false;
m_MapdownloadSha256 = SHA256_ZEROED; m_MapdownloadSha256 = SHA256_ZEROED;
m_MapdownloadCrc = 0; m_MapdownloadCrc = 0;
m_MapdownloadTotalsize = -1; m_MapdownloadTotalsize = -1;
m_MapdownloadAmount = 0; m_MapdownloadAmount = 0;
m_aMapdownloadFilename[0] = '\0';
m_aMapdownloadFilenameTemp[0] = '\0';
m_aMapdownloadName[0] = '\0';
}
} }
void CClient::FinishMapDownload() void CClient::FinishMapDownload()
{ {
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
const int PrevMapdownloadTotalsize = m_MapdownloadTotalsize;
SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : nullptr; SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : nullptr;
bool FileSuccess = true; bool FileSuccess = true;
@ -2236,7 +2238,6 @@ void CClient::FinishMapDownload()
FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE); FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE);
if(!FileSuccess) if(!FileSuccess)
{ {
ResetMapDownload();
char aError[128 + IO_MAX_PATH_LENGTH]; char aError[128 + IO_MAX_PATH_LENGTH];
str_format(aError, sizeof(aError), Localize("Could not save downloaded map. Try manually deleting this file: %s"), m_aMapdownloadFilename); str_format(aError, sizeof(aError), Localize("Could not save downloaded map. Try manually deleting this file: %s"), m_aMapdownloadFilename);
DisconnectWithReason(aError); DisconnectWithReason(aError);
@ -2246,19 +2247,17 @@ void CClient::FinishMapDownload()
const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc); const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc);
if(!pError) if(!pError)
{ {
ResetMapDownload(); ResetMapDownload(true);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
SendReady(CONN_MAIN); SendReady(CONN_MAIN);
} }
else if(m_pMapdownloadTask) // fallback else if(m_pMapdownloadTask) // fallback
{ {
ResetMapDownload(); ResetMapDownload(false);
m_MapdownloadTotalsize = PrevMapdownloadTotalsize;
SendMapRequest(); SendMapRequest();
} }
else else
{ {
ResetMapDownload();
DisconnectWithReason(pError); DisconnectWithReason(pError);
} }
} }
@ -2784,7 +2783,7 @@ void CClient::Update()
else if(m_pMapdownloadTask->State() == EHttpState::ERROR || m_pMapdownloadTask->State() == EHttpState::ABORTED) else if(m_pMapdownloadTask->State() == EHttpState::ERROR || m_pMapdownloadTask->State() == EHttpState::ABORTED)
{ {
dbg_msg("webdl", "http failed, falling back to gameserver"); dbg_msg("webdl", "http failed, falling back to gameserver");
ResetMapDownload(); ResetMapDownload(false);
SendMapRequest(); SendMapRequest();
} }
} }

View file

@ -360,7 +360,7 @@ public:
int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo); int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo);
void ResetMapDownload(); void ResetMapDownload(bool ResetActive);
void FinishMapDownload(); void FinishMapDownload();
void RequestDDNetInfo() override; void RequestDDNetInfo() override;

View file

@ -73,6 +73,9 @@ MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE,
MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds") MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds")
MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes") MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes")
MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last") MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last")
MACRO_CONFIG_INT(ClSpecCursor, cl_spec_cursor, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable the cursor of spectating player if available")
MACRO_CONFIG_INT(ClSpecCursorInterp, cl_spec_cursor_interp, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interpolate the cursor of spectating player")
MACRO_CONFIG_INT(ClSpecCursorDemo, cl_spec_cursor_demo, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show cursor during demo playback if available")
MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator") MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator")
MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded") MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded")

View file

@ -585,16 +585,35 @@ void CHud::RenderTeambalanceWarning()
void CHud::RenderCursor() void CHud::RenderCursor()
{ {
if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK) int CurWeapon;
vec2 TargetPos;
if(Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_pLocalCharacter)
{
// render local cursor
CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy];
}
else if(g_Config.m_ClSpecCursor && m_pClient->m_Snap.m_pSpecCursor && m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW)
{
// render spec cursor
CurWeapon = m_pClient->m_Snap.m_pSpecCursor->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_Snap.m_SpecInfo.m_Position + m_pClient->m_Snap.m_DisplayCursorPos;
}
else if(g_Config.m_ClSpecCursorDemo && m_pClient->m_Snap.m_pSpecCursor && Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_pLocalCharacter)
{
CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_LocalCharacterPos + m_pClient->m_Snap.m_DisplayCursorPos;
}
else
{
return; return;
}
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
// render cursor
int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f); Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpriteWeaponCursors[CurWeapon]); Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpriteWeaponCursors[CurWeapon]);
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aCursorOffset[CurWeapon], m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].x, m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].y); Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aCursorOffset[CurWeapon], TargetPos.x, TargetPos.y);
} }
void CHud::PrepareAmmoHealthAndArmorQuads() void CHud::PrepareAmmoHealthAndArmorQuads()

View file

@ -718,6 +718,8 @@ void CGameClient::UpdatePositions()
if(!m_MultiViewActivated && m_MultiView.m_IsInit) if(!m_MultiViewActivated && m_MultiView.m_IsInit)
ResetMultiView(); ResetMultiView();
UpdateSpectatorCursor();
UpdateRenderedCharacters(); UpdateRenderedCharacters();
} }
@ -1831,6 +1833,11 @@ void CGameClient::OnNewSnapshot()
m_aSwitchStateTeam[g_Config.m_ClDummy] = -1; m_aSwitchStateTeam[g_Config.m_ClDummy] = -1;
GotSwitchStateTeam = true; GotSwitchStateTeam = true;
} }
else if(Item.m_Type == NETOBJTYPE_SPECCURSOR)
{
m_Snap.m_pSpecCursor = (const CNetObj_SpecCursor *)Item.m_pData;
m_Snap.m_pPrevSpecCursor = (const CNetObj_SpecCursor *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECCURSOR, Item.m_Id);
}
} }
} }
@ -2985,6 +2992,55 @@ void CGameClient::UpdatePrediction()
m_GameWorld.NetObjEnd(); m_GameWorld.NetObjEnd();
} }
void CGameClient::UpdateSpectatorCursor()
{
if(m_Snap.m_pSpecCursor)
{
const float IntraTickSincePrev = Client()->IntraGameTickSincePrev(g_Config.m_ClDummy);
// Decode previous cursor position from current snapshot
const int TargetX = m_Snap.m_pSpecCursor->m_TargetX;
const int TargetY = m_Snap.m_pSpecCursor->m_TargetY;
const int PrevTargetX = TargetX + m_Snap.m_pSpecCursor->m_DeltaPrevTargetX;
const int PrevTargetY = TargetY + m_Snap.m_pSpecCursor->m_DeltaPrevTargetY;
int PrevPrevTargetX, PrevPrevTargetY;
int PrevPrevPrevTargetX, PrevPrevPrevTargetY;
if(m_Snap.m_pPrevSpecCursor)
{
PrevPrevTargetX = m_Snap.m_pPrevSpecCursor->m_TargetX;
PrevPrevTargetY = m_Snap.m_pPrevSpecCursor->m_TargetY;
PrevPrevPrevTargetX = PrevPrevTargetX + m_Snap.m_pPrevSpecCursor->m_DeltaPrevTargetX;
PrevPrevPrevTargetY = PrevPrevTargetY + m_Snap.m_pPrevSpecCursor->m_DeltaPrevTargetY;
}
else
{
PrevPrevTargetX = PrevTargetX;
PrevPrevTargetY = PrevTargetY;
PrevPrevPrevTargetX = PrevTargetX;
PrevPrevPrevTargetY = PrevTargetX;
}
if(IntraTickSincePrev <= 1.0f)
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevPrevPrevTargetX, PrevPrevPrevTargetY), vec2(PrevPrevTargetX, PrevPrevTargetY), Intra);
}
else if(IntraTickSincePrev <= 2.0f)
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev - 1.0f : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevPrevTargetX, PrevPrevTargetY), vec2(PrevTargetX, PrevTargetY), Intra);
}
else
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev - 2.0f : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevTargetX, PrevTargetY), vec2(TargetX, TargetY), Intra);
}
}
}
void CGameClient::UpdateRenderedCharacters() void CGameClient::UpdateRenderedCharacters()
{ {
for(int i = 0; i < MAX_CLIENTS; i++) for(int i = 0; i < MAX_CLIENTS; i++)

View file

@ -313,6 +313,8 @@ public:
const CNetObj_PlayerInfo *m_pLocalInfo; const CNetObj_PlayerInfo *m_pLocalInfo;
const CNetObj_SpectatorInfo *m_pSpectatorInfo; const CNetObj_SpectatorInfo *m_pSpectatorInfo;
const CNetObj_SpectatorInfo *m_pPrevSpectatorInfo; const CNetObj_SpectatorInfo *m_pPrevSpectatorInfo;
const CNetObj_SpecCursor *m_pSpecCursor;
const CNetObj_SpecCursor *m_pPrevSpecCursor;
const CNetObj_Flag *m_apFlags[2]; const CNetObj_Flag *m_apFlags[2];
const CNetObj_GameInfo *m_pGameInfoObj; const CNetObj_GameInfo *m_pGameInfoObj;
const CNetObj_GameData *m_pGameDataObj; const CNetObj_GameData *m_pGameDataObj;
@ -338,6 +340,9 @@ public:
vec2 m_Position; vec2 m_Position;
} m_SpecInfo; } m_SpecInfo;
// cursor data
vec2 m_DisplayCursorPos;
// //
struct CCharacterInfo struct CCharacterInfo
{ {
@ -794,6 +799,7 @@ private:
int m_aShowOthers[NUM_DUMMIES]; int m_aShowOthers[NUM_DUMMIES];
void UpdatePrediction(); void UpdatePrediction();
void UpdateSpectatorCursor();
void UpdateRenderedCharacters(); void UpdateRenderedCharacters();
int m_aLastUpdateTick[MAX_CLIENTS] = {0}; int m_aLastUpdateTick[MAX_CLIENTS] = {0};

View file

@ -123,6 +123,13 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
delete GameServer()->m_apSavedTees[m_pPlayer->GetCid()]; delete GameServer()->m_apSavedTees[m_pPlayer->GetCid()];
GameServer()->m_apSavedTees[m_pPlayer->GetCid()] = nullptr; GameServer()->m_apSavedTees[m_pPlayer->GetCid()] = nullptr;
} }
if(GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()])
{
m_pPlayer->m_LastTeleTee = *GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()];
delete GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()];
GameServer()->m_apSavedTeleTees[m_pPlayer->GetCid()] = nullptr;
}
} }
return true; return true;
@ -1136,6 +1143,24 @@ void CCharacter::SnapCharacter(int SnappingClient, int Id)
} }
} }
void CCharacter::SnapSpecCursor(int SnappingClient)
{
CNetObj_SpecCursor *pCursorInfo = static_cast<CNetObj_SpecCursor *>(Server()->SnapNewItem(NETOBJTYPE_SPECCURSOR, SnappingClient, sizeof(CNetObj_SpecCursor)));
if(pCursorInfo)
{
pCursorInfo->m_Weapon = GetActiveWeapon();
pCursorInfo->m_TargetX = m_Input.m_TargetX;
pCursorInfo->m_TargetY = m_Input.m_TargetY;
/*
ensure info density at SERVER_TICK_SPEED even if sv_high_bandwidth is 0
*/
pCursorInfo->m_DeltaPrevTargetX = m_PrevInput.m_TargetX - m_Input.m_TargetX;
pCursorInfo->m_DeltaPrevTargetY = m_PrevInput.m_TargetY - m_Input.m_TargetY;
}
}
bool CCharacter::CanSnapCharacter(int SnappingClient) bool CCharacter::CanSnapCharacter(int SnappingClient)
{ {
if(SnappingClient == SERVER_DEMO_CLIENT) if(SnappingClient == SERVER_DEMO_CLIENT)

View file

@ -100,6 +100,8 @@ public:
void AddVelocity(vec2 Addition); void AddVelocity(vec2 Addition);
void ApplyMoveRestrictions(); void ApplyMoveRestrictions();
void SnapSpecCursor(int SnappingClient);
private: private:
// player controlling this character // player controlling this character
class CPlayer *m_pPlayer; class CPlayer *m_pPlayer;

View file

@ -108,6 +108,9 @@ void CGameContext::Construct(int Resetting)
for(auto &pSavedTee : m_apSavedTees) for(auto &pSavedTee : m_apSavedTees)
pSavedTee = nullptr; pSavedTee = nullptr;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
pSavedTeleTee = nullptr;
for(auto &pSavedTeam : m_apSavedTeams) for(auto &pSavedTeam : m_apSavedTeams)
pSavedTeam = nullptr; pSavedTeam = nullptr;
@ -131,6 +134,9 @@ void CGameContext::Destruct(int Resetting)
for(auto &pSavedTee : m_apSavedTees) for(auto &pSavedTee : m_apSavedTees)
delete pSavedTee; delete pSavedTee;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
delete pSavedTeleTee;
for(auto &pSavedTeam : m_apSavedTeams) for(auto &pSavedTeam : m_apSavedTeams)
delete pSavedTeam; delete pSavedTeam;
@ -773,7 +779,6 @@ void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char
{ {
// reset votes // reset votes
m_VoteEnforce = VOTE_ENFORCE_UNKNOWN; m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
m_VoteEnforcer = -1;
for(auto &pPlayer : m_apPlayers) for(auto &pPlayer : m_apPlayers)
{ {
if(pPlayer) if(pPlayer)
@ -1204,7 +1209,7 @@ void CGameContext::OnTick()
} }
else if(m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN) else if(m_VoteEnforce == VOTE_ENFORCE_YES_ADMIN)
{ {
Console()->ExecuteLine(m_aVoteCommand, m_VoteEnforcer); Console()->ExecuteLine(m_aVoteCommand, m_VoteCreator);
SendChat(-1, TEAM_ALL, "Vote passed enforced by authorized player", -1, FLAG_SIX); SendChat(-1, TEAM_ALL, "Vote passed enforced by authorized player", -1, FLAG_SIX);
EndVote(); EndVote();
} }
@ -1712,6 +1717,9 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason)
delete m_apSavedTees[ClientId]; delete m_apSavedTees[ClientId];
m_apSavedTees[ClientId] = nullptr; m_apSavedTees[ClientId] = nullptr;
delete m_apSavedTeleTees[ClientId];
m_apSavedTeleTees[ClientId] = nullptr;
m_aTeamMapping[ClientId] = -1; m_aTeamMapping[ClientId] = -1;
m_VoteUpdate = true; m_VoteUpdate = true;
@ -3208,12 +3216,19 @@ void CGameContext::ConHotReload(IConsole::IResult *pResult, void *pUserData)
if(!pSelf->GetPlayerChar(i)) if(!pSelf->GetPlayerChar(i))
continue; continue;
CCharacter *pChar = pSelf->GetPlayerChar(i);
// Save the tee individually // Save the tee individually
pSelf->m_apSavedTees[i] = new CSaveTee(); pSelf->m_apSavedTees[i] = new CSaveTee();
pSelf->m_apSavedTees[i]->Save(pSelf->GetPlayerChar(i), false); pSelf->m_apSavedTees[i]->Save(pChar, false);
if(pSelf->m_apPlayers[i])
pSelf->m_apSavedTeleTees[i] = new CSaveTee(pSelf->m_apPlayers[i]->m_LastTeleTee);
// Save the team state // Save the team state
pSelf->m_aTeamMapping[i] = pSelf->GetDDRaceTeam(i); pSelf->m_aTeamMapping[i] = pSelf->GetDDRaceTeam(i);
if(pSelf->m_aTeamMapping[i] == TEAM_SUPER)
pSelf->m_aTeamMapping[i] = pChar->m_TeamBeforeSuper;
if(pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]]) if(pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]])
continue; continue;
@ -4820,7 +4835,6 @@ void CGameContext::ForceVote(int EnforcerId, bool Success)
return; return;
m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN; m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN;
m_VoteEnforcer = EnforcerId;
char aBuf[256]; char aBuf[256];
const char *pOption = Success ? "yes" : "no"; const char *pOption = Success ? "yes" : "no";

View file

@ -183,6 +183,7 @@ public:
bool m_aPlayerHasInput[MAX_CLIENTS]; bool m_aPlayerHasInput[MAX_CLIENTS];
CSaveTeam *m_apSavedTeams[MAX_CLIENTS]; CSaveTeam *m_apSavedTeams[MAX_CLIENTS];
CSaveTee *m_apSavedTees[MAX_CLIENTS]; CSaveTee *m_apSavedTees[MAX_CLIENTS];
CSaveTee *m_apSavedTeleTees[MAX_CLIENTS];
int m_aTeamMapping[MAX_CLIENTS]; int m_aTeamMapping[MAX_CLIENTS];
// returns last input if available otherwise nulled PlayerInput object // returns last input if available otherwise nulled PlayerInput object
@ -575,7 +576,6 @@ public:
VOTE_TYPE_SPECTATE, VOTE_TYPE_SPECTATE,
}; };
int m_VoteVictim; int m_VoteVictim;
int m_VoteEnforcer;
inline bool IsOptionVote() const { return m_VoteType == VOTE_TYPE_OPTION; } inline bool IsOptionVote() const { return m_VoteType == VOTE_TYPE_OPTION; }
inline bool IsKickVote() const { return m_VoteType == VOTE_TYPE_KICK; } inline bool IsKickVote() const { return m_VoteType == VOTE_TYPE_KICK; }

View file

@ -410,6 +410,29 @@ void CPlayer::Snap(int SnappingClient)
} }
} }
if(m_ClientId == SnappingClient && !Server()->IsSixup(SnappingClient))
{
/*
Snap spectator cursors for local players by default.
The information is not visible to local player unless they recorded demo, which the spectator cursor will act as local player's cursor.
*/
CPlayer *pCursorOwner = this;
if(m_Team == TEAM_SPECTATORS || IsPaused())
{
pCursorOwner = m_SpectatorId != SPEC_FREEVIEW ? GameServer()->m_apPlayers[m_SpectatorId] : NULL;
}
if(pCursorOwner)
{
CCharacter *pCursorOwnerChar = pCursorOwner->GetCharacter();
if(pCursorOwnerChar && pCursorOwnerChar->IsAlive())
{
pCursorOwnerChar->SnapSpecCursor(m_ClientId);
}
}
}
CNetObj_DDNetPlayer *pDDNetPlayer = Server()->SnapNewItem<CNetObj_DDNetPlayer>(id); CNetObj_DDNetPlayer *pDDNetPlayer = Server()->SnapNewItem<CNetObj_DDNetPlayer>(id);
if(!pDDNetPlayer) if(!pDDNetPlayer)
return; return;