From 9b35be51e2effb3ac4c4281f3487546ebc844fc1 Mon Sep 17 00:00:00 2001 From: Alan Shen Date: Tue, 30 Jun 2026 23:20:38 -0600 Subject: [PATCH 1/2] Configurable class limits --- .../neo/game_controls/neo_classmenu.cpp | 67 +++++++- .../client/neo/game_controls/neo_classmenu.h | 1 + src/game/server/neo/bot/neo_bot.cpp | 19 +++ src/game/server/neo/neo_player.cpp | 33 +++- src/game/shared/neo/neo_gamerules.cpp | 152 ++++++++++++++++++ src/game/shared/neo/neo_gamerules.h | 22 +++ 6 files changed, 286 insertions(+), 8 deletions(-) diff --git a/src/game/client/neo/game_controls/neo_classmenu.cpp b/src/game/client/neo/game_controls/neo_classmenu.cpp index 58092ea7f2..8f40968180 100644 --- a/src/game/client/neo/game_controls/neo_classmenu.cpp +++ b/src/game/client/neo/game_controls/neo_classmenu.cpp @@ -248,18 +248,30 @@ void CNeoClassMenu::OnKeyCodeReleased(vgui::KeyCode code) return; } + C_NEO_Player* player = C_NEO_Player::GetLocalNEOPlayer(); + int teamNumber = player ? player->GetTeamNumber() : TEAM_UNASSIGNED; + switch (code) { case KEY_1: - UpdateSkinImages(0); - engine->ClientCmd("setclass 1"); + if (!NEORules() || !NEORules()->IsClassFull(teamNumber, NEO_CLASS_RECON)) + { + UpdateSkinImages(0); + engine->ClientCmd("setclass 1"); + } break; case KEY_2: - UpdateSkinImages(1); - engine->ClientCmd("setclass 2"); + if (!NEORules() || !NEORules()->IsClassFull(teamNumber, NEO_CLASS_ASSAULT)) + { + UpdateSkinImages(1); + engine->ClientCmd("setclass 2"); + } break; case KEY_3: - UpdateSkinImages(2); - engine->ClientCmd("setclass 3"); + if (!NEORules() || !NEORules()->IsClassFull(teamNumber, NEO_CLASS_SUPPORT)) + { + UpdateSkinImages(2); + engine->ClientCmd("setclass 3"); + } break; case KEY_SPACE: // Continue with currently selected class and skin ChangeMenu("loadoutmenu"); @@ -405,4 +417,47 @@ void CNeoClassMenu::ShowPanel( bool bShow ) void CNeoClassMenu::OnThink() { BaseClass::OnThink(); + UpdateClassButtons(); +} + +void CNeoClassMenu::UpdateClassButtons() +{ + C_NEO_Player* player = C_NEO_Player::GetLocalNEOPlayer(); + if (!player || !NEORules()) + { + return; + } + + int teamNumber = player->GetTeamNumber(); + if (teamNumber != TEAM_JINRAI && teamNumber != TEAM_NSF) + { + // Not on a playing team, enable all buttons + if (m_pRecon_Button) + { + m_pRecon_Button->SetEnabled(true); + } + if (m_pAssault_Button) + { + m_pAssault_Button->SetEnabled(true); + } + if (m_pSupport_Button) + { + m_pSupport_Button->SetEnabled(true); + } + return; + } + + // Check class limits and disable buttons accordingly + if (m_pRecon_Button) + { + m_pRecon_Button->SetEnabled(!NEORules()->IsClassFull(teamNumber, NEO_CLASS_RECON)); + } + if (m_pAssault_Button) + { + m_pAssault_Button->SetEnabled(!NEORules()->IsClassFull(teamNumber, NEO_CLASS_ASSAULT)); + } + if (m_pSupport_Button) + { + m_pSupport_Button->SetEnabled(!NEORules()->IsClassFull(teamNumber, NEO_CLASS_SUPPORT)); + } } diff --git a/src/game/client/neo/game_controls/neo_classmenu.h b/src/game/client/neo/game_controls/neo_classmenu.h index 598b9bd965..0a3c82d452 100644 --- a/src/game/client/neo/game_controls/neo_classmenu.h +++ b/src/game/client/neo/game_controls/neo_classmenu.h @@ -81,6 +81,7 @@ class CNeoClassMenu : public vgui::Frame, void FindButtons(); void UpdateSkinImages(int classNumber = -1, int overrideTeamNumber = -1); void UpdateTimer() { } + void UpdateClassButtons(); // vgui overrides virtual void PerformLayout() { } diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 91d06de787..5e92d9a92a 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -2677,12 +2677,31 @@ NeoClass CNEOBot::ChooseRandomClass() const for (int i = 0; i <= NEO_CLASS_SUPPORT; ++i) { bValidClasses[i] = (m_profile.flagClass & (1 << i)); + // Check class limits + if (bValidClasses[i] && NEORules()->IsClassFull(GetTeamNumber(), i)) + { + bValidClasses[i] = false; + } if (bValidClasses[i]) { ++iClassCounts; } } + if (iClassCounts == 0) + { + // If all profile classes are full/banned, allow any class that isn't full + for (int i = 0; i <= NEO_CLASS_SUPPORT; ++i) + { + if (!NEORules()->IsClassFull(GetTeamNumber(), i)) + { + bValidClasses[i] = true; + ++iClassCounts; + } + } + } + + // NEO JANK: If still no valid classes (all full), allow any class as fallback if (iClassCounts == 0) { for (int i = 0; i <= NEO_CLASS_SUPPORT; ++i) diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 5a3536e136..3c1d580481 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -251,6 +251,17 @@ void CNEO_Player::RequestSetClass(int newClass) return; } + // Enforce class limits for Recon/Assault/Support + if (newClass >= NEO_CLASS_RECON && newClass <= NEO_CLASS_SUPPORT) + { + if (NEORules()->IsClassFull(GetTeamNumber(), newClass)) + { + newClass = NEORules()->GetFallbackClass(GetTeamNumber(), newClass); + // If fallback returns the same class (all full), we'll proceed anyway + // This allows the player to spawn even if all classes are full + } + } + const bool bIsTypeDM = (NEORules()->GetGameType() == NEO_GAME_TYPE_TDM || NEORules()->GetGameType() == NEO_GAME_TYPE_DM); const NeoRoundStatus status = NEORules()->GetRoundStatus(); if (IsDead() || sv_neo_can_change_classes_anytime.GetBool() || @@ -438,8 +449,15 @@ void SetClass(const CCommand &command) { return; } - + nextClass = clamp(nextClass, NEO_CLASS_RECON, NEO_CLASS_SUPPORT); + + // Enforce class limits + if (NEORules()->IsClassFull(player->GetTeamNumber(), nextClass)) + { + nextClass = NEORules()->GetFallbackClass(player->GetTeamNumber(), nextClass); + } + player->RequestSetClass(nextClass); } } @@ -692,7 +710,18 @@ void CNEO_Player::Spawn(void) // Should do this class update first, because most of the stuff below depends on which player class we are. if ((m_iNextSpawnClassChoice != NEO_CLASS_RANDOM) && (m_iNeoClass != m_iNextSpawnClassChoice)) { - m_iNeoClass = m_iNextSpawnClassChoice; + int desiredClass = m_iNextSpawnClassChoice; + + // Enforce class limits for Recon/Assault/Support + if (desiredClass >= NEO_CLASS_RECON && desiredClass <= NEO_CLASS_SUPPORT) + { + if (NEORules()->IsClassFull(GetTeamNumber(), desiredClass)) + { + desiredClass = NEORules()->GetFallbackClass(GetTeamNumber(), desiredClass); + } + } + + m_iNeoClass = desiredClass; } BaseClass::Spawn(); diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index eeff852c6f..2631c3c900 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -53,6 +53,10 @@ ConVar sv_neo_player_restore("sv_neo_player_restore", "1", FCVAR_REPLICATED, "If ConVar sv_neo_spraydisable("sv_neo_spraydisable", "0", FCVAR_REPLICATED, "If enabled, disables the players ability to spray.", true, 0.0f, true, 1.0f); +ConVar sv_neo_class_limit_recon("sv_neo_class_limit_recon", "-1", FCVAR_REPLICATED, "Maximum number of Recon class players per team (-1 = no limit, 0 = banned).", true, -1, true, 32); +ConVar sv_neo_class_limit_assault("sv_neo_class_limit_assault", "-1", FCVAR_REPLICATED, "Maximum number of Assault class players per team (-1 = no limit, 0 = banned).", true, -1, true, 32); +ConVar sv_neo_class_limit_support("sv_neo_class_limit_support", "-1", FCVAR_REPLICATED, "Maximum number of Support class players per team (-1 = no limit, 0 = banned).", true, -1, true, 32); + #ifdef CLIENT_DLL ConVar neo_name("neo_name", "", FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_PRINTABLEONLY, "The nickname to set instead of the steam profile name."); ConVar cl_onlysteamnick("cl_onlysteamnick", "0", FCVAR_USERINFO | FCVAR_ARCHIVE, "Only show players Steam names, otherwise show player set names.", true, 0.0f, true, 1.0f); @@ -292,6 +296,15 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) RecvPropInt(RECVINFO(m_iLastAttacker)), RecvPropInt(RECVINFO(m_iLastKiller)), RecvPropInt(RECVINFO(m_iLastGhoster)), + RecvPropInt(RECVINFO(m_iClassLimitRecon)), + RecvPropInt(RECVINFO(m_iClassLimitAssault)), + RecvPropInt(RECVINFO(m_iClassLimitSupport)), + RecvPropInt(RECVINFO(m_iJinraiReconCount)), + RecvPropInt(RECVINFO(m_iJinraiAssaultCount)), + RecvPropInt(RECVINFO(m_iJinraiSupportCount)), + RecvPropInt(RECVINFO(m_iNsfReconCount)), + RecvPropInt(RECVINFO(m_iNsfAssaultCount)), + RecvPropInt(RECVINFO(m_iNsfSupportCount)), #else SendPropTime(SENDINFO(m_flNeoNextRoundStartTime)), SendPropTime(SENDINFO(m_flNeoRoundStartTime)), @@ -326,6 +339,15 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) SendPropInt(SENDINFO(m_iLastAttacker), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), SendPropInt(SENDINFO(m_iLastKiller), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), SendPropInt(SENDINFO(m_iLastGhoster), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iClassLimitRecon)), + SendPropInt(SENDINFO(m_iClassLimitAssault)), + SendPropInt(SENDINFO(m_iClassLimitSupport)), + SendPropInt(SENDINFO(m_iJinraiReconCount)), + SendPropInt(SENDINFO(m_iJinraiAssaultCount)), + SendPropInt(SENDINFO(m_iJinraiSupportCount)), + SendPropInt(SENDINFO(m_iNsfReconCount)), + SendPropInt(SENDINFO(m_iNsfAssaultCount)), + SendPropInt(SENDINFO(m_iNsfSupportCount)), #endif END_NETWORK_TABLE() @@ -652,6 +674,17 @@ CNEORules::CNEORules() m_iForcedWeapon = -1; m_bCyberspaceLevel = false; + m_iClassLimitRecon = -1; + m_iClassLimitAssault = -1; + m_iClassLimitSupport = -1; + + m_iJinraiReconCount = 0; + m_iJinraiAssaultCount = 0; + m_iJinraiSupportCount = 0; + m_iNsfReconCount = 0; + m_iNsfAssaultCount = 0; + m_iNsfSupportCount = 0; + ResetMapSessionCommon(); ListenForGameEvent("round_start"); ListenForGameEvent("game_end"); @@ -673,7 +706,124 @@ void CNEORules::Precache() { BaseClass::Precache(); } + +int CNEORules::GetClassCount(int team, int classId) const +{ + if (team != TEAM_JINRAI && team != TEAM_NSF) + { + return 0; + } + + if (classId < NEO_CLASS_RECON || classId > NEO_CLASS_SUPPORT) + { + return 0; + } + + int count = 0; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CNEO_Player *player = ToNEOPlayer(UTIL_PlayerByIndex(i)); + if (player && player->GetTeamNumber() == team && player->GetClass() == classId) + { + count++; + } + } + return count; +} + +void CNEORules::UpdateClassLimitsFromConVars() +{ + m_iClassLimitRecon = sv_neo_class_limit_recon.GetInt(); + m_iClassLimitAssault = sv_neo_class_limit_assault.GetInt(); + m_iClassLimitSupport = sv_neo_class_limit_support.GetInt(); +} + +void CNEORules::UpdateClassCounts() +{ + m_iJinraiReconCount = GetClassCount(TEAM_JINRAI, NEO_CLASS_RECON); + m_iJinraiAssaultCount = GetClassCount(TEAM_JINRAI, NEO_CLASS_ASSAULT); + m_iJinraiSupportCount = GetClassCount(TEAM_JINRAI, NEO_CLASS_SUPPORT); + m_iNsfReconCount = GetClassCount(TEAM_NSF, NEO_CLASS_RECON); + m_iNsfAssaultCount = GetClassCount(TEAM_NSF, NEO_CLASS_ASSAULT); + m_iNsfSupportCount = GetClassCount(TEAM_NSF, NEO_CLASS_SUPPORT); +} +#endif + +bool CNEORules::IsClassFull(int team, int classId) const +{ + if (classId < NEO_CLASS_RECON || classId > NEO_CLASS_SUPPORT) + { + return false; + } + + int limit = -1; + switch (classId) + { + case NEO_CLASS_RECON: + limit = m_iClassLimitRecon; + break; + case NEO_CLASS_ASSAULT: + limit = m_iClassLimitAssault; + break; + case NEO_CLASS_SUPPORT: + limit = m_iClassLimitSupport; + break; + } + + if (limit < 0) + return false; + + if (limit == 0) + return true; + + // Use networked class counts for client, server counts for server + int count = 0; +#ifdef GAME_DLL + count = GetClassCount(team, classId); +#else + switch (team) + { + case TEAM_JINRAI: + switch (classId) + { + case NEO_CLASS_RECON: count = m_iJinraiReconCount; break; + case NEO_CLASS_ASSAULT: count = m_iJinraiAssaultCount; break; + case NEO_CLASS_SUPPORT: count = m_iJinraiSupportCount; break; + } + break; + case TEAM_NSF: + switch (classId) + { + case NEO_CLASS_RECON: count = m_iNsfReconCount; break; + case NEO_CLASS_ASSAULT: count = m_iNsfAssaultCount; break; + case NEO_CLASS_SUPPORT: count = m_iNsfSupportCount; break; + } + break; + } #endif + return count >= limit; +} + +int CNEORules::GetFallbackClass(int team, int preferredClass) const +{ + // Only consider Recon/Assault/Support for fallback + for (int c = NEO_CLASS_RECON; c <= NEO_CLASS_SUPPORT; c++) + { + if (c == preferredClass) + { + continue; // Skip the class that was full/banned + } + + if (!IsClassFull(team, c)) + { + return c; + } + } + + // All classes are full/banned, return the preferred class anyway + // The caller will need to handle this case + return preferredClass; +} ConVar sk_max_neo_ammo("sk_max_neo_ammo", "10000", FCVAR_REPLICATED); ConVar sk_plr_dmg_neo("sk_plr_dmg_neo", "0", FCVAR_REPLICATED); @@ -1074,6 +1224,8 @@ void CNEORules::Think(void) { #ifdef GAME_DLL CheckGameConfig(); + UpdateClassLimitsFromConVars(); + UpdateClassCounts(); if (CheckShouldNotThink()) { // This is kind of wonky, but we only need it for the tutorial, in order to play the dummy beacon sounds... diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index 2ed698a414..c05794e1ef 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -413,6 +413,15 @@ class CNEORules : public CHL2MPRules, public CGameEventListener void OnNavMeshLoad() override; #endif // GAME_DL: + // Class limit methods + int GetClassCount(int team, int classId) const; + bool IsClassFull(int team, int classId) const; + int GetFallbackClass(int team, int preferredClass) const; + void UpdateClassLimitsFromConVars(); +#ifdef GAME_DLL + void UpdateClassCounts(); +#endif + public: #ifdef GAME_DLL // Workaround for bot spawning. See Bot_f() for details. @@ -541,6 +550,19 @@ class CNEORules : public CHL2MPRules, public CGameEventListener CNetworkVar(int, m_iLastKiller); CNetworkVar(int, m_iLastGhoster); + // Class limit networked variables + CNetworkVar(int, m_iClassLimitRecon); + CNetworkVar(int, m_iClassLimitAssault); + CNetworkVar(int, m_iClassLimitSupport); + + // Class count networked variables (for client UI) + CNetworkVar(int, m_iJinraiReconCount); + CNetworkVar(int, m_iJinraiAssaultCount); + CNetworkVar(int, m_iJinraiSupportCount); + CNetworkVar(int, m_iNsfReconCount); + CNetworkVar(int, m_iNsfAssaultCount); + CNetworkVar(int, m_iNsfSupportCount); + public: // VIP networked variables CNetworkVar(int, m_iEscortingTeam); From 8cbcfb24456864dfa32aa629d8a641df85fc2278 Mon Sep 17 00:00:00 2001 From: Adam <44210793+AdamTadeusz@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:08:47 +0100 Subject: [PATCH 2/2] UI changes move logic to c team (#21) * Configurable class limits * update class counts as players join and leave team * use networked variables where available * back to 3 class limits * neobotmanager maintains class limits --- game/neo/resource/neo_ui/Neo_ClassMenu.res | 88 +++++++++-- src/game/client/c_team.cpp | 60 +++++++- src/game/client/c_team.h | 11 ++ .../neo/game_controls/neo_classmenu.cpp | 140 +++++++++++++----- .../client/neo/game_controls/neo_classmenu.h | 8 +- src/game/server/neo/bot/neo_bot.cpp | 51 +++++-- src/game/server/neo/bot/neo_bot_manager.cpp | 71 ++++++--- src/game/server/neo/neo_player.cpp | 60 +++++--- src/game/server/neo/neo_player.h | 1 + src/game/server/team.cpp | 128 +++++++++++++++- src/game/server/team.h | 16 ++ src/game/shared/neo/neo_gamerules.cpp | 121 +-------------- src/game/shared/neo/neo_gamerules.h | 4 - 13 files changed, 521 insertions(+), 238 deletions(-) diff --git a/game/neo/resource/neo_ui/Neo_ClassMenu.res b/game/neo/resource/neo_ui/Neo_ClassMenu.res index a3515b0735..ebeff3d905 100644 --- a/game/neo/resource/neo_ui/Neo_ClassMenu.res +++ b/game/neo/resource/neo_ui/Neo_ClassMenu.res @@ -7,7 +7,7 @@ "xpos" "cs-0.5" "ypos" "cs-0.5" "wide" "430" - "tall" "432" + "tall" "448" "autoResize" "0" "pinCorner" "0" "visible" "1" @@ -56,7 +56,7 @@ "xpos" "0" "ypos" "16" "wide" "f" - "tall" "418" + "tall" "f" "autoResize" "0" "pinCorner" "0" "visible" "1" @@ -105,7 +105,7 @@ "ControlName" "ImagePanel" "fieldName" "IconPanel2" "xpos" "87" - "ypos" "90" + "ypos" "106" "wide" "256" "tall" "32" "autoResize" "0" @@ -115,13 +115,33 @@ "tabPosition" "0" "image" "choose_playermodel" "scaleImage" "0" - } + } + "Recon_Label" + { + "ControlName" "Label" + "fieldName" "Recon_Label" + "xpos" "10" + "ypos" "55" + "wide" "128" + "tall" "16" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "1" + "labelText" "0" + "textAlignment" "center" + "dulltext" "0" + "brighttext" "1" + "font" "Default" + "wrap" "0" + } "Scout_Button" { "ControlName" "CNeoButton" "fieldName" "Scout_Button" "xpos" "10" - "ypos" "55" + "ypos" "71" "wide" "128" "tall" "30" "autoResize" "0" @@ -129,7 +149,7 @@ "visible" "1" "enabled" "1" "tabPosition" "0" - "labelText" "RECON " + "labelText" "RECON" "textAlignment" "center" "dulltext" "0" "brighttext" "0" @@ -138,12 +158,32 @@ "Command" "setclass 1" "Default" "0" } + "Assault_Label" + { + "ControlName" "Label" + "fieldName" "Assault_Label" + "xpos" "150" + "ypos" "55" + "wide" "128" + "tall" "16" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "1" + "labelText" "0" + "textAlignment" "center" + "dulltext" "0" + "brighttext" "1" + "font" "Default" + "wrap" "0" + } "Assault_Button" { "ControlName" "CNeoButton" "fieldName" "Assault_Button" "xpos" "150" - "ypos" "55" + "ypos" "71" "wide" "128" "tall" "30" "autoResize" "0" @@ -151,7 +191,7 @@ "visible" "1" "enabled" "1" "tabPosition" "0" - "labelText" "ASSAULT " + "labelText" "ASSAULT" "textAlignment" "center" "dulltext" "0" "brighttext" "0" @@ -159,12 +199,32 @@ "Command" "setclass 2" "Default" "0" } + "Support_Label" + { + "ControlName" "Label" + "fieldName" "Support_Label" + "xpos" "290" + "ypos" "55" + "wide" "128" + "tall" "16" + "autoResize" "0" + "pinCorner" "0" + "visible" "1" + "enabled" "1" + "tabPosition" "1" + "labelText" "0" + "textAlignment" "center" + "dulltext" "0" + "brighttext" "1" + "font" "Default" + "wrap" "0" + } "Heavy_Button" { "ControlName" "CNeoButton" "fieldName" "Heavy_Button" "xpos" "290" - "ypos" "55" + "ypos" "71" "wide" "128" "tall" "30" "autoResize" "0" @@ -172,7 +232,7 @@ "visible" "1" "enabled" "1" "tabPosition" "0" - "labelText" "SUPPORT " + "labelText" "SUPPORT" "textAlignment" "center" "dulltext" "0" "brighttext" "0" @@ -185,7 +245,7 @@ "ControlName" "CNeoImageButton" "fieldName" "Skin1_Button" "xpos" "10" - "ypos" "130" + "ypos" "146" "wide" "128" "tall" "256" "autoResize" "0" @@ -202,7 +262,7 @@ "ControlName" "CNeoImageButton" "fieldName" "Skin2_Button" "xpos" "150" - "ypos" "130" + "ypos" "146" "wide" "128" "tall" "256" "autoResize" "0" @@ -219,7 +279,7 @@ "ControlName" "CNeoImageButton" "fieldName" "Skin3_Button" "xpos" "290" - "ypos" "130" + "ypos" "146" "wide" "128" "tall" "256" "autoResize" "0" @@ -236,7 +296,7 @@ "ControlName" "CNeoButton" "fieldName" "Back_Button" "xpos" "8" - "ypos" "394" + "ypos" "410" "wide" "207" "tall" "30" "autoResize" "0" diff --git a/src/game/client/c_team.cpp b/src/game/client/c_team.cpp index 5b5b36318c..28ced91c95 100644 --- a/src/game/client/c_team.cpp +++ b/src/game/client/c_team.cpp @@ -6,6 +6,9 @@ //=============================================================================// #include "cbase.h" #include "c_team.h" +#ifdef NEO +#include "c_playerresource.h" +#endif // NEO // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -33,6 +36,11 @@ IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_Team, DT_Team, CTeam) RecvPropInt( RECVINFO(m_iTeamNum)), RecvPropInt( RECVINFO(m_iScore)), RecvPropInt( RECVINFO(m_iRoundsWon) ), +#ifdef NEO + RecvPropInt( RECVINFO(m_iReconCount) ), + RecvPropInt( RECVINFO(m_iAssaultCount) ), + RecvPropInt( RECVINFO(m_iSupportCount) ), +#endif // NEO RecvPropString( RECVINFO(m_szTeamname)), RecvPropArray2( @@ -256,4 +264,54 @@ bool ArePlayersOnSameTeam( int iPlayerIndex1, int iPlayerIndex2 ) int GetNumberOfTeams( void ) { return g_Teams.Size(); -} \ No newline at end of file +} + +#ifdef NEO +int C_Team::GetClassCount(int neoClass) +{ + switch (neoClass) + { + case NEO_CLASS_RECON: + return m_iReconCount; + case NEO_CLASS_ASSAULT: + return m_iAssaultCount; + case NEO_CLASS_SUPPORT: + return m_iSupportCount; + } + + // C_Team is always networked, but individual players not necessarily, grab from g_PR instead + if (!g_PR) + return 0; + + int iNumClass = 0; + + const int iNumPlayers = GetNumPlayers(); + for (int i = 0; i < iNumPlayers; i++) + { + if (neoClass == g_PR->GetClass(m_aPlayers[i])) + { + iNumClass++; + } + } + + return iNumClass; +} + +extern ConVar sv_neo_class_limit_recon; +extern ConVar sv_neo_class_limit_assault; +extern ConVar sv_neo_class_limit_support; +bool C_Team::IsClassFull(int neoClass) const +{ + switch (neoClass) + { + case NEO_CLASS_RECON: + return sv_neo_class_limit_recon.GetInt() == -1 ? false : m_iReconCount >= sv_neo_class_limit_recon.GetInt(); + case NEO_CLASS_ASSAULT: + return sv_neo_class_limit_assault.GetInt() == -1 ? false : m_iAssaultCount >= sv_neo_class_limit_assault.GetInt(); + case NEO_CLASS_SUPPORT: + return sv_neo_class_limit_support.GetInt() == -1 ? false : m_iSupportCount >= sv_neo_class_limit_support.GetInt(); + } + + return false; +} +#endif // NEO diff --git a/src/game/client/c_team.h b/src/game/client/c_team.h index 5f26917b5b..2459875d5b 100644 --- a/src/game/client/c_team.h +++ b/src/game/client/c_team.h @@ -50,6 +50,11 @@ class C_Team : public C_BaseEntity void RemoveAllPlayers(); +#ifdef NEO + // Team Handling + virtual int GetClassCount(int neoClass); + virtual bool IsClassFull(int neoClass) const; +#endif // NEO // IClientThinkable overrides. public: @@ -70,6 +75,12 @@ class C_Team : public C_BaseEntity int m_iPing; int m_iPacketloss; int m_iTeamNum; + +#ifdef NEO + int m_iReconCount; + int m_iAssaultCount; + int m_iSupportCount; +#endif // NEO }; diff --git a/src/game/client/neo/game_controls/neo_classmenu.cpp b/src/game/client/neo/game_controls/neo_classmenu.cpp index 8f40968180..fb68bcf420 100644 --- a/src/game/client/neo/game_controls/neo_classmenu.cpp +++ b/src/game/client/neo/game_controls/neo_classmenu.cpp @@ -28,6 +28,7 @@ #endif #include "c_neo_player.h" +#include "c_team.h" #include #include @@ -103,7 +104,7 @@ CNeoClassMenu::CNeoClassMenu(IViewPort *pViewPort) //SetKeyBoardInputEnabled(true); // Leaving here to highlight menu navigation with keyboard is possible atm SetTitleBarVisible(false); - FindButtons(); + FindControls(); ListenForGameEvent("player_team"); } @@ -112,6 +113,9 @@ CNeoClassMenu::~CNeoClassMenu() m_pSkin1_Button->SetAutoDelete(true); m_pSkin2_Button->SetAutoDelete(true); m_pSkin3_Button->SetAutoDelete(true); + m_pRecon_Label->SetAutoDelete(true); + m_pAssault_Label->SetAutoDelete(true); + m_pSupport_Label->SetAutoDelete(true); m_pRecon_Button->SetAutoDelete(true); m_pAssault_Button->SetAutoDelete(true); m_pSupport_Button->SetAutoDelete(true); @@ -136,7 +140,7 @@ void CNeoClassMenu::FireGameEvent(IGameEvent* event) } } -void CNeoClassMenu::FindButtons() +void CNeoClassMenu::FindControls() { m_pSkin1_Button = FindControl("Skin1_Button"); if (m_pSkin1_Button) @@ -155,6 +159,10 @@ void CNeoClassMenu::FindButtons() { m_pSkin3_Button->SetButtonTexture("vgui/cm/jinrai_assault03"); } + + m_pRecon_Label = FindControl