mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-12 19:18:20 +00:00
Add spectator cursor
This commit is contained in:
parent
b30b493ab8
commit
7c45688fd2
|
@ -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 = [
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -100,6 +100,8 @@ public:
|
|||
void AddVelocity(vec2 Addition);
|
||||
void ApplyMoveRestrictions();
|
||||
|
||||
void SnapSpecCursor(int SnappingClient);
|
||||
|
||||
private:
|
||||
// player controlling this character
|
||||
class CPlayer *m_pPlayer;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue