diff --git a/src/game/client/swarm/c_asw_player.cpp b/src/game/client/swarm/c_asw_player.cpp index 072a54471..ae87ff4d4 100644 --- a/src/game/client/swarm/c_asw_player.cpp +++ b/src/game/client/swarm/c_asw_player.cpp @@ -149,6 +149,9 @@ ConVar asw_turret_fog_end( "asw_turret_fog_end", "1200", 0, "Fog end distance fo ConVar rd_force_spectate_marine( "rd_force_spectate_marine", "-1", FCVAR_DONTRECORD, "spectate this marine resource index if it exists", true, -1, true, ASW_MAX_MARINE_RESOURCES ); +static void set_rd_spectate_order( IConVar *pConVar = NULL, const char *pOldValue = NULL, float flOldValue = 0.0f ); +ConVar rd_spectate_order( "rd_spectate_order", "", FCVAR_ARCHIVE, "Prioritize this marine profiles for spectate order. Space separated list. Example: \"4 1 7 3 0\".", set_rd_spectate_order ); + extern ConVar asw_allow_detach; extern ConVar asw_stim_cam_time; extern ConVar asw_rts_controls; @@ -1516,6 +1519,12 @@ void C_ASW_Player::OnDataChanged( DataUpdateType_t updateType ) } } + // send our rd_spectate_order to the server + if ( Q_strlen( rd_spectate_order.GetString() ) > 0 ) + { + set_rd_spectate_order(); + } + // tell other players that we're fully connected engine->ServerCmd( "cl_fullyjoined\n" ); } @@ -2999,3 +3008,29 @@ CSteamID C_ASW_Player::GetSteamID() CSteamID invalid; return invalid; } + +static void set_rd_spectate_order( IConVar *pConVar, const char *pOldValue, float flOldValue ) +{ + const char *pNewValue = pConVar ? ConVarRef( pConVar ).GetString() : rd_spectate_order.GetString(); + + int iProfiles[ASW_NUM_MARINE_PROFILES]; + const int nProfiles = parseSpectateOrder( pNewValue, iProfiles ); + + // Failed parsing + if ( nProfiles == -1 ) + { + if ( pConVar ) + { + ConVarRef( pConVar ).SetValue( pOldValue ); + } + return; + } + + if ( engine->IsInGame() ) + { + const int iBufLen = 30 + ASW_NUM_MARINE_PROFILES * 3; + char szbuf[iBufLen]; + Q_snprintf( szbuf, iBufLen, "rd_spectate_order_set \"%s\"\n", pNewValue ); + engine->ClientCmd( szbuf ); + } +} diff --git a/src/game/server/swarm/asw_player.cpp b/src/game/server/swarm/asw_player.cpp index 2f43f2825..e3d2f6019 100644 --- a/src/game/server/swarm/asw_player.cpp +++ b/src/game/server/swarm/asw_player.cpp @@ -2175,6 +2175,12 @@ HSCRIPT CASW_Player::ScriptGetNPC() const void CASW_Player::SpectateNextMarine() { + if ( m_bSpectatingInOrder ) + { + SpectateNextMarineInOrder(); + return; + } + CASW_Game_Resource* pGameResource = ASWGameResource(); if (!pGameResource) return; @@ -2220,6 +2226,153 @@ void CASW_Player::SpectateNextMarine() } } +void CASW_Player::SpectateNextMarineInOrder() +{ + CASW_Game_Resource* pGameResource = ASWGameResource(); + if (!pGameResource) + return; + //Msg("CASW_Player::SpectateNextMarineInOrder\n"); + + CASW_Marine *pFirst = NULL; // First with best priority or first and only seen + CASW_Marine *pBefore = CASW_Marine::AsMarine(GetSpectatingNPC()); // currently spectating + const int iBeforePrio = pBefore ? GetSpectatingPriority(pBefore) : 0; // when start (or when current died), go with first best + CASW_Marine *pNextBest = NULL; // Next in order after the one we just spectated + + // loop through all valid marines + for (int i=0;iGetMaxMarineResources();i++) + { + //Msg("Checking pMR %d\n", i); + CASW_Marine_Resource* pMR = pGameResource->GetMarineResource(i); + CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL; + const int iMarinePrio = GetSpectatingPriority(pMarine); + + if (!pMarine || !pMarine->IsAlive() || pMarine->GetHealth() <= 0) + { + //Msg(" but he's dead\n"); + continue; + } + + // there can be multiple unprioritized marines, or multiple players can have same marine character + if (GetSpectatingNPC() == NULL && iMarinePrio == iBeforePrio) + { + //Msg(" We're not spectating anyone and this dude has same priority as current spectator, so we're gonna spec this dude\n"); + SetSpectatingNPC(pMarine); + break; + } + + // try to find marine with best priority for fallback + if (!pFirst || iMarinePrio < GetSpectatingPriority(pFirst)) + { + //Msg(" set this (better) guy as our first\n"); + pFirst = pMarine; + } + + if (GetSpectatingNPC() == pMarine) // if we're spectating this one, then clear it, so the next one we find will get set + { + //Msg(" we're spectating this dude, so clearing our current spectator\n"); + SetSpectatingNPC(NULL); + continue; + } + + // Next perfect (iBeforePrio+1) can be dead, so check everyone after iBeforePrio + if (iMarinePrio > iBeforePrio && (!pNextBest || iMarinePrio < GetSpectatingPriority(pNextBest))) + { + //Msg(" remember this guy as next best choice in order\n"); + pNextBest = pMarine; + } + } + //Msg("end\n"); + // haven't found next marine with same priority, so try to set next best + if (GetSpectatingNPC() == NULL && pNextBest) + { + //Msg(" and spectate next best marine\n"); + SetSpectatingNPC(pNextBest); + } + // if we're still not spectating anything but we found at least marine, then that means we were spectating the last one in the list and need to set this + if (GetSpectatingNPC() == NULL && pFirst) + { + //Msg(" but we're still not speccing anyone and we have a first set, so speccing that dude\n"); + SetSpectatingNPC(pFirst); + } +} + +// Can have some marine profiles; rest will have worst priority +void CASW_Player::SetSpectatingOrder( const int* iProfiles, int nProfiles ) +{ + if ( nProfiles < 0 ) + { + Warning( "set spectating order: bad parsing?\n" ); + return; + } + + if ( nProfiles == 0 ) + { + UnsetSpectatingOrder(); + return; + } + + nProfiles = MIN( nProfiles, ASW_NUM_MARINE_PROFILES ); + m_iWorstPriority = nProfiles; + + // Prefill with worst priority in case a profile is not in the list or is invalid + for ( int i = 0; i < ASW_NUM_MARINE_PROFILES; i++ ) + { + m_iProfileToSpectatingPriority[i] = m_iWorstPriority; + } + + for ( int i = 0; i < nProfiles; i++ ) + { + const int iProfile = iProfiles[i]; + if ( iProfile >= 0 && iProfile < ASW_NUM_MARINE_PROFILES ) + { + m_iProfileToSpectatingPriority[iProfile] = i; + } + } + + m_bSpectatingInOrder = true; +} + +int CASW_Player::GetSpectatingPriority( CASW_Marine* pMarine ) const +{ + const int iProfile = pMarine ? pMarine->GetMarineProfile()->m_ProfileIndex : -1; + return ( iProfile >= 0 && iProfile < ASW_NUM_MARINE_PROFILES ) + ? m_iProfileToSpectatingPriority[iProfile] : m_iWorstPriority; +} + +CON_COMMAND_F( rd_spectate_order_set, "Prioritize this marine profiles for spectate order. Example: \"4 1 7 3 0\". \"\" for unset.", FCVAR_HIDDEN ) +{ + CASW_Player *pPlayer = ToASW_Player( UTIL_GetCommandClient() ); + if ( !pPlayer ) + { + Warning( "%s: Not a Player (not connected to a server?)\n", args[0] ); + return; + } + + if ( args.ArgC() != 2 ) + { + Warning( "%s: Expected quoted space separated list. Example: \"4 1 7 3 0\". \"\" for unset.\n", args[0] ); + return; + } + + int iProfiles[ASW_NUM_MARINE_PROFILES]; + const int nProfiles = parseSpectateOrder( args[1], iProfiles ); + + // Failed parsing + if ( nProfiles == -1 ) + { + return; + } + + pPlayer->SetSpectatingOrder( iProfiles, nProfiles ); + + // Start spectating "best" marine if we are spectating + if ( ASWGameRules()->GetGameState() == ASW_GS_INGAME && pPlayer->GetSpectatingNPC() != NULL ) + { + pPlayer->SetSpectatingNPC( NULL ); + pPlayer->SpectateNextMarine(); + } +} + void CASW_Player::SetSpectatingNPC( CASW_Inhabitable_NPC *pSpectating ) { unsigned int nOwnIndex = 1u << ( GetClientIndex() + 1 ); diff --git a/src/game/server/swarm/asw_player.h b/src/game/server/swarm/asw_player.h index fc8aa428e..7ecfa9bf5 100644 --- a/src/game/server/swarm/asw_player.h +++ b/src/game/server/swarm/asw_player.h @@ -87,7 +87,16 @@ class CASW_Player : public CBaseMultiplayerPlayer, public IASWPlayerAnimStateHel virtual bool ASWAnim_CanMove(); // spectating +private: + bool m_bSpectatingInOrder = false; + int m_iWorstPriority = ASW_NUM_MARINE_PROFILES; // "unreachable" priority + int m_iProfileToSpectatingPriority[ASW_NUM_MARINE_PROFILES]; // smaller number is higher priority +public: void SpectateNextMarine(); + void SpectateNextMarineInOrder(); + void SetSpectatingOrder( const int* iProfiles, int nProfiles ); + void UnsetSpectatingOrder() { m_bSpectatingInOrder = false; } + int GetSpectatingPriority( CASW_Marine* pMarine ) const; // returns worst priority if no marine or no profile index void SetSpectatingNPC( CASW_Inhabitable_NPC *pSpectating ); CASW_Inhabitable_NPC *GetSpectatingNPC() const; HSCRIPT ScriptGetSpectatingNPC() const; diff --git a/src/game/shared/swarm/asw_player_shared.cpp b/src/game/shared/swarm/asw_player_shared.cpp index 5fa82027d..fa98ed77d 100644 --- a/src/game/shared/swarm/asw_player_shared.cpp +++ b/src/game/shared/swarm/asw_player_shared.cpp @@ -1452,3 +1452,52 @@ DLL_EXPORT ASW_Controls_t GetASWControlsForPlayer( CASW_Player *pPlayer ) return pPlayer->GetASWControls(); } + +int parseSpectateOrder( const char *szInput, int *iProfiles, const int nProfilesMax ) +{ + Assert( nProfilesMax > 0 && nProfilesMax <= ASW_NUM_MARINE_PROFILES ); + + if ( Q_strlen( szInput ) == 0 ) + { + return 0; + } + + CSplitString szProfiles( szInput, " " ); + const int nProfiles = szProfiles.Count(); + + if ( nProfiles > nProfilesMax ) + { + Warning( "parse specate order: Too many profiles (%d > %d)\n", nProfiles, nProfilesMax ); + return -1; + } + + for ( int i = 0; i < nProfiles; i++ ) + { + const int iProfile = atoi( szProfiles[i] ); + + if ( iProfile == 0 && szProfiles[i][0] != '0' ) + { + Warning( "parse specate order: Not a number (#%d: %s)\n", i + 1, szProfiles[i] ); + return -1; + } + + if ( iProfile < 0 || iProfile >= nProfilesMax ) + { + Warning( "parse specate order: Incorrect profile number (#%d: %s)\n", i + 1, szProfiles[i] ); + return -1; + } + + for ( int j = 0; j < i; j++ ) + { + if ( iProfiles[j] == iProfile ) + { + Warning( "parse specate order: Duplicate profile number (#%d: %d)\n", i + 1, iProfile ); + return -1; + } + } + + iProfiles[i] = iProfile; + } + + return nProfiles; +} diff --git a/src/game/shared/swarm/asw_player_shared.h b/src/game/shared/swarm/asw_player_shared.h index be7c8b740..2a3f32a89 100644 --- a/src/game/shared/swarm/asw_player_shared.h +++ b/src/game/shared/swarm/asw_player_shared.h @@ -12,6 +12,8 @@ #pragma once #endif +#include "asw_shareddefs.h" + // Shared header file for players #if defined( CLIENT_DLL ) #define CASW_Player C_ASW_Player @@ -57,4 +59,9 @@ enum CASW_Earned_XP_t ASW_NUM_XP_TYPES }; +// Parse space separated profile numbers into iProfiles +// @returns Number of parsed profiles +// @returns -1 if parsing failed +int parseSpectateOrder( const char *szInput, int *iProfiles, const int nProfilesMax = ASW_NUM_MARINE_PROFILES ); + #endif // ASW_PLAYER_SHARED_H