Add spectator cursor

This commit is contained in:
TsFreddie 2024-08-28 15:06:13 +08:00
parent b30b493ab8
commit 7c45688fd2
8 changed files with 141 additions and 5 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

@ -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

@ -678,6 +678,8 @@ void CGameClient::UpdatePositions()
if(!m_MultiViewActivated && m_MultiView.m_IsInit)
ResetMultiView();
UpdateSpectatorCursor();
UpdateRenderedCharacters();
}
@ -1791,6 +1793,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);
}
}
}
@ -2945,6 +2952,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

@ -1170,6 +1170,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

@ -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;