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", [
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 = [

View file

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

View file

@ -360,7 +360,7 @@ public:
int UnpackAndValidateSnapshot(CSnapshot *pFrom, CSnapshot *pTo);
void ResetMapDownload();
void ResetMapDownload(bool ResetActive);
void FinishMapDownload();
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(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(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(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()
{
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;
}
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()->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()

View file

@ -718,6 +718,8 @@ void CGameClient::UpdatePositions()
if(!m_MultiViewActivated && m_MultiView.m_IsInit)
ResetMultiView();
UpdateSpectatorCursor();
UpdateRenderedCharacters();
}
@ -1831,6 +1833,11 @@ void CGameClient::OnNewSnapshot()
m_aSwitchStateTeam[g_Config.m_ClDummy] = -1;
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();
}
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()
{
for(int i = 0; i < MAX_CLIENTS; i++)

View file

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

View file

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

View file

@ -108,6 +108,9 @@ void CGameContext::Construct(int Resetting)
for(auto &pSavedTee : m_apSavedTees)
pSavedTee = nullptr;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
pSavedTeleTee = nullptr;
for(auto &pSavedTeam : m_apSavedTeams)
pSavedTeam = nullptr;
@ -131,6 +134,9 @@ void CGameContext::Destruct(int Resetting)
for(auto &pSavedTee : m_apSavedTees)
delete pSavedTee;
for(auto &pSavedTeleTee : m_apSavedTeleTees)
delete pSavedTeleTee;
for(auto &pSavedTeam : m_apSavedTeams)
delete pSavedTeam;
@ -773,7 +779,6 @@ void CGameContext::StartVote(const char *pDesc, const char *pCommand, const char
{
// reset votes
m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
m_VoteEnforcer = -1;
for(auto &pPlayer : m_apPlayers)
{
if(pPlayer)
@ -1204,7 +1209,7 @@ void CGameContext::OnTick()
}
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);
EndVote();
}
@ -1712,6 +1717,9 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason)
delete m_apSavedTees[ClientId];
m_apSavedTees[ClientId] = nullptr;
delete m_apSavedTeleTees[ClientId];
m_apSavedTeleTees[ClientId] = nullptr;
m_aTeamMapping[ClientId] = -1;
m_VoteUpdate = true;
@ -3208,12 +3216,19 @@ void CGameContext::ConHotReload(IConsole::IResult *pResult, void *pUserData)
if(!pSelf->GetPlayerChar(i))
continue;
CCharacter *pChar = pSelf->GetPlayerChar(i);
// Save the tee individually
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
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]])
continue;
@ -4820,7 +4835,6 @@ void CGameContext::ForceVote(int EnforcerId, bool Success)
return;
m_VoteEnforce = Success ? CGameContext::VOTE_ENFORCE_YES_ADMIN : CGameContext::VOTE_ENFORCE_NO_ADMIN;
m_VoteEnforcer = EnforcerId;
char aBuf[256];
const char *pOption = Success ? "yes" : "no";

View file

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