diff --git a/src/game/client/neo/game_controls/neo_loadoutmenu.cpp b/src/game/client/neo/game_controls/neo_loadoutmenu.cpp index 47edb4149..d005506a7 100644 --- a/src/game/client/neo/game_controls/neo_loadoutmenu.cpp +++ b/src/game/client/neo/game_controls/neo_loadoutmenu.cpp @@ -190,7 +190,6 @@ void CNeoLoadoutMenu::OnMousePressed(vgui::MouseCode code) extern ConCommand loadoutmenu; -extern ConVar sv_neo_ignore_wep_xp_limit; extern ConVar sv_neo_dev_loadout; void CNeoLoadoutMenu::OnClose() @@ -227,7 +226,7 @@ void CNeoLoadoutMenu::OnCommand(const char* command) auto localPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!localPlayer) { return; } - int currentXP = localPlayer->m_iXP.Get(); + int currentXP = CNEOWeaponLoadout::GetEffectiveXP(localPlayer->m_iXP.Get()); int currentClass = localPlayer->m_iNextSpawnClassChoice.Get() != NEO_CLASS_RANDOM ? localPlayer->m_iNextSpawnClassChoice.Get() : localPlayer->m_iNeoClass.Get(); int numWeapons = CNEOWeaponLoadout::GetNumberOfLoadoutWeapons(currentXP, sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : currentClass); @@ -295,7 +294,7 @@ void CNeoLoadoutMenu::ApplySchemeSettings(vgui::IScheme *pScheme) auto localPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!localPlayer) { return; } - const int currentXP = localPlayer->m_iXP.Get(); + const int currentXP = CNEOWeaponLoadout::GetEffectiveXP(localPlayer->m_iXP.Get()); const int currentClass = localPlayer->m_iNextSpawnClassChoice.Get() != NEO_CLASS_RANDOM ? localPlayer->m_iNextSpawnClassChoice.Get() : localPlayer->m_iNeoClass.Get(); int iLoadout = sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : currentClass; diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 175eb07c2..c020f523c 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -643,7 +643,8 @@ void CNEOBot::Spawn() int CNEOBot::ChooseRandomWeaponIndex() const { - const ENeoRank eRank = static_cast(GetRank(m_iXP) - 1); + const int iEffectiveXP = CNEOWeaponLoadout::GetEffectiveXP(m_iXP); + const ENeoRank eRank = static_cast(GetRank(iEffectiveXP) - 1); if (eRank == NEO_RANK_RANKLESS_DOG || (false == IN_BETWEEN_EQ(NEO_CLASS_RECON, m_iNeoClass, NEO_CLASS_VIP))) { return 0; @@ -665,7 +666,7 @@ int CNEOBot::ChooseRandomWeaponIndex() const // Generally shouldn't happen, but if so, just pick from any under the XP limit for (int i = 0; i < MAX_WEAPON_LOADOUTS; ++i) { - if (CNEOWeaponLoadout::s_LoadoutWeapons[m_iNeoClass][i].m_iWeaponPrice > m_iXP) + if (CNEOWeaponLoadout::s_LoadoutWeapons[m_iNeoClass][i].m_iWeaponPrice > iEffectiveXP) { break; } diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index bfe41aaec..e85940a6c 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -142,8 +142,8 @@ CNEOGameRulesProxy* neoGameRules; extern CBaseEntity *g_pLastSpawn; extern ConVar sv_neo_bot_cmdr_enable; -extern ConVar sv_neo_ignore_wep_xp_limit; extern ConVar sv_neo_clantag_allow; +extern ConVar sv_neo_detpack_xp_limit; extern ConVar sv_neo_dev_test_clantag; extern ConVar sv_stickysprint; extern ConVar sv_neo_dev_loadout; @@ -389,9 +389,8 @@ bool CNEO_Player::RequestSetLoadout(int loadoutNumber) result = false; } - if (!sv_neo_ignore_wep_xp_limit.GetBool() && - loadoutNumber+1 > CNEOWeaponLoadout::GetNumberOfLoadoutWeapons(m_iXP, - sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : classChosen)) + if (loadoutNumber+1 > CNEOWeaponLoadout::GetNumberOfLoadoutWeapons(CNEOWeaponLoadout::GetEffectiveXP(m_iXP), + sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : classChosen)) { DevMsg("Insufficient XP for %s\n", pszWepName); result = RequestSetLoadout(0); @@ -3475,6 +3474,15 @@ CBaseEntity* CNEO_Player::GiveNamedItem(const char* szName, int iSubType) void GiveDet(CNEO_Player* pPlayer) { + // Cost of -1 XP means no XP cost. Checked before creating the weapon + // entity so ineligible players don't pay a create/destroy cycle. + const int detXpCost = sv_neo_detpack_xp_limit.GetInt(); + const bool canHaveDet = (detXpCost < 0 || CNEOWeaponLoadout::GetEffectiveXP(pPlayer->m_iXP) >= detXpCost); + if (!canHaveDet) + { + return; + } + constexpr const char* detpackClassname = "weapon_remotedet"; if (!pPlayer->Weapon_OwnsThisType(detpackClassname)) { @@ -3492,23 +3500,12 @@ void GiveDet(CNEO_Player* pPlayer) auto pWeapon = assert_cast((CBaseEntity*)pent); if (pWeapon) { - const int detXpCost = pWeapon->GetNeoWepXPCost(pPlayer->GetClass()); - // Cost of -1 XP means no XP cost. - const bool canHaveDet = (detXpCost < 0 || pPlayer->m_iXP >= detXpCost); - pWeapon->SetSubType(0); - if (canHaveDet) - { - DispatchSpawn(pent); + DispatchSpawn(pent); - if (pent != NULL && !(pent->IsMarkedForDeletion())) - { - pent->Touch(pPlayer); - } - } - else + if (pent != NULL && !(pent->IsMarkedForDeletion())) { - UTIL_Remove(pWeapon); + pent->Touch(pPlayer); } } } @@ -3522,7 +3519,7 @@ void CNEO_Player::GiveDefaultItems(void) case NEO_CLASS_RECON: GiveNamedItem("weapon_knife"); GiveNamedItem("weapon_milso"); - if (this->m_iXP >= 4) { GiveDet(this); } + GiveDet(this); Weapon_Switch(Weapon_OwnsThisType("weapon_milso")); break; case NEO_CLASS_ASSAULT: @@ -3596,9 +3593,8 @@ void CNEO_Player::GiveLoadoutWeapon(void) CNEOBaseCombatWeapon *pNeoWeapon = assert_cast((CBaseEntity*)pEnt); if (pNeoWeapon) { - if (sv_neo_ignore_wep_xp_limit.GetBool() || - m_iLoadoutWepChoice+1 <= CNEOWeaponLoadout::GetNumberOfLoadoutWeapons(m_iXP, - sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : m_iNeoClass.Get())) + if (m_iLoadoutWepChoice+1 <= CNEOWeaponLoadout::GetNumberOfLoadoutWeapons(CNEOWeaponLoadout::GetEffectiveXP(m_iXP), + sv_neo_dev_loadout.GetBool() ? NEO_LOADOUT_DEV : m_iNeoClass.Get())) { pNeoWeapon->SetSubType(wepSubType); diff --git a/src/game/shared/neo/neo_weapon_loadout.cpp b/src/game/shared/neo/neo_weapon_loadout.cpp index 64870b6c3..65ae9c5e9 100644 --- a/src/game/shared/neo/neo_weapon_loadout.cpp +++ b/src/game/shared/neo/neo_weapon_loadout.cpp @@ -2,8 +2,11 @@ #include "tier0/dbg.h" #include "tier0/commonmacros.h" +#include "convar.h" #include "neo_misc.h" +extern ConVar sv_neo_ignore_wep_xp_limit; + namespace CNEOWeaponLoadout { #define WEAPON_DEF(M_BITWNAME, M_NAME, M_DISPLAY_NAME, ...) \ @@ -105,5 +108,19 @@ int GetNumberOfLoadoutWeapons(const int rank, const int classType) return amount; } +ConVar sv_neo_wep_xp_override("sv_neo_wep_xp_override", "-1", FCVAR_CHEAT | FCVAR_REPLICATED, + "Override the effective XP used for weapon eligibility checks, or -1 to disable.", true, -1.0f, false, 0.0f); + +int GetEffectiveXP(const int actualXP) +{ + if (sv_neo_ignore_wep_xp_limit.GetBool()) + { + // Treat as max rank so every XP-gated weapon is eligible + return XP_LIEUTENANT; + } + const int iOverride = sv_neo_wep_xp_override.GetInt(); + return (iOverride >= 0) ? iOverride : actualXP; +} + } // namespace CNEOWeaponLoadout diff --git a/src/game/shared/neo/neo_weapon_loadout.h b/src/game/shared/neo/neo_weapon_loadout.h index 26f4cbc99..4f7d3ef25 100644 --- a/src/game/shared/neo/neo_weapon_loadout.h +++ b/src/game/shared/neo/neo_weapon_loadout.h @@ -44,5 +44,9 @@ namespace CNEOWeaponLoadout extern const CLoadoutWeapon s_LoadoutWeapons[NEO_LOADOUT__COUNT][MAX_WEAPON_LOADOUTS]; int GetNumberOfLoadoutWeapons(const int rank, const int classType); + + // The XP value to use for weapon eligibility checks. Returns actualXP unless + // sv_neo_wep_xp_override is set (>= 0), in which case the override wins. + int GetEffectiveXP(const int actualXP); } // namespace CNEOWeaponLoadout