//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //============================================================================= #include "cbase.h" #include "hud.h" #include "hudelement.h" #include "c_tf_player.h" #include "iclientmode.h" #include "ienginevgui.h" #include #include #include #include #include "c_baseobject.h" #include "tf_gamerules.h" #include "tf_item_inventory.h" #include "tf_hud_menu_engy_build.h" #include "inputsystem/iinputsystem.h" // NVNT haptics for buildings #include "haptics/haptic_utils.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; // Set to 1 to simulate xbox-style menu interaction ConVar tf_build_menu_controller_mode( "tf_build_menu_controller_mode", "0", FCVAR_ARCHIVE, "Use console controller build menus. 1 = ON, 0 = OFF." ); const EngyConstructBuilding_t g_kEngyBuildings[ NUM_ENGY_BUILDINGS ] = { // Sentry gun EngyConstructBuilding_t( true, OBJ_SENTRYGUN, 0, "sentry_active.res", "sentry_already_built.res", "sentry_cant_afford.res", "sentry_unavailable.res", "sentry_active.res", "sentry_inactive.res", "sentry_inactive.res" ), // Dispenser EngyConstructBuilding_t( true, OBJ_DISPENSER, 0, "dispenser_active.res", "dispenser_already_built.res", "dispenser_cant_afford.res", "dispenser_unavailable.res", "dispenser_active.res", "dispenser_inactive.res", "dispenser_inactive.res" ), // Teleporter entrance EngyConstructBuilding_t( true, OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE, "tele_entrance_active.res", "tele_entrance_already_built.res", "tele_entrance_cant_afford.res", "tele_entrance_unavailable.res", "tele_entrance_active.res", "tele_entrance_inactive.res", "tele_entrance_inactive.res" ), // Teleporter exit EngyConstructBuilding_t( true, OBJ_TELEPORTER, MODE_TELEPORTER_EXIT, "tele_exit_active.res", "tele_exit_already_built.res", "tele_exit_cant_afford.res", "tele_exit_unavailable.res", "tele_exit_active.res", "tele_exit_inactive.res", "tele_exit_inactive.res" ) }; static int flagSlots[NUM_ENGY_BUILDINGS] = { 0x01, 0x02, 0x04, 0x08 }; #ifdef STAGING_ONLY const EngyBuildingReplacement_t s_alternateEngineerBuildings[] = { // Catapult EngyBuildingReplacement_t( OBJ_CATAPULT, 0, "catapult_active.res", "catapult_already_built.res", "catapult_cant_afford.res", "catapult_unavailable.res", "catapult_active.res", "catapult_inactive.res", "catapult_inactive.res", flagSlots[2], flagSlots[3] ), // Speed 1 EngyBuildingReplacement_t( OBJ_TELEPORTER, MODE_TELEPORTER_SPEED, "speedpad_active.res", "speedpad_already_built.res", "speedpad_cant_afford.res", "speedpad_unavailable.res", "speedpad_active.res", "speedpad_inactive.res", "speedpad_inactive.res", flagSlots[2], 0 ), // Speed 2 EngyBuildingReplacement_t( OBJ_TELEPORTER, MODE_TELEPORTER_SPEED2, "speedpad_active.res", "speedpad_already_built.res", "speedpad_cant_afford.res", "speedpad_unavailable.res", "speedpad_active.res", "speedpad_inactive.res", "speedpad_inactive.res", flagSlots[3], 0 ), // Add more objects here }; #endif //====================================== DECLARE_HUDELEMENT_DEPTH( CHudMenuEngyBuild, 40 ); // in front of engy building status //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CHudMenuEngyBuild::CHudMenuEngyBuild( const char *pElementName ) : CHudBaseBuildMenu( pElementName, "HudMenuEngyBuild" ) { Panel *pParent = g_pClientMode->GetViewport(); SetParent( pParent ); SetHiddenBits( HIDEHUD_MISCSTATUS ); for ( int i=0; iAddTickSignal( GetVPanel() ); m_pActiveSelection = NULL; m_iSelectedItem = -1; m_pBuildLabelBright = NULL; m_pBuildLabelDim = NULL; m_pDestroyLabelBright = NULL; m_pDestroyLabelDim = NULL; m_bInConsoleMode = false; m_eCurrentBuildMenuLayout = BUILDMENU_DEFAULT; RegisterForRenderGroup( "mid" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudMenuEngyBuild::ApplySchemeSettings( IScheme *pScheme ) { bool bSteamController = ::input->IsSteamControllerActive(); bool b360Style = ( bSteamController || IsConsole() || tf_build_menu_controller_mode.GetBool() ); // load control settings... if ( b360Style ) { auto res_dir = bSteamController ? "resource/UI/build_menu_sc" : "resource/UI/build_menu_360"; LoadControlSettings( VarArgs("%s/HudMenuEngyBuild.res", res_dir ) ); // Load the already built images, destroyable m_pAlreadyBuiltObjects[0]->LoadControlSettings( VarArgs( "%s/sentry_already_built.res", res_dir ) ); m_pAlreadyBuiltObjects[1]->LoadControlSettings( VarArgs( "%s/dispenser_already_built.res", res_dir ) ); m_pAlreadyBuiltObjects[2]->LoadControlSettings( VarArgs( "%s/tele_entrance_already_built.res", res_dir ) ); m_pAlreadyBuiltObjects[3]->LoadControlSettings( VarArgs( "%s/tele_exit_already_built.res", res_dir ) ); m_pAvailableObjects[0]->LoadControlSettings( VarArgs( "%s/sentry_active.res", res_dir ) ); m_pAvailableObjects[1]->LoadControlSettings( VarArgs( "%s/dispenser_active.res", res_dir ) ); m_pAvailableObjects[2]->LoadControlSettings( VarArgs( "%s/tele_entrance_active.res", res_dir ) ); m_pAvailableObjects[3]->LoadControlSettings( VarArgs( "%s/tele_exit_active.res", res_dir ) ); m_pCantAffordObjects[0]->LoadControlSettings( VarArgs( "%s/sentry_cant_afford.res", res_dir ) ); m_pCantAffordObjects[1]->LoadControlSettings( VarArgs( "%s/dispenser_cant_afford.res", res_dir ) ); m_pCantAffordObjects[2]->LoadControlSettings( VarArgs( "%s/tele_entrance_cant_afford.res", res_dir ) ); m_pCantAffordObjects[3]->LoadControlSettings( VarArgs( "%s/tele_exit_cant_afford.res", res_dir ) ); m_pUnavailableObjects[0]->LoadControlSettings( "resource/UI/build_menu/sentry_unavailable.res" ); m_pUnavailableObjects[1]->LoadControlSettings( "resource/UI/build_menu/dispenser_unavailable.res" ); m_pUnavailableObjects[2]->LoadControlSettings( "resource/UI/build_menu/tele_entrance_unavailable.res" ); m_pUnavailableObjects[3]->LoadControlSettings( "resource/UI/build_menu/tele_exit_unavailable.res" ); m_pActiveSelection = dynamic_cast< CIconPanel * >( FindChildByName( "active_selection_bg" ) ); m_pBuildLabelBright = dynamic_cast< CExLabel * >( FindChildByName( "BuildHintLabel_Bright" ) ); m_pBuildLabelDim = dynamic_cast< CExLabel * >( FindChildByName( "BuildHintLabel_Dim" ) ); m_pDestroyLabelBright = dynamic_cast< CExLabel * >( FindChildByName( "DestroyHintLabel_Bright" ) ); m_pDestroyLabelDim = dynamic_cast< CExLabel * >( FindChildByName( "DestroyHintLabel_Dim" ) ); // Reposition the active selection to the default position m_iSelectedItem = -1; // force reposition SetSelectedItem( 1 ); } else { const char *pszCustomDir = NULL; switch( m_eCurrentBuildMenuLayout ) { case BUILDMENU_PIPBOY: pszCustomDir = "resource/UI/build_menu/pipboy"; break; default: case BUILDMENU_DEFAULT: pszCustomDir = "resource/UI/build_menu"; break; } LoadControlSettings( VarArgs("%s/HudMenuEngyBuild.res",pszCustomDir) ); // Load the already built images, not destroyable for ( int i=0; iLoadControlSettings( VarArgs( "%s/%s", pszCustomDir, m_Buildings[i].m_pszConstructAvailableObjectRes ) ); m_pAlreadyBuiltObjects[i]->LoadControlSettings( VarArgs( "%s/%s", pszCustomDir, m_Buildings[i].m_pszConstructAlreadyBuiltObjectRes ) ); m_pCantAffordObjects[i]->LoadControlSettings( VarArgs( "%s/%s", pszCustomDir, m_Buildings[i].m_pszConstructCantAffordObjectRes ) ); m_pUnavailableObjects[i]->LoadControlSettings( VarArgs( "%s/%s", pszCustomDir, m_Buildings[i].m_pszConstructUnavailableObjectRes ) ); // Set the Numerical Number pNumberLabel = dynamic_cast< CExLabel * >( m_pAvailableObjects[i]->FindChildByName( "NumberLabel" ) ); if ( pNumberLabel ) { pNumberLabel->SetText( VarArgs( "%d", i+1 ) ); } // Set the Numerical Number pNumberLabel = dynamic_cast( m_pAlreadyBuiltObjects[i]->FindChildByName( "NumberLabel" ) ); if ( pNumberLabel ) { pNumberLabel->SetText( VarArgs( "%d", i+1 ) ); } // Set the Numerical Number pNumberLabel = dynamic_cast( m_pCantAffordObjects[i]->FindChildByName( "NumberLabel" ) ); if ( pNumberLabel ) { pNumberLabel->SetText( VarArgs( "%d", i+1 ) ); } // Set the Numerical Number pNumberLabel = dynamic_cast( m_pUnavailableObjects[i]->FindChildByName( "NumberLabel" ) ); if ( pNumberLabel ) { pNumberLabel->SetText( VarArgs( "%d", i+1 ) ); } } m_pActiveSelection = NULL; m_pBuildLabelBright = NULL; m_pBuildLabelDim = NULL; m_pDestroyLabelBright = NULL; m_pDestroyLabelDim = NULL; } C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); // Set the cost label for ( int i=0; im_Shared.CalculateObjectCost( pLocalPlayer, iBuilding ) : GetObjectInfo( iBuilding )->m_Cost; m_pAvailableObjects[i]->SetDialogVariable( "metal", iCost ); m_pAlreadyBuiltObjects[i]->SetDialogVariable( "metal", iCost ); m_pCantAffordObjects[i]->SetDialogVariable( "metal", iCost ); m_pUnavailableObjects[i]->SetDialogVariable( "metal", iCost ); } BaseClass::ApplySchemeSettings( pScheme ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudMenuEngyBuild::GetBuildingIDAndModeFromSlot( int iSlot, int &iBuilding, int &iMode, const EngyConstructBuilding_t (&buildings)[ NUM_ENGY_BUILDINGS ] ) { iBuilding = OBJ_LAST; iMode = 0; int index = iSlot - 1; if ( index >= 0 && index < NUM_ENGY_BUILDINGS ) { iBuilding = buildings[index].m_iObjectType; iMode = buildings[index].m_iMode; } else { Assert( !"What slot are we asking for and why?" ); } } //----------------------------------------------------------------------------- // Purpose: Keyboard input hook. Return 0 if handled //----------------------------------------------------------------------------- int CHudMenuEngyBuild::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) { if ( !ShouldDraw() ) { return 1; } if ( !down ) { return 1; } bool bController = ( IsConsole() || ( keynum >= JOYSTICK_FIRST ) ); if ( bController ) { int iNewSelection = m_iSelectedItem; switch( keynum ) { case KEY_XBUTTON_UP: case STEAMCONTROLLER_DPAD_UP: // jump to last iNewSelection = NUM_ENGY_BUILDINGS; break; case KEY_XBUTTON_DOWN: case STEAMCONTROLLER_DPAD_DOWN: // jump to first iNewSelection = 1; break; case KEY_XBUTTON_RIGHT: case STEAMCONTROLLER_DPAD_RIGHT: // move selection to the right iNewSelection++; if ( iNewSelection > NUM_ENGY_BUILDINGS ) iNewSelection = 1; break; case KEY_XBUTTON_LEFT: case STEAMCONTROLLER_DPAD_LEFT: // move selection to the left iNewSelection--; if ( iNewSelection < 1 ) iNewSelection = NUM_ENGY_BUILDINGS; break; case KEY_XBUTTON_A: case KEY_XBUTTON_RTRIGGER: case STEAMCONTROLLER_A: // build selected item SendBuildMessage( m_iSelectedItem ); return 0; case KEY_XBUTTON_Y: case KEY_XBUTTON_LTRIGGER: case STEAMCONTROLLER_Y: { // destroy selected item bool bSuccess = SendDestroyMessage( m_iSelectedItem ); if ( bSuccess ) { engine->ExecuteClientCmd( "lastinv" ); } } return 0; case KEY_XBUTTON_B: case STEAMCONTROLLER_B: // cancel, close the menu engine->ExecuteClientCmd( "lastinv" ); return 0; default: return 1; // key not handled } SetSelectedItem( iNewSelection ); return 0; } else { int iSlot = 0; // convert slot1, slot2 etc to 1,2,3,4 if( pszCurrentBinding && ( !Q_strncmp( pszCurrentBinding, "slot", NUM_ENGY_BUILDINGS ) && Q_strlen(pszCurrentBinding) > NUM_ENGY_BUILDINGS ) ) { const char *pszNum = pszCurrentBinding+NUM_ENGY_BUILDINGS; iSlot = atoi(pszNum); // slot10 cancels if ( iSlot == 10 ) { engine->ExecuteClientCmd( "lastinv" ); return 0; } // allow slot1 - slot4 if ( iSlot < 1 || iSlot > NUM_ENGY_BUILDINGS ) return 1; } else { switch( keynum ) { case KEY_1: iSlot = 1; break; case KEY_2: iSlot = 2; break; case KEY_3: iSlot = 3; break; case KEY_4: iSlot = 4; break; case KEY_5: case KEY_6: case KEY_7: case KEY_8: case KEY_9: // Eat these keys return 0; case KEY_0: case KEY_XBUTTON_B: case STEAMCONTROLLER_B: // cancel, close the menu engine->ExecuteClientCmd( "lastinv" ); return 0; default: return 1; // key not handled } } if ( iSlot > 0 ) { SendBuildMessage( iSlot ); return 0; } } return 1; // key not handled } void CHudMenuEngyBuild::SendBuildMessage( int iSlot ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; int iBuilding, iMode; GetBuildingIDAndModeFromSlot( iSlot, iBuilding, iMode, m_Buildings ); if ( CanBuild( iSlot ) == false ) { return; } C_BaseObject *pObj = pLocalPlayer->GetObjectOfType( iBuilding, iMode ); int iCost = pLocalPlayer->m_Shared.CalculateObjectCost( pLocalPlayer, iBuilding ); int iBuildDisposableSents = CB_CANNOT_BUILD; if ( TFGameRules()->GameModeUsesUpgrades() && iBuilding == OBJ_SENTRYGUN ) { iBuildDisposableSents = pLocalPlayer->CanBuild( iBuilding, iMode ); } // If we don't already have a sentry (NULL), or we're allowed to build multiple, and we can afford it if ( ( pObj == NULL || iBuildDisposableSents == CB_CAN_BUILD ) && pLocalPlayer->GetAmmoCount( TF_AMMO_METAL ) >= iCost ) { char szCmd[128]; Q_snprintf( szCmd, sizeof(szCmd), "build %d %d", iBuilding, iMode ); engine->ClientCmd( szCmd ); // NVNT send the build command if ( haptics ) haptics->ProcessHapticEvent(2, "Game", szCmd); } else { pLocalPlayer->EmitSound( "Player.DenyWeaponSelection" ); } } bool CHudMenuEngyBuild::SendDestroyMessage( int iSlot ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return false; bool bSuccess = false; int iBuilding, iMode; GetBuildingIDAndModeFromSlot( iSlot, iBuilding, iMode, m_Buildings ); C_BaseObject *pObj = pLocalPlayer->GetObjectOfType( iBuilding, iMode ); if ( pObj != NULL ) { char szCmd[128]; Q_snprintf( szCmd, sizeof(szCmd), "destroy %d %d", iBuilding, iMode ); engine->ClientCmd( szCmd ); // NVNT send the destroy command if ( haptics ) haptics->ProcessHapticEvent(2, "Game", szCmd); bSuccess = true; } else { pLocalPlayer->EmitSound( "Player.DenyWeaponSelection" ); } return bSuccess; } // NVNT gate for placing effect. void CHudMenuEngyBuild::OnTick( void ) { if ( !IsVisible() ) return; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; int iAccount = pLocalPlayer->GetAmmoCount( TF_AMMO_METAL ); for ( int i=0;iGetObjectOfType( iRemappedObjectID, iMode ); } m_pAvailableObjects[i]->SetVisible( false ); m_pAlreadyBuiltObjects[i]->SetVisible( false ); m_pCantAffordObjects[i]->SetVisible( false ); m_pUnavailableObjects[i]->SetVisible( false ); if ( !m_Buildings[i].m_bEnabled ) { continue; } int iCost = pLocalPlayer->m_Shared.CalculateObjectCost( pLocalPlayer, iRemappedObjectID ); bool bAvailable = CanBuild( i + 1 ); // If the building is already built, and we don't have an ability to build more than one (sentry) if ( pObj != NULL && !pObj->IsPlacing() && !( pLocalPlayer->CanBuild( iRemappedObjectID, iMode ) == CB_CAN_BUILD ) ) { m_pAlreadyBuiltObjects[i]->SetVisible( true ); } // unavailable else if ( bAvailable == false ) { m_pUnavailableObjects[i]->SetVisible( true ); } // See if we can afford it else if ( iAccount < iCost ) { m_pCantAffordObjects[i]->SetVisible( true ); } else { // we can buy it m_pAvailableObjects[i]->SetVisible( true ); } } } void CHudMenuEngyBuild::SetVisible( bool state ) { if ( state == true ) { InitBuildings(); // close the weapon selection menu engine->ClientCmd( "cancelselect" ); bool bConsoleMode = ( IsConsole() || tf_build_menu_controller_mode.GetBool() ); if ( bConsoleMode != m_bInConsoleMode ) { InvalidateLayout( true, true ); m_bInConsoleMode = bConsoleMode; } else { // See if our layout needs to change, due to equipped items buildmenulayouts_t eDesired = CalcCustomBuildMenuLayout(); if ( eDesired != m_eCurrentBuildMenuLayout ) { m_eCurrentBuildMenuLayout = eDesired; InvalidateLayout( true, true ); } } // set the %lastinv% dialog var to our binding const char *key = engine->Key_LookupBinding( "lastinv" ); if ( !key ) { key = "< not bound >"; } SetDialogVariable( "lastinv", key ); // Set selection to the first available building that we can build C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; int iDefaultSlot = 1; // Find the first slot that represents a building that we haven't built int iSlot; for ( iSlot = 1; iSlot <= NUM_ENGY_BUILDINGS; iSlot++ ) { int iBuilding, iMode; GetBuildingIDAndModeFromSlot( iSlot, iBuilding, iMode, m_Buildings ); C_BaseObject *pObj = pLocalPlayer->GetObjectOfType( iBuilding, iMode ); if ( pObj == NULL ) { iDefaultSlot = iSlot; break; } } m_iSelectedItem = -1; //force redo SetSelectedItem( iDefaultSlot ); HideLowerPriorityHudElementsInGroup( "mid" ); for ( int i=0; im_Shared.CalculateObjectCost( pLocalPlayer, iBuilding ); m_pAvailableObjects[i]->SetDialogVariable( "metal", iCost ); m_pAlreadyBuiltObjects[i]->SetDialogVariable( "metal", iCost ); m_pCantAffordObjects[i]->SetDialogVariable( "metal", iCost ); } } else { UnhideLowerPriorityHudElementsInGroup( "mid" ); } BaseClass::SetVisible( state ); } void CHudMenuEngyBuild::SetSelectedItem( int iSlot ) { if ( m_iSelectedItem != iSlot ) { m_iSelectedItem = iSlot; // move the selection item to the new position if ( m_pActiveSelection ) { // move the selection background int x, y; m_pAlreadyBuiltObjects[m_iSelectedItem-1]->GetPos( x, y ); x -= XRES(NUM_ENGY_BUILDINGS); y -= XRES(NUM_ENGY_BUILDINGS); m_pActiveSelection->SetPos( x, y ); UpdateHintLabels(); } } } void CHudMenuEngyBuild::UpdateHintLabels( void ) { // hilight the action we can perform ( build or destroy or neither ) C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer ) { int iBuilding, iMode; GetBuildingIDAndModeFromSlot( m_iSelectedItem, iBuilding, iMode, m_Buildings ); C_BaseObject *pObj = pLocalPlayer->GetObjectOfType( iBuilding ); bool bDestroyLabelBright = false; bool bBuildLabelBright = false; int iCost = pLocalPlayer->m_Shared.CalculateObjectCost( pLocalPlayer, iBuilding ); if ( pObj ) { // hilight destroy, we have a building bDestroyLabelBright = true; } else if ( pLocalPlayer->GetAmmoCount( TF_AMMO_METAL ) >= iCost ) // I can afford it { // hilight build, we can build this bBuildLabelBright = true; } else { // dim both, do nothing } if ( m_pDestroyLabelBright && m_pDestroyLabelDim && m_pBuildLabelBright && m_pBuildLabelDim ) { m_pDestroyLabelBright->SetVisible( bDestroyLabelBright ); m_pDestroyLabelDim->SetVisible( !bDestroyLabelBright ); m_pBuildLabelBright->SetVisible( bBuildLabelBright ); m_pBuildLabelDim->SetVisible( !bBuildLabelBright ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- buildmenulayouts_t CHudMenuEngyBuild::CalcCustomBuildMenuLayout( void ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return BUILDMENU_DEFAULT; int iMenu = BUILDMENU_DEFAULT; CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iMenu, set_custom_buildmenu ); return (buildmenulayouts_t)iMenu; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudMenuEngyBuild::InitBuildings() { for( int i=0; i vecReplacements; #ifdef STAGING_ONLY int iOverrideType = -1; CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iOverrideType, override_engineer_object_type ); if ( iOverrideType >= 0 && iOverrideType < ARRAYSIZE( s_alternateEngineerBuildings ) ) { vecReplacements.AddToTail( &s_alternateEngineerBuildings[iOverrideType] ); } iOverrideType = -1; CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iOverrideType, override_engineer_object_type_2 ); if ( iOverrideType >= 0 && iOverrideType < ARRAYSIZE( s_alternateEngineerBuildings ) ) { vecReplacements.AddToTail( &s_alternateEngineerBuildings[iOverrideType] ); } // add more replacement attributes here #endif // verify the override data to make sure that they don't conflict with each other int iReplacedSlots = 0; int iDisabledSlots = 0; bool bReplaced = false; for ( int i=0; im_iReplacementSlots; int iDisablingSlots = pReplace->m_iDisableSlots; if ( iReplacedSlots & iReplacingSlots ) { AssertMsg( 0, "Trying to replace the same engineer building slot multiple time" ); continue; } if ( iReplacedSlots & iDisablingSlots ) { AssertMsg( 0, "Trying to disable a replaced engineer building slot" ); continue; } if ( iDisabledSlots & iReplacingSlots ) { AssertMsg( 0, "Trying to replace a disabled slot" ); continue; } // no conflict, replace the building for ( int j=0; jm_building; } else if ( flagSlots[j] & iDisablingSlots ) { targetBuildings[j].m_bEnabled = false; } } iReplacedSlots |= iReplacingSlots; iDisabledSlots |= iDisablingSlots; bReplaced = true; } } bool CHudMenuEngyBuild::CanBuild( int iSlot ) { bool bInTraining = TFGameRules() && TFGameRules()->IsInTraining(); if ( bInTraining == false ) { int slot = iSlot - 1; if ( slot >= 0 && slot < NUM_ENGY_BUILDINGS ) { return m_Buildings[slot].m_bEnabled; } return false; } bool bCanBuild = true; switch ( iSlot ) { case 1: { ConVarRef training_can_build_sentry( "training_can_build_sentry"); bCanBuild = training_can_build_sentry.GetInt() != 0; } break; case 2: { ConVarRef training_can_build_dispenser( "training_can_build_dispenser"); bCanBuild = training_can_build_dispenser.GetInt() != 0; } break; case 3: { ConVarRef training_can_build_tele_entrance( "training_can_build_tele_entrance"); bCanBuild = training_can_build_tele_entrance.GetInt() != 0; } break; case 4: { ConVarRef training_can_build_tele_exit( "training_can_build_tele_exit"); bCanBuild = training_can_build_tele_exit.GetInt() != 0; } break; } return bCanBuild; }