From 753fe25c18308015e2b9e5745633a6c137d91ef6 Mon Sep 17 00:00:00 2001 From: Scott Ehlert Date: Mon, 11 Oct 2010 17:51:21 -0500 Subject: [PATCH] SDK sync. --- game/client/swarm/asw_medal_store.cpp | 4 +- game/client/swarm/c_asw_steamstats.cpp | 100 +++- game/client/swarm/c_asw_steamstats.h | 12 +- game/client/swarm/gameui/swarm/vfooterpanel.h | 1 + .../client/swarm/gameui/swarm/vfoundgames.cpp | 2 +- .../swarm/gameui/swarm/vfoundpublicgames.cpp | 17 + .../swarm/gameui/swarm/vfoundpublicgames.h | 1 + .../swarm/gameui/swarm/vgamesettings.cpp | 132 ++++- .../client/swarm/gameui/swarm/vgamesettings.h | 2 + .../gameui/swarm/vingamedifficultyselect.cpp | 3 +- .../swarm/vgui/asw_difficulty_chooser.cpp | 3 + game/client/swarm/vgui/experience_bar.cpp | 79 +-- game/client/swarm/vgui/experience_bar.h | 2 + game/client/swarm/vgui/experience_report.cpp | 42 +- game/client/swarm/vgui/missionstatspanel.cpp | 34 +- .../swarm/vgui/nb_commander_list_entry.cpp | 2 +- game/client/swarm/vgui/nb_lobby_row.cpp | 5 +- game/client/swarm/vgui/nb_lobby_row.h | 1 + game/client/swarm/vgui/nb_lobby_tooltip.cpp | 3 + game/client/swarm/vgui/nb_main_panel.cpp | 2 +- game/client/swarm/vgui/nb_mission_options.cpp | 3 + game/client/swarm/vgui/nb_mission_panel.cpp | 83 +++- game/client/swarm/vgui/nb_mission_panel.h | 4 + game/client/swarm/vgui/nb_mission_summary.cpp | 33 +- game/server/ai_basenpc.cpp | 4 +- game/server/gameinterface.cpp | 14 +- game/server/swarm/asw_alien.cpp | 28 ++ game/server/swarm/asw_alien.h | 2 + game/server/swarm/asw_base_spawner.cpp | 7 +- game/server/swarm/asw_buzzer.cpp | 4 +- game/server/swarm/asw_director.cpp | 112 +++-- game/server/swarm/asw_director.h | 2 + game/server/swarm/asw_drone_advanced.cpp | 1 + game/server/swarm/asw_egg.cpp | 2 +- game/server/swarm/asw_gameinterface.cpp | 35 ++ game/server/swarm/asw_hack_computer.cpp | 2 +- game/server/swarm/asw_map_scores.cpp | 2 + game/server/swarm/asw_marine.cpp | 16 +- game/server/swarm/asw_marine_schedule.cpp | 1 + game/server/swarm/asw_parasite.cpp | 1 + game/server/swarm/asw_player.cpp | 72 ++- game/server/swarm/asw_queen.cpp | 1 + game/server/swarm/asw_spawn_manager.cpp | 461 +++++++++++++++++- game/server/swarm/asw_spawn_manager.h | 36 +- game/server/swarm/asw_spawner.cpp | 19 + game/server/swarm/asw_spawner.h | 1 + game/shared/SoundEmitterSystem.cpp | 11 +- game/shared/ai_responsesystem.cpp | 5 + game/shared/swarm/asw_achievements.cpp | 70 ++- game/shared/swarm/asw_achievements.h | 4 +- game/shared/swarm/asw_gamerules.cpp | 101 +++- game/shared/swarm/asw_gamerules.h | 10 +- game/shared/swarm/asw_marine_shared.cpp | 1 + game/shared/swarm/asw_player_experience.cpp | 34 +- game/shared/swarm/asw_player_shared.h | 4 +- game/shared/swarm/asw_shareddefs.h | 11 +- game/shared/swarm/asw_util_shared.cpp | 2 +- game/shared/swarm/asw_weapon_jump_jet.cpp | 2 +- game/shared/swarm/asw_weapon_shared.cpp | 1 + lib/public/vmpi.lib | Bin 6505102 -> 6505754 bytes public/filesystem_init.cpp | 29 +- 61 files changed, 1469 insertions(+), 209 deletions(-) diff --git a/game/client/swarm/asw_medal_store.cpp b/game/client/swarm/asw_medal_store.cpp index 9439be1c..d4af26db 100644 --- a/game/client/swarm/asw_medal_store.cpp +++ b/game/client/swarm/asw_medal_store.cpp @@ -56,7 +56,7 @@ void C_ASW_Medal_Store::LoadMedalStore() return; char szMedalFile[ 256 ]; - Q_snprintf( szMedalFile, sizeof( szMedalFile ), "cfg/clientc_%s.dat", pSteamUser->GetSteamID().Render() ); + Q_snprintf( szMedalFile, sizeof( szMedalFile ), "cfg/clientc_%I64u.dat", pSteamUser->GetSteamID().ConvertToUint64() ); int len = Q_strlen( szMedalFile ); for ( int i = 0; i < len; i++ ) { @@ -322,7 +322,7 @@ bool C_ASW_Medal_Store::SaveMedalStore() return false; char szMedalFile[ 256 ]; - Q_snprintf( szMedalFile, sizeof( szMedalFile ), "cfg/clientc_%s.dat", pSteamUser->GetSteamID().Render() ); + Q_snprintf( szMedalFile, sizeof( szMedalFile ), "cfg/clientc_%I64u.dat", pSteamUser->GetSteamID().ConvertToUint64() ); int len = Q_strlen( szMedalFile ); for ( int i = 0; i < len; i++ ) { diff --git a/game/client/swarm/c_asw_steamstats.cpp b/game/client/swarm/c_asw_steamstats.cpp index 4f00894e..9692b757 100644 --- a/game/client/swarm/c_asw_steamstats.cpp +++ b/game/client/swarm/c_asw_steamstats.cpp @@ -11,6 +11,7 @@ #include #include "asw_shareddefs.h" #include "c_asw_marine_resource.h" +#include "c_asw_campaign_save.h" CASW_Steamstats g_ASW_Steamstats; @@ -39,15 +40,56 @@ namespace const char* szShotsTotal = ".shotsfired.total"; const char* szShotsHit = ".shotshit.total"; const char* szHealingTotal = ".healing.total"; + const char* szTimeTotal = ".time.total"; + const char* szKillsAvg = ".kills.avg"; + const char* szDamageAvg = ".damage.avg"; + const char* szFFAvg = ".ff.avg"; + const char* szTimeAvg = ".time.avg"; + const char* szBestDifficulty = ".difficulty.best"; + const char* szBestTime = ".time.best"; + const char* szBestSpeedrunDifficulty = ".time.best.difficulty"; + // difficulty names used when fetching steam stats const char* g_szDifficulties[] = { "Easy", "Normal", "Hard", - "Insane" + "Insane", + "imba" }; + const char *g_OfficialMaps[] = + { + "asi-jac1-landingbay_01", + "asi-jac1-landingbay_02", + "asi-jac2-deima", + "asi-jac3-rydberg", + "asi-jac4-residential", + "asi-jac6-sewerjunction", + "asi-jac7-timorstation" + }; +} + +bool IsOfficialCampaign() +{ + if( !ASWGameRules()->IsCampaignGame() ) + return false; + + CASW_Campaign_Save *pCampaign = ASWGameRules()->GetCampaignSave(); + + const char *szMapName = engine->GetLevelNameShort(); + const char *szCampaignName = pCampaign->GetCampaignName(); + if( FStrEq( szCampaignName, "jacob" ) ) + { + for( int i=0; i < ARRAYSIZE( g_OfficialMaps ); ++i ) + { + if( FStrEq( szMapName, g_OfficialMaps[i] ) ) + return true; + } + } + + return false; } bool IsDamagingWeapon( const char* szWeaponName, bool bIsExtraEquip ) @@ -149,6 +191,10 @@ bool CASW_Steamstats::FetchStats( CSteamID playerSteamID, CASW_Player *pPlayer ) m_DifficultyCounts.Purge(); m_WeaponStats.Purge(); + // Returns true so we don't re-fetch stats + if( !IsOfficialCampaign() ) + return true; + // Fetch the player's overall stats FETCH_STEAM_STATS( "iTotalKills", m_iTotalKills ); FETCH_STEAM_STATS( "fAccuracy", m_fAccuracy ); @@ -253,7 +299,7 @@ bool CASW_Steamstats::FetchStats( CSteamID playerSteamID, CASW_Player *pPlayer ) } // Get difficulty counts - for( int i=0; i < 4; ++i ) + for( int i=0; i < 5; ++i ) { int32 iTempCount; FETCH_STEAM_STATS( CFmtStr( "%s.games.total", g_szDifficulties[ i ] ), iTempCount ); @@ -275,7 +321,13 @@ void CASW_Steamstats::PrepStatsForSend( CASW_Player *pPlayer ) return; // Update stats from the briefing screen - if( !GetDebriefStats() || !ASWGameResource() ) + if( !GetDebriefStats() + || !ASWGameResource() + || !IsOfficialCampaign() +#ifndef DEBUG + || ASWGameRules()->m_bCheated +#endif + ) return; if( m_MarineSelectionCounts.Count() == 0 || @@ -386,7 +438,10 @@ void CASW_Steamstats::PrepStatsForSend( CASW_Player *pPlayer ) SEND_STEAM_STATS( CFmtStr( "marines.%i.total", iMarineProfileIndex ), m_MarineSelectionCounts[iMarineProfileIndex] ); int iLevel = pPlayer->GetLevel(); SEND_STEAM_STATS( "level", iLevel ); - SEND_STEAM_STATS( "level.xprequired", ( iLevel == NELEMS( g_iLevelExperience ) ) ? 0 : g_iLevelExperience[ iLevel ] ); + int iPromotion = pPlayer->GetPromotion(); + float flXPRequired = ( iLevel == NELEMS( g_iLevelExperience ) ) ? 0 : g_iLevelExperience[ iLevel ]; + flXPRequired *= g_flPromotionXPScale[ iPromotion ]; + SEND_STEAM_STATS( "level.xprequired", (int) flXPRequired ); // Send favorite equip info SEND_STEAM_STATS( "equips.primary.fav", GetFavoriteEquip(0) ); @@ -560,6 +615,8 @@ bool DifficultyStats_t::FetchDifficultyStats( CSteamAPIContext * pSteamContext, break; case 4: szDifficulty = "insane"; break; + case 5: szDifficulty = "imba"; + break; } if( szDifficulty ) { @@ -618,6 +675,8 @@ void DifficultyStats_t::PrepStatsForSend( CASW_Player *pPlayer ) break; case 4: szDifficulty = "insane"; break; + case 5: szDifficulty = "imba"; + break; } if( szDifficulty ) { @@ -652,6 +711,12 @@ bool MissionStats_t::FetchMissionStats( CSteamAPIContext * pSteamContext, CSteam FETCH_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szKillsTotal ), m_iKillsTotal ); FETCH_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szDamageTotal ), m_iDamageTotal ); FETCH_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szFFTotal ), m_iFFTotal ); + FETCH_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szTimeTotal ), m_iTimeTotal ); + FETCH_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szBestDifficulty ), m_iHighestDifficulty ); + for( int i=0; i<5; ++i ) + { + FETCH_STEAM_STATS( CFmtStr( "%s%s.%s", szLevelName, szBestTime, g_szDifficulties[i] ), m_iBestSpeedrunTimes[i] ); + } return bOK; } @@ -663,6 +728,7 @@ void MissionStats_t::PrepStatsForSend( CASW_Player *pPlayer ) return; CASW_Marine_Resource *pMR = ASWGameResource()->GetFirstMarineResourceForPlayer( pPlayer ); + int iDifficulty = ASWGameRules()->GetSkillLevel(); if ( pMR ) { int iMarineIndex = ASWGameResource()->GetMarineResourceIndex( pMR ); @@ -674,6 +740,24 @@ void MissionStats_t::PrepStatsForSend( CASW_Player *pPlayer ) m_iGamesTotal++; m_iGamesSuccess += ASWGameRules()->GetMissionSuccess() ? 1 : 0; m_fGamesSuccessPercent = m_iGamesSuccess / (float)m_iGamesTotal * 100.0f; + m_iTimeTotal += GetDebriefStats()->m_fTimeTaken; + if( ASWGameRules()->GetMissionSuccess() ) + { + if( iDifficulty > m_iHighestDifficulty ) + m_iHighestDifficulty = iDifficulty; + + if( (unsigned int)m_iBestSpeedrunTimes[ iDifficulty - 1 ] > GetDebriefStats()->m_fTimeTaken ) + { + m_iBestSpeedrunTimes[ iDifficulty - 1 ] = GetDebriefStats()->m_fTimeTaken; + } + } + + + // Safely compute averages + m_fKillsAvg = m_iKillsTotal / (float)m_iGamesTotal; + m_fFFAvg = m_iFFTotal / (float)m_iGamesTotal; + m_fDamageAvg = m_iDamageTotal / (float)m_iGamesTotal; + m_iTimeAvg = m_iTimeTotal / (float)m_iGamesTotal; } } @@ -692,7 +776,13 @@ void MissionStats_t::PrepStatsForSend( CASW_Player *pPlayer ) SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szKillsTotal ), m_iKillsTotal ); SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szDamageTotal ), m_iDamageTotal ); SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szFFTotal ), m_iFFTotal ); - + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szTimeTotal ), m_iTimeTotal ); + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szKillsAvg ), m_fKillsAvg ); + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szDamageAvg ), m_fDamageAvg ); + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szFFAvg ), m_fFFAvg ); + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szTimeAvg ), m_iTimeAvg ); + SEND_STEAM_STATS( CFmtStr( "%s%s", szLevelName, szBestDifficulty ), m_iHighestDifficulty ); + SEND_STEAM_STATS( CFmtStr( "%s%s.%s", szLevelName, szBestTime, g_szDifficulties[ iDifficulty - 1 ] ), m_iBestSpeedrunTimes[ iDifficulty - 1 ] ); } bool WeaponStats_t::FetchWeaponStats( CSteamAPIContext * pSteamContext, CSteamID playerSteamID, const char *szClassName ) diff --git a/game/client/swarm/c_asw_steamstats.h b/game/client/swarm/c_asw_steamstats.h index 144955ea..7bcde65a 100644 --- a/game/client/swarm/c_asw_steamstats.h +++ b/game/client/swarm/c_asw_steamstats.h @@ -34,9 +34,13 @@ struct MissionStats_t int32 m_iKillsTotal; int32 m_iDamageTotal; int32 m_iFFTotal; - int32 m_iKillsAvg; - int32 m_iDamageAvg; - int32 m_iFFAvg; + float32 m_fKillsAvg; + float32 m_fDamageAvg; + float32 m_fFFAvg; + int32 m_iTimeTotal; + int32 m_iTimeAvg; + int32 m_iHighestDifficulty; + int32 m_iBestSpeedrunTimes[5]; }; struct WeaponStats_t @@ -105,7 +109,7 @@ private: StatList_Int_t m_MarineSelectionCounts; StatList_Int_t m_DifficultyCounts; - DifficultyStats_t m_DifficultyStats[4]; + DifficultyStats_t m_DifficultyStats[5]; MissionStats_t m_MissionStats; WeaponStatList_t m_WeaponStats; diff --git a/game/client/swarm/gameui/swarm/vfooterpanel.h b/game/client/swarm/gameui/swarm/vfooterpanel.h index a94d43a8..ae6e22db 100644 --- a/game/client/swarm/gameui/swarm/vfooterpanel.h +++ b/game/client/swarm/gameui/swarm/vfooterpanel.h @@ -58,6 +58,7 @@ public: FooterFormat_t GetFormat(); bool GetHelpTextEnabled(); void SetHelpText( const char *text ); + const char * GetHelpText() { return m_HelpText; } void FadeHelpText( void ); void GetPosition( int &x, int &y ); bool HasContent( void ); diff --git a/game/client/swarm/gameui/swarm/vfoundgames.cpp b/game/client/swarm/gameui/swarm/vfoundgames.cpp index 9d55eb42..e77a2dbd 100644 --- a/game/client/swarm/gameui/swarm/vfoundgames.cpp +++ b/game/client/swarm/gameui/swarm/vfoundgames.cpp @@ -966,7 +966,7 @@ FoundGames::FoundGames( Panel *parent, const char *panelName ): m_pHeaderFooter->SetHeaderEnabled( false ); m_pHeaderFooter->SetFooterEnabled( true ); m_pHeaderFooter->SetGradientBarEnabled( true ); - m_pHeaderFooter->SetGradientBarPos( 80, 300 ); + m_pHeaderFooter->SetGradientBarPos( 80, 315 ); m_pTitle = new vgui::Label( this, "Title", "" ); diff --git a/game/client/swarm/gameui/swarm/vfoundpublicgames.cpp b/game/client/swarm/gameui/swarm/vfoundpublicgames.cpp index 55b69691..e6d6c470 100644 --- a/game/client/swarm/gameui/swarm/vfoundpublicgames.cpp +++ b/game/client/swarm/gameui/swarm/vfoundpublicgames.cpp @@ -29,6 +29,7 @@ using namespace BaseModUI; //============================================================================= static ConVar ui_public_lobby_filter_difficulty2( "ui_public_lobby_filter_difficulty2", "", FCVAR_ARCHIVE, "Filter type for difficulty on the public lobby display" ); +static ConVar ui_public_lobby_filter_onslaught( "ui_public_lobby_filter_onslaught", "", FCVAR_ARCHIVE, "Filter type for Onslaught mode on the public lobby display"); ConVar ui_public_lobby_filter_campaign( "ui_public_lobby_filter_campaign", "", FCVAR_ARCHIVE, "Filter type for campaigns on the public lobby display" ); ConVar ui_public_lobby_filter_status( "ui_public_lobby_filter_status", "", FCVAR_ARCHIVE, "Filter type for game status on the public lobby display" ); @@ -38,6 +39,7 @@ FoundPublicGames::FoundPublicGames( Panel *parent, const char *panelName ) : m_pSearchManager( NULL ) { m_drpDifficulty = NULL; + m_drpOnslaught = NULL; m_drpGameStatus = NULL; m_drpCampaign = NULL; @@ -92,6 +94,7 @@ void FoundPublicGames::ApplySchemeSettings( IScheme *pScheme ) BaseClass::ApplySchemeSettings( pScheme ); m_drpDifficulty = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpFilterDifficulty" ) ); + m_drpOnslaught = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpFilterOnslaught" ) ); m_drpGameStatus = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpFilterGameStatus" ) ); m_drpCampaign = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpFilterCampaign" ) ); m_btnFilters = dynamic_cast< BaseModUI::BaseModHybridButton* >( FindChildByName( "BtnFilters" ) ); @@ -168,6 +171,10 @@ void FoundPublicGames::StartSearching( void ) if ( szDifficulty && *szDifficulty && GameModeHasDifficulty( szGameMode ) ) pKeyValuesSearch->SetString( "game/difficulty", szDifficulty ); + char const *szOnslaught = ui_public_lobby_filter_onslaught.GetString(); + if ( szOnslaught && *szOnslaught ) + pKeyValuesSearch->SetInt( "game/onslaught", 1 ); + char const *szStatus = ui_public_lobby_filter_status.GetString(); if ( szStatus && *szStatus ) pKeyValuesSearch->SetString( "game/state", szStatus ); @@ -501,6 +508,11 @@ void FoundPublicGames::OnCommand( const char *command ) ui_public_lobby_filter_difficulty2.SetValue( filterDifficulty ); StartSearching(); } + else if ( char const *filterOnslaught = StringAfterPrefix( command, "filter_onslaught_" ) ) + { + ui_public_lobby_filter_onslaught.SetValue( filterOnslaught ); + StartSearching(); + } else if ( char const *filterCampaign = StringAfterPrefix( command, "filter_campaign_" ) ) { ui_public_lobby_filter_campaign.SetValue( filterCampaign ); @@ -544,6 +556,11 @@ void FoundPublicGames::Activate() m_drpDifficulty->SetCurrentSelection( CFmtStr( "filter_difficulty_%s", ui_public_lobby_filter_difficulty2.GetString() ) ); } + if ( m_drpOnslaught ) + { + m_drpOnslaught->SetCurrentSelection( CFmtStr( "filter_onslaught_%s", ui_public_lobby_filter_onslaught.GetString() ) ); + } + if ( m_drpGameStatus ) { m_drpGameStatus->SetCurrentSelection( CFmtStr( "filter_status_%s", ui_public_lobby_filter_status.GetString() ) ); diff --git a/game/client/swarm/gameui/swarm/vfoundpublicgames.h b/game/client/swarm/gameui/swarm/vfoundpublicgames.h index f5fb080d..f25bdd0f 100644 --- a/game/client/swarm/gameui/swarm/vfoundpublicgames.h +++ b/game/client/swarm/gameui/swarm/vfoundpublicgames.h @@ -55,6 +55,7 @@ namespace BaseModUI { #endif DropDownMenu* m_drpDifficulty; + DropDownMenu* m_drpOnslaught; DropDownMenu* m_drpGameStatus; DropDownMenu* m_drpCampaign; BaseModUI::BaseModHybridButton *m_btnFilters; diff --git a/game/client/swarm/gameui/swarm/vgamesettings.cpp b/game/client/swarm/gameui/swarm/vgamesettings.cpp index 2b4fe2b0..03a3e4dd 100644 --- a/game/client/swarm/gameui/swarm/vgamesettings.cpp +++ b/game/client/swarm/gameui/swarm/vgamesettings.cpp @@ -55,13 +55,15 @@ GameSettings::GameSettings( vgui::Panel *parent, const char *panelName ): m_drpStartingMission( NULL ), m_bEditingSession( false ), m_bAllowChangeToCustomCampaign( true ), - m_bPreventSessionModifications( false ) + m_bPreventSessionModifications( false ), + m_drpFriendlyFire( NULL ), + m_drpOnslaught( NULL ) { m_pHeaderFooter = new CNB_Header_Footer( this, "HeaderFooter" ); m_pHeaderFooter->SetTitle( "" ); m_pHeaderFooter->SetHeaderEnabled( false ); m_pHeaderFooter->SetGradientBarEnabled( true ); - m_pHeaderFooter->SetGradientBarPos( 150, 170 ); + m_pHeaderFooter->SetGradientBarPos( 140, 190 ); m_pTitle = new vgui::Label( this, "Title", "" ); SetDeleteSelfOnClose(true); SetProportional( true ); @@ -206,6 +208,36 @@ void GameSettings::Activate() flyout->CloseMenu( NULL ); } + if ( m_drpFriendlyFire ) + { + if ( m_pSettings->GetInt( "game/hardcoreFF", 0 ) == 1 ) + { + m_drpFriendlyFire->SetCurrentSelection( "#L4D360UI_HardcoreFF" ); + } + else + { + m_drpFriendlyFire->SetCurrentSelection( "#L4D360UI_RegularFF" ); + } + + if ( FlyoutMenu* flyout = m_drpFriendlyFire->GetCurrentFlyout() ) + flyout->CloseMenu( NULL ); + } + + if ( m_drpOnslaught ) + { + if ( m_pSettings->GetInt( "game/onslaught", 0 ) == 1 ) + { + m_drpOnslaught->SetCurrentSelection( "#L4D360UI_OnslaughtEnabled" ); + } + else + { + m_drpOnslaught->SetCurrentSelection( "#L4D360UI_OnslaughtDisabled" ); + } + + if ( FlyoutMenu* flyout = m_drpOnslaught->GetCurrentFlyout() ) + flyout->CloseMenu( NULL ); + } + // If we have an active control, navigate from it since we'll be setting a new one if ( m_ActiveControl ) { @@ -581,6 +613,94 @@ void GameSettings::OnCommand(const char *command) pFlyout->SetListener( this ); } } + else if ( !Q_strcmp( command, "#L4D360UI_RegularFF" ) ) + { + KeyValues *pSettings = KeyValues::FromString( + "update", + " update { " + " game { " + " hardcoreFF = " + " } " + " } " + ); + KeyValues::AutoDelete autodelete( pSettings ); + + pSettings->SetInt( "update/game/hardcoreFF", 0 ); + + UpdateSessionSettings( pSettings ); + + if( m_drpFriendlyFire ) + { + if ( FlyoutMenu* pFlyout = m_drpFriendlyFire->GetCurrentFlyout() ) + pFlyout->SetListener( this ); + } + } + else if ( !Q_strcmp( command, "#L4D360UI_HardcoreFF" ) ) + { + KeyValues *pSettings = KeyValues::FromString( + "update", + " update { " + " game { " + " hardcoreFF = " + " } " + " } " + ); + KeyValues::AutoDelete autodelete( pSettings ); + + pSettings->SetInt( "update/game/hardcoreFF", 1 ); + + UpdateSessionSettings( pSettings ); + + if( m_drpFriendlyFire ) + { + if ( FlyoutMenu* pFlyout = m_drpFriendlyFire->GetCurrentFlyout() ) + pFlyout->SetListener( this ); + } + } + else if ( !Q_strcmp( command, "#L4D360UI_OnslaughtDisabled" ) ) + { + KeyValues *pSettings = KeyValues::FromString( + "update", + " update { " + " game { " + " onslaught = " + " } " + " } " + ); + KeyValues::AutoDelete autodelete( pSettings ); + + pSettings->SetInt( "update/game/onslaught", 0 ); + + UpdateSessionSettings( pSettings ); + + if( m_drpOnslaught ) + { + if ( FlyoutMenu* pFlyout = m_drpOnslaught->GetCurrentFlyout() ) + pFlyout->SetListener( this ); + } + } + else if ( !Q_strcmp( command, "#L4D360UI_OnslaughtEnabled" ) ) + { + KeyValues *pSettings = KeyValues::FromString( + "update", + " update { " + " game { " + " onslaught = " + " } " + " } " + ); + KeyValues::AutoDelete autodelete( pSettings ); + + pSettings->SetInt( "update/game/onslaught", 1 ); + + UpdateSessionSettings( pSettings ); + + if( m_drpOnslaught ) + { + if ( FlyoutMenu* pFlyout = m_drpOnslaught->GetCurrentFlyout() ) + pFlyout->SetListener( this ); + } + } else if ( const char *szRoundLimitValue = StringAfterPrefix( command, "#L4D360UI_RoundLimit_" ) ) { KeyValues *pSettings = new KeyValues( "update" ); @@ -654,6 +774,8 @@ void GameSettings::ApplySchemeSettings( vgui::IScheme *pScheme ) m_drpDifficulty = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpDifficulty" ) ); m_drpGameType = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpGameType" ) ); + m_drpFriendlyFire = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpFriendlyFire" ) ); + m_drpOnslaught = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpOnslaught" ) ); m_drpGameAccess = dynamic_cast< DropDownMenu* >( FindChildByName( "DrpGameAccess" ) ); if ( m_drpGameAccess ) @@ -710,6 +832,12 @@ void GameSettings::OnClose() if( m_drpGameType ) m_drpGameType->CloseDropDown(); + if( m_drpFriendlyFire ) + m_drpFriendlyFire->CloseDropDown(); + + if( m_drpOnslaught ) + m_drpOnslaught->CloseDropDown(); + m_pSettings = NULL; // NULL out settings in case we get some calls // after we are closed if ( m_bCloseSessionOnClose ) diff --git a/game/client/swarm/gameui/swarm/vgamesettings.h b/game/client/swarm/gameui/swarm/vgamesettings.h index 638d1c86..4fab632b 100644 --- a/game/client/swarm/gameui/swarm/vgamesettings.h +++ b/game/client/swarm/gameui/swarm/vgamesettings.h @@ -73,6 +73,8 @@ private: DropDownMenu* m_drpGameAccess; DropDownMenu* m_drpServerType; DropDownMenu* m_drpStartingMission; + DropDownMenu* m_drpFriendlyFire; + DropDownMenu* m_drpOnslaught; CNB_Header_Footer *m_pHeaderFooter; vgui::Label *m_pTitle; diff --git a/game/client/swarm/gameui/swarm/vingamedifficultyselect.cpp b/game/client/swarm/gameui/swarm/vingamedifficultyselect.cpp index 0e800fcd..fe709bf1 100644 --- a/game/client/swarm/gameui/swarm/vingamedifficultyselect.cpp +++ b/game/client/swarm/gameui/swarm/vingamedifficultyselect.cpp @@ -92,7 +92,8 @@ void InGameDifficultySelect::OnCommand(const char *command) if ( !Q_strcmp( command, "Easy" ) || !Q_strcmp( command, "Normal" ) || !Q_strcmp( command, "Hard" ) || - !Q_strcmp( command, "Insane" ) ) + !Q_strcmp( command, "Insane" ) || + !Q_strcmp( command, "Imba" ) ) { CGameUIConVarRef z_difficulty("z_difficulty"); diff --git a/game/client/swarm/vgui/asw_difficulty_chooser.cpp b/game/client/swarm/vgui/asw_difficulty_chooser.cpp index 13824670..c1f6d79b 100644 --- a/game/client/swarm/vgui/asw_difficulty_chooser.cpp +++ b/game/client/swarm/vgui/asw_difficulty_chooser.cpp @@ -140,6 +140,7 @@ CASW_Difficulty_Entry::CASW_Difficulty_Entry( vgui::Panel *pParent, const char * case 2: m_pDifficultyLabel->SetText("#asw_difficulty_chooser_normal"); break; case 3: m_pDifficultyLabel->SetText("#asw_difficulty_chooser_hard"); break; case 4: m_pDifficultyLabel->SetText("#asw_difficulty_chooser_insane"); break; + case 5: m_pDifficultyLabel->SetText("#asw_difficulty_chooser_imba"); break; default: m_pDifficultyLabel->SetText("???"); break; } @@ -149,6 +150,7 @@ CASW_Difficulty_Entry::CASW_Difficulty_Entry( vgui::Panel *pParent, const char * case 2: m_pDifficultyDescriptionLabel->SetText("#asw_difficulty_chooser_normald"); break; case 3: m_pDifficultyDescriptionLabel->SetText("#asw_difficulty_chooser_hardd"); break; case 4: m_pDifficultyDescriptionLabel->SetText("#asw_difficulty_chooser_insaned"); break; + case 5: m_pDifficultyDescriptionLabel->SetText("#asw_difficulty_chooser_imbad"); break; default: m_pDifficultyDescriptionLabel->SetText("???"); break; } @@ -158,6 +160,7 @@ CASW_Difficulty_Entry::CASW_Difficulty_Entry( vgui::Panel *pParent, const char * case 2: m_pImagePanel->SetImage("swarm/MissionPics/DifficultyPicNormal"); break; case 3: m_pImagePanel->SetImage("swarm/MissionPics/DifficultyPicHard"); break; case 4: m_pImagePanel->SetImage("swarm/MissionPics/DifficultyPicInsane"); break; + case 5: m_pImagePanel->SetImage("swarm/MissionPics/DifficultyPicInsane"); break; default: m_pImagePanel->SetImage("swarm/MissionPics/UnknownMissionPic"); break; } diff --git a/game/client/swarm/vgui/experience_bar.cpp b/game/client/swarm/vgui/experience_bar.cpp index 72b4a509..cdba4689 100644 --- a/game/client/swarm/vgui/experience_bar.cpp +++ b/game/client/swarm/vgui/experience_bar.cpp @@ -40,17 +40,28 @@ ExperienceBar::ExperienceBar(vgui::Panel *parent, const char *name) : m_pExperienceBar->SetShowMaxOnCounter( true ); m_pExperienceBar->SetColors( Color( 255, 255, 255, 0 ), Color( 93,148,192,255 ), Color( 255, 255, 255, 255 ), Color( 17,37,57,255 ), Color( 35, 77, 111, 255 ) ); //m_pExperienceBar->m_bShowCumulativeTotal = true; - m_pExperienceBar->AddMinMax( 0, g_iLevelExperience[ 0 ] ); - for ( int i = 0; i < ASW_NUM_EXPERIENCE_LEVELS - 1; i++ ) - { - m_pExperienceBar->AddMinMax( g_iLevelExperience[ i ], g_iLevelExperience[ i + 1 ] ); - } + m_nLastPromotion = -1; + UpdateMinMaxes( 0 ); m_pExperienceBar->m_flBorder = 1.5f; vgui::ivgui()->AddTickSignal( GetVPanel() ); } +void ExperienceBar::UpdateMinMaxes( int nPromotion ) +{ + if ( m_nLastPromotion == nPromotion ) + return; + + m_nLastPromotion = nPromotion; + m_pExperienceBar->ClearMinMax(); + m_pExperienceBar->AddMinMax( 0, g_iLevelExperience[ 0 ] * g_flPromotionXPScale[ m_nLastPromotion ] ); + for ( int i = 0; i < ASW_NUM_EXPERIENCE_LEVELS - 1; i++ ) + { + m_pExperienceBar->AddMinMax( g_iLevelExperience[ i ] * g_flPromotionXPScale[ m_nLastPromotion ] , g_iLevelExperience[ i + 1 ] * g_flPromotionXPScale[ m_nLastPromotion ] ); + } +} + void ExperienceBar::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); @@ -97,9 +108,9 @@ void ExperienceBar::OnTick() } } - if ( ASWGameRules()->GetGameState() <= ASW_GS_BRIEFING ) + if ( m_hPlayer.Get() ) { - if ( m_hPlayer.Get() ) + if ( ASWGameRules()->GetGameState() <= ASW_GS_BRIEFING ) { int nXP = m_hPlayer->GetExperience(); if ( nXP != m_nOldPlayerXP ) @@ -114,32 +125,32 @@ void ExperienceBar::OnTick() m_pPlayerNameLabel->SetText( m_hPlayer->GetPlayerName() ); } - } - else - { - float flBarMin = m_pExperienceBar->GetBarMin(); - bool bCapped = ( (int) m_pExperienceBar->m_fCurrent ) == ASW_XP_CAP; - - if ( m_flOldBarMin == -1 ) + else { + float flBarMin = m_pExperienceBar->GetBarMin(); + bool bCapped = ( (int) m_pExperienceBar->m_fCurrent ) >= ASW_XP_CAP * g_flPromotionXPScale[ m_hPlayer->GetPromotion() ]; + + if ( m_flOldBarMin == -1 ) + { + m_bOldCapped = bCapped; + } + + if ( m_flOldBarMin != -1 && ( m_flOldBarMin != flBarMin || m_bOldCapped != bCapped ) ) // bar min has changed - player has levelled up! + { + m_iPlayerLevel = LevelFromXP( m_pExperienceBar->m_fCurrent, m_hPlayer->GetPromotion() ); + UpdateLevelLabel(); + + m_pLevelUpLabel->SetVisible( true ); + SkillAnimPanel *pSkillAnim = dynamic_cast(GetClientMode()->GetViewport()->FindChildByName("SkillAnimPanel", true)); + if ( pSkillAnim ) + { + pSkillAnim->AddParticlesAroundPanel( m_pPlayerLevelLabel ); + } + } + + m_flOldBarMin = flBarMin; m_bOldCapped = bCapped; } - - if ( m_flOldBarMin != -1 && ( m_flOldBarMin != flBarMin || m_bOldCapped != bCapped ) ) // bar min has changed - player has levelled up! - { - m_iPlayerLevel = LevelFromXP( m_pExperienceBar->m_fCurrent ); - UpdateLevelLabel(); - - m_pLevelUpLabel->SetVisible( true ); - SkillAnimPanel *pSkillAnim = dynamic_cast(GetClientMode()->GetViewport()->FindChildByName("SkillAnimPanel", true)); - if ( pSkillAnim ) - { - pSkillAnim->AddParticlesAroundPanel( m_pPlayerLevelLabel ); - } - } - - m_flOldBarMin = flBarMin; - m_bOldCapped = bCapped; } } @@ -157,11 +168,6 @@ void ExperienceBar::InitFor( C_ASW_Player *pPlayer ) m_pPlayerNameLabel->SetText( "Player" ); m_pPlayerLevelLabel->SetText( "Level 5" ); m_pExperienceBar->Init( 1200, 1500, 1500.0f / 4.0f, true, false ); - m_pExperienceBar->AddMinMax( 0, g_iLevelExperience[ 0 ] ); - for ( int i = 0; i < ASW_NUM_EXPERIENCE_LEVELS - 1; i++ ) - { - m_pExperienceBar->AddMinMax( g_iLevelExperience[ i ], g_iLevelExperience[ i + 1 ] ); - } m_pExperienceBar->SetStartCountingTime( gpGlobals->curtime + 15.0f ); } return; @@ -188,6 +194,7 @@ void ExperienceBar::InitFor( C_ASW_Player *pPlayer ) } #endif + UpdateMinMaxes( pPlayer->GetPromotion() ); if ( ASWGameRules()->GetGameState() <= ASW_GS_BRIEFING ) { m_iPlayerLevel = pPlayer->GetLevel(); @@ -204,7 +211,7 @@ void ExperienceBar::InitFor( C_ASW_Player *pPlayer ) int iEarnedXP = pPlayer->GetEarnedXP( ASW_XP_TOTAL ); int nGoalXP = pPlayer->GetExperienceBeforeDebrief() + iEarnedXP; - nGoalXP = MIN( nGoalXP, ASW_XP_CAP ); + nGoalXP = MIN( nGoalXP, ASW_XP_CAP * g_flPromotionXPScale[ pPlayer->GetPromotion() ] ); float flRate = (float) iEarnedXP / 3.0f; // take 4 seconds to increase XP. if ( iEarnedXP < 150 ) // if XP is really low, count it up in 1 second { diff --git a/game/client/swarm/vgui/experience_bar.h b/game/client/swarm/vgui/experience_bar.h index 0337d194..13390faf 100644 --- a/game/client/swarm/vgui/experience_bar.h +++ b/game/client/swarm/vgui/experience_bar.h @@ -32,6 +32,7 @@ public: void InitFor( C_ASW_Player *pPlayer ); void UpdateLevelLabel(); + void UpdateMinMaxes( int nPromotion ); bool IsDoneAnimating(); @@ -51,6 +52,7 @@ public: int m_iPlayerLevel; int m_nOldPlayerXP; CSteamID m_lastSteamID; + int m_nLastPromotion; }; class ExperienceBarSmall : public ExperienceBar diff --git a/game/client/swarm/vgui/experience_report.cpp b/game/client/swarm/vgui/experience_report.cpp index 5418b707..2811f7b9 100644 --- a/game/client/swarm/vgui/experience_report.cpp +++ b/game/client/swarm/vgui/experience_report.cpp @@ -161,7 +161,7 @@ void CExperienceReport::PerformLayout() if ( pPlayer ) { int nPlayerLevel = pPlayer->GetLevel(); - m_pWeaponUnlockPanel->SetVisible( nPlayerLevel < ASW_LEVEL_CAP ); + m_pWeaponUnlockPanel->SetVisible( nPlayerLevel < ASW_NUM_EXPERIENCE_LEVELS ); if ( ASWGameRules() && ASWGameRules()->m_iSkillLevel == 2 && !ASWGameRules()->IsOfflineGame() && !( ASWGameRules()->IsCampaignGame() && ASWGameRules()->CampaignMissionsLeft() <= 1 ) ) @@ -194,26 +194,28 @@ void CExperienceReport::OnThink() // monitor local player's experience bar to see when it loops float flBarMin = m_pExperienceBar[ 0 ]->m_pExperienceBar->GetBarMin(); - bool bCapped = ( (int) m_pExperienceBar[ 0 ]->m_pExperienceBar->m_fCurrent ) == ASW_XP_CAP; - - if ( m_flOldBarMin == -1 ) + bool bCapped = false; + + C_ASW_Player *pPlayer = C_ASW_Player::GetLocalASWPlayer(); + if ( pPlayer ) { - m_bOldCapped = bCapped; - } - - if ( m_flOldBarMin != -1 && ( m_flOldBarMin != flBarMin || m_bOldCapped != bCapped ) ) // bar min has changed - player has levelled up! - { - m_bPendingUnlockSequence = true; - IGameEvent *event = gameeventmanager->CreateEvent( "level_up" ); - int nNewLevel = LevelFromXP( (int) m_pExperienceBar[ 0 ]->m_pExperienceBar->m_fCurrent ); - if ( event ) + bCapped = ( (int) m_pExperienceBar[ 0 ]->m_pExperienceBar->m_fCurrent ) >= ASW_XP_CAP * g_flPromotionXPScale[ pPlayer->GetPromotion() ]; + if ( m_flOldBarMin == -1 ) { - event->SetInt( "level", nNewLevel ); - gameeventmanager->FireEventClientSide( event ); + m_bOldCapped = bCapped; } - C_ASW_Player *pPlayer = C_ASW_Player::GetLocalASWPlayer(); - if ( pPlayer ) + + if ( m_flOldBarMin != -1 && ( m_flOldBarMin != flBarMin || m_bOldCapped != bCapped ) ) // bar min has changed - player has levelled up! { + m_bPendingUnlockSequence = true; + IGameEvent *event = gameeventmanager->CreateEvent( "level_up" ); + int nNewLevel = LevelFromXP( (int) m_pExperienceBar[ 0 ]->m_pExperienceBar->m_fCurrent, pPlayer->GetPromotion() ); + if ( event ) + { + event->SetInt( "level", nNewLevel ); + gameeventmanager->FireEventClientSide( event ); + } + const char *szWeaponClassUnlocked = pPlayer->GetWeaponUnlockedAtLevel( nNewLevel ); if ( szWeaponClassUnlocked ) { @@ -227,9 +229,10 @@ void CExperienceReport::OnThink() pComplete->OnWeaponUnlocked( szWeaponClassUnlocked ); } } + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, -1 /*SOUND_FROM_LOCAL_PLAYER*/, "ASW_XP.LevelUp" ); } - CLocalPlayerFilter filter; - C_BaseEntity::EmitSound( filter, -1 /*SOUND_FROM_LOCAL_PLAYER*/, "ASW_XP.LevelUp" ); } m_flOldBarMin = flBarMin; @@ -256,6 +259,7 @@ void CExperienceReport::OnThink() case 2: m_pXPDifficultyScaleNumber->SetText( "" ); flTotalXP *= g_flXPDifficultyScale[1]; break; case 3: m_pXPDifficultyScaleNumber->SetText( "+20%" ); flTotalXP *= g_flXPDifficultyScale[2]; break; case 4: m_pXPDifficultyScaleNumber->SetText( "+40%" ); flTotalXP *= g_flXPDifficultyScale[3]; break; + case 5: m_pXPDifficultyScaleNumber->SetText( "+50%" ); flTotalXP *= g_flXPDifficultyScale[4]; break; } bool bShowDifficultyBonus = ( ASWGameRules()->GetSkillLevel() != 2 ); m_pXPDifficultyScaleNumber->SetVisible( bShowDifficultyBonus ); diff --git a/game/client/swarm/vgui/missionstatspanel.cpp b/game/client/swarm/vgui/missionstatspanel.cpp index 4f3ac30f..2452dd27 100644 --- a/game/client/swarm/vgui/missionstatspanel.cpp +++ b/game/client/swarm/vgui/missionstatspanel.cpp @@ -115,29 +115,25 @@ void MissionStatsPanel::SetMissionLabels(vgui::Label *pMissionLabel, vgui::Label pszToken = "#asw_difficulty_easy"; else if (iDiff == 3) pszToken = "#asw_difficulty_hard"; - else if (iDiff >= 4) + else if (iDiff == 4) pszToken = "#asw_difficulty_insane"; + else if (iDiff >= 5) + pszToken = "#asw_difficulty_imba"; const wchar_t *pDiff = g_pVGuiLocalize->Find( pszToken ); - // find campaign/single mission - if (bCampaign) - pszToken = "#asw_difficulty_campaign"; - else - pszToken = "#asw_difficulty_mission"; - const wchar_t *pCampaign = g_pVGuiLocalize->Find( pszToken ); + bool bOnslaught = CAlienSwarm::IsOnslaught(); - // find style - if (bUber) - pszToken = "#asw_difficulty_uber"; - else if (bCarnage) - pszToken = "#asw_difficulty_carnage"; - else if (bHardcore) - pszToken = "#asw_difficulty_hardcore"; - - const wchar_t *pStyle = L""; - if (bUber || bCarnage || bHardcore) + const wchar_t *pOnslaught = L""; + if ( bOnslaught ) { - pStyle = g_pVGuiLocalize->Find( pszToken ); + pOnslaught = g_pVGuiLocalize->Find( "#nb_onslaught_title" ); + } + + bool bHardcoreFriendlyFire = CAlienSwarm::IsHardcoreFF(); + const wchar_t *pHardcoreFF = L""; + if ( bHardcoreFriendlyFire ) + { + pHardcoreFF = g_pVGuiLocalize->Find( "#asw_hardcore_ff" ); } const wchar_t *pCheated = L""; @@ -149,7 +145,7 @@ void MissionStatsPanel::SetMissionLabels(vgui::Label *pMissionLabel, vgui::Label wchar_t mission_difficulty[96]; g_pVGuiLocalize->ConstructString( mission_difficulty, sizeof(mission_difficulty), g_pVGuiLocalize->Find("#asw_mission_difficulty"), 4, - pCampaign, pDiff, pStyle, pCheated); + pDiff, pOnslaught, pHardcoreFF, pCheated); pDifficultyLabel->SetText(mission_difficulty); } diff --git a/game/client/swarm/vgui/nb_commander_list_entry.cpp b/game/client/swarm/vgui/nb_commander_list_entry.cpp index dae963ae..0d84e155 100644 --- a/game/client/swarm/vgui/nb_commander_list_entry.cpp +++ b/game/client/swarm/vgui/nb_commander_list_entry.cpp @@ -87,7 +87,7 @@ void CNB_Commander_List_Entry::OnThink() m_pCommanderName->SetText( pPlayer->GetPlayerName() ); int nXP = pPlayer->GetExperienceBeforeDebrief() + pPlayer->GetEarnedXP( ASW_XP_TOTAL ); - int nLevel = LevelFromXP( nXP ); + int nLevel = LevelFromXP( nXP, pPlayer->GetPromotion() ); wchar_t szLevelNum[16]=L""; _snwprintf( szLevelNum, ARRAYSIZE( szLevelNum ), L"%i", nLevel + 1 ); // levels start at 0 in code, but show from 1 in the UI diff --git a/game/client/swarm/vgui/nb_lobby_row.cpp b/game/client/swarm/vgui/nb_lobby_row.cpp index 25ccf07b..feaf20a0 100644 --- a/game/client/swarm/vgui/nb_lobby_row.cpp +++ b/game/client/swarm/vgui/nb_lobby_row.cpp @@ -77,10 +77,11 @@ CNB_Lobby_Row::CNB_Lobby_Row( vgui::Panel *parent, const char *name ) : BaseClas m_pXPBar->SetShowMaxOnCounter( true ); m_pXPBar->SetColors( Color( 255, 255, 255, 0 ), Color( 93,148,192,255 ), Color( 255, 255, 255, 255 ), Color( 17,37,57,255 ), Color( 35, 77, 111, 255 ) ); //m_pXPBar->m_bShowCumulativeTotal = true; - m_pXPBar->AddMinMax( 0, g_iLevelExperience[ 0 ] ); + m_nLastPromotion = 0; + m_pXPBar->AddMinMax( 0, g_iLevelExperience[ 0 ] * g_flPromotionXPScale[ m_nLastPromotion ] ); for ( int i = 0; i < ASW_NUM_EXPERIENCE_LEVELS - 1; i++ ) { - m_pXPBar->AddMinMax( g_iLevelExperience[ i ], g_iLevelExperience[ i + 1 ] ); + m_pXPBar->AddMinMax( g_iLevelExperience[ i ] * g_flPromotionXPScale[ m_nLastPromotion ], g_iLevelExperience[ i + 1 ] * g_flPromotionXPScale[ m_nLastPromotion ] ); } m_pXPBar->m_flBorder = 1.5f; diff --git a/game/client/swarm/vgui/nb_lobby_row.h b/game/client/swarm/vgui/nb_lobby_row.h index 1ebe8f98..3b7ce6f1 100644 --- a/game/client/swarm/vgui/nb_lobby_row.h +++ b/game/client/swarm/vgui/nb_lobby_row.h @@ -68,6 +68,7 @@ public: char m_szLastWeaponImage[ ASW_NUM_INVENTORY_SLOTS ][ 255 ]; char m_szLastPortraitImage[ 255 ]; CSteamID m_lastSteamID; + int m_nLastPromotion; int m_nLobbySlot; }; diff --git a/game/client/swarm/vgui/nb_lobby_tooltip.cpp b/game/client/swarm/vgui/nb_lobby_tooltip.cpp index 24572962..1fc7c242 100644 --- a/game/client/swarm/vgui/nb_lobby_tooltip.cpp +++ b/game/client/swarm/vgui/nb_lobby_tooltip.cpp @@ -170,6 +170,9 @@ void CNB_Lobby_Tooltip::OnTick() case 1: m_pPromotionLabel->SetText( "#nb_first_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_1"); break; case 2: m_pPromotionLabel->SetText( "#nb_second_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_2"); break; case 3: m_pPromotionLabel->SetText( "#nb_third_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_3"); break; + case 4: m_pPromotionLabel->SetText( "#nb_fourth_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_4"); break; + case 5: m_pPromotionLabel->SetText( "#nb_fifth_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_5"); break; + case 6: m_pPromotionLabel->SetText( "#nb_sixth_promotion" ); m_pTitle->SetText( "#nb_promotion_medal_6"); break; } m_pPromotionIcon->SetImage( VarArgs( "briefing/promotion_%d_LG", nPromotion ) ); } diff --git a/game/client/swarm/vgui/nb_main_panel.cpp b/game/client/swarm/vgui/nb_main_panel.cpp index e35314cf..0a5c6aa6 100644 --- a/game/client/swarm/vgui/nb_main_panel.cpp +++ b/game/client/swarm/vgui/nb_main_panel.cpp @@ -158,7 +158,7 @@ void CNB_Main_Panel::OnThink() m_pChatButton->SetVisible( gpGlobals->maxClients > 1 ); m_pVoteButton->SetVisible( gpGlobals->maxClients > 1 ); C_ASW_Player *pPlayer = C_ASW_Player::GetLocalASWPlayer(); - m_pPromotionButton->SetVisible( pPlayer && pPlayer->GetExperience() >= ASW_XP_CAP && pPlayer->GetPromotion() < ASW_PROMOTION_CAP ); + m_pPromotionButton->SetVisible( pPlayer && pPlayer->GetExperience() >= ( ASW_XP_CAP * g_flPromotionXPScale[ pPlayer->GetPromotion() ] ) && pPlayer->GetPromotion() < ASW_PROMOTION_CAP ); diff --git a/game/client/swarm/vgui/nb_mission_options.cpp b/game/client/swarm/vgui/nb_mission_options.cpp index 596f7bfe..2ccf4203 100644 --- a/game/client/swarm/vgui/nb_mission_options.cpp +++ b/game/client/swarm/vgui/nb_mission_options.cpp @@ -70,6 +70,8 @@ void CNB_Mission_Options::OnThink() m_pSkillLevelLabel->SetText("#asw_difficulty_hard"); else if (m_iLastSkillLevel == 1) m_pSkillLevelLabel->SetText("#asw_difficulty_easy"); + else if (m_iLastSkillLevel == 5) + m_pSkillLevelLabel->SetText("#asw_difficulty_imba"); else m_pSkillLevelLabel->SetText("#asw_difficulty_normal"); @@ -80,6 +82,7 @@ void CNB_Mission_Options::OnThink() case 2: m_pSkillDescriptionLabel->SetText("#asw_difficulty_chooser_normald"); break; case 3: m_pSkillDescriptionLabel->SetText("#asw_difficulty_chooser_hardd"); break; case 4: m_pSkillDescriptionLabel->SetText("#asw_difficulty_chooser_insaned"); break; + case 5: m_pSkillDescriptionLabel->SetText("#asw_difficulty_chooser_imbad"); break; default: m_pSkillDescriptionLabel->SetText("???"); break; } diff --git a/game/client/swarm/vgui/nb_mission_panel.cpp b/game/client/swarm/vgui/nb_mission_panel.cpp index 44da0716..2aa69ee5 100644 --- a/game/client/swarm/vgui/nb_mission_panel.cpp +++ b/game/client/swarm/vgui/nb_mission_panel.cpp @@ -19,6 +19,8 @@ #include "c_asw_game_resource.h" #include "asw_input.h" #include "nb_island.h" +#include "gameui/swarm/basemodpanel.h" +#include "gameui/swarm/VFooterPanel.h" using namespace vgui; @@ -45,6 +47,8 @@ CNB_Mission_Panel::CNB_Mission_Panel( vgui::Panel *parent, const char *name ) : // == MANAGED_MEMBER_CREATION_END == m_pBackButton = new CNB_Button( this, "BackButton", "", this, "BackButton" ); m_drpDifficulty = new BaseModUI::DropDownMenu( this, "DrpDifficulty" ); + m_drpFriendlyFire = new BaseModUI::DropDownMenu( this, "DrpFriendlyFire" ); + m_drpOnslaught = new BaseModUI::DropDownMenu( this, "DrpOnslaught" ); m_drpFixedSkillPoints = new BaseModUI::DropDownMenu( this, "DrpFixedSkillPoints" ); m_pHeaderFooter->SetTitle( "#nb_mission_details" ); @@ -61,6 +65,8 @@ CNB_Mission_Panel::CNB_Mission_Panel( vgui::Panel *parent, const char *name ) : m_iLastSkillLevel = -1; m_iLastFixedSkillPoints = -1; + m_iLastHardcoreFF = -1; + m_iLastOnslaught = -1; } CNB_Mission_Panel::~CNB_Mission_Panel() @@ -165,6 +171,8 @@ void CNB_Mission_Panel::OnThink() bool bLeader = ( pPlayer && (pPlayer->entindex() == iLeaderIndex ) ); m_drpDifficulty->SetEnabled( ASWGameRules()->GetGameState() == ASW_GS_BRIEFING && bLeader ); + m_drpFriendlyFire->SetEnabled( ASWGameRules()->GetGameState() == ASW_GS_BRIEFING && bLeader ); + m_drpOnslaught->SetEnabled( ASWGameRules()->GetGameState() == ASW_GS_BRIEFING && bLeader ); m_drpFixedSkillPoints->SetEnabled( false ); //ASWGameRules()->GetGameState() == ASW_GS_BRIEFING && bLeader ); if (m_iLastSkillLevel != ASWGameRules()->GetSkillLevel()) @@ -173,27 +181,69 @@ void CNB_Mission_Panel::OnThink() if (m_iLastSkillLevel == 4) { m_drpDifficulty->SetCurrentSelection("#L4D360UI_Difficulty_insane"); - m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_insaned" ); + //m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_insaned" ); } else if (m_iLastSkillLevel == 3) { m_drpDifficulty->SetCurrentSelection("#L4D360UI_Difficulty_hard"); - m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_hardd" ); + //m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_hardd" ); } else if (m_iLastSkillLevel == 1) { m_drpDifficulty->SetCurrentSelection("#L4D360UI_Difficulty_easy"); - m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_easyd" ); + //m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_easyd" ); + } + else if (m_iLastSkillLevel == 5) + { + m_drpDifficulty->SetCurrentSelection("#L4D360UI_Difficulty_imba"); + //m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_imbad" ); } else { m_drpDifficulty->SetCurrentSelection("#L4D360UI_Difficulty_normal"); - m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_normald" ); + //m_pDifficultyDescription->SetText( "#asw_difficulty_chooser_normald" ); } } + extern ConVar asw_sentry_friendly_fire_scale; + extern ConVar asw_marine_ff_absorption; + int nHardcoreFF = ( asw_sentry_friendly_fire_scale.GetFloat() != 0.0f || asw_marine_ff_absorption.GetInt() != 1 ) ? 1 : 0; + if ( m_iLastHardcoreFF != nHardcoreFF ) + { + m_iLastHardcoreFF = nHardcoreFF; + if ( nHardcoreFF == 1 ) + { + m_drpFriendlyFire->SetCurrentSelection( "#L4D360UI_HardcoreFF" ); + } + else + { + m_drpFriendlyFire->SetCurrentSelection( "#L4D360UI_RegularFF" ); + } + } + extern ConVar asw_horde_override; + extern ConVar asw_wanderer_override; + int nOnslaught = ( asw_horde_override.GetBool() || asw_wanderer_override.GetBool() ) ? 1 : 0; + if ( m_iLastOnslaught != nOnslaught ) + { + m_iLastOnslaught = nOnslaught; + if ( nOnslaught == 1 ) + { + m_drpOnslaught->SetCurrentSelection( "#L4D360UI_OnslaughtEnabled" ); + } + else + { + m_drpOnslaught->SetCurrentSelection( "#L4D360UI_OnslaughtDisabled" ); + } + } + + BaseModUI::CBaseModFooterPanel *footer = BaseModUI::CBaseModPanel::GetSingleton().GetFooterPanel(); + if ( footer ) + { + m_pDifficultyDescription->SetText( footer->GetHelpText() ); + } // only show insane in multiplayer m_drpDifficulty->SetFlyoutItemEnabled( "BtnImpossible", gpGlobals->maxClients > 1 ); + m_drpDifficulty->SetFlyoutItemEnabled( "BtnImba", gpGlobals->maxClients > 1 ); if ( ASWGameRules()->IsCampaignGame() && ASWGameRules()->GetCampaignSave() && ASWGameRules()->GetGameState() != ASW_GS_INGAME ) { @@ -265,6 +315,31 @@ void CNB_Mission_Panel::OnCommand( const char *command ) engine->ClientCmd( "cl_skill 4" ); return; } + else if ( !Q_stricmp( command, "#L4D360UI_Difficulty_imba" ) ) + { + engine->ClientCmd( "cl_skill 5" ); + return; + } + else if ( !Q_stricmp( command, "#L4D360UI_RegularFF" ) ) + { + engine->ClientCmd( "cl_hardcore_ff 0" ); + return; + } + else if ( !Q_stricmp( command, "#L4D360UI_HardcoreFF" ) ) + { + engine->ClientCmd( "cl_hardcore_ff 1" ); + return; + } + else if ( !Q_stricmp( command, "#L4D360UI_OnslaughtDisabled" ) ) + { + engine->ClientCmd( "cl_onslaught 0" ); + return; + } + else if ( !Q_stricmp( command, "#L4D360UI_OnslaughtEnabled" ) ) + { + engine->ClientCmd( "cl_onslaught 1" ); + return; + } BaseClass::OnCommand( command ); } diff --git a/game/client/swarm/vgui/nb_mission_panel.h b/game/client/swarm/vgui/nb_mission_panel.h index 985a5845..e8e0b5e0 100644 --- a/game/client/swarm/vgui/nb_mission_panel.h +++ b/game/client/swarm/vgui/nb_mission_panel.h @@ -45,6 +45,8 @@ public: CNB_Button *m_pBackButton; BaseModUI::DropDownMenu* m_drpDifficulty; BaseModUI::DropDownMenu* m_drpFixedSkillPoints; + BaseModUI::DropDownMenu* m_drpFriendlyFire; + BaseModUI::DropDownMenu* m_drpOnslaught; ObjectiveListBox* m_pObjectiveList; ObjectiveDetailsPanel* m_pObjectiveDetails; @@ -59,6 +61,8 @@ public: int m_iLastSkillLevel; int m_iLastFixedSkillPoints; + int m_iLastHardcoreFF; + int m_iLastOnslaught; }; class InGameMissionPanelFrame : public vgui::Frame diff --git a/game/client/swarm/vgui/nb_mission_summary.cpp b/game/client/swarm/vgui/nb_mission_summary.cpp index 4d5f147c..717f3118 100644 --- a/game/client/swarm/vgui/nb_mission_summary.cpp +++ b/game/client/swarm/vgui/nb_mission_summary.cpp @@ -105,14 +105,31 @@ void CNB_Mission_Summary::OnThink() return; int nSkillLevel = ASWGameRules()->GetSkillLevel(); - if (nSkillLevel == 4) - m_pDifficultyLabel->SetText("#asw_difficulty_insane"); - else if (nSkillLevel == 3) - m_pDifficultyLabel->SetText("#asw_difficulty_hard"); - else if (nSkillLevel == 1) - m_pDifficultyLabel->SetText("#asw_difficulty_easy"); - else - m_pDifficultyLabel->SetText("#asw_difficulty_normal"); + const wchar_t *pDifficulty = NULL; + switch( nSkillLevel ) + { + case 1: pDifficulty = g_pVGuiLocalize->Find( "#asw_difficulty_easy" ); break; + default: + case 2: pDifficulty = g_pVGuiLocalize->Find( "#asw_difficulty_normal" ); break; + case 3: pDifficulty = g_pVGuiLocalize->Find( "#asw_difficulty_hard" ); break; + case 4: pDifficulty = g_pVGuiLocalize->Find( "#asw_difficulty_insane" ); break; + case 5: pDifficulty = g_pVGuiLocalize->Find( "#asw_difficulty_imba" ); break; + } + if ( !pDifficulty ) + { + pDifficulty = L""; + } + + if ( CAlienSwarm::IsOnslaught() ) + { + wchar_t wszText[ 128 ]; + _snwprintf( wszText, sizeof( wszText ), L"%s %s", pDifficulty, g_pVGuiLocalize->FindSafe( "#nb_onslaught_title" ) ); + m_pDifficultyLabel->SetText( wszText ); + } + else + { + m_pDifficultyLabel->SetText( pDifficulty ); + } CASWHudMinimap *pMap = GET_HUDELEMENT( CASWHudMinimap ); if ( pMap ) diff --git a/game/server/ai_basenpc.cpp b/game/server/ai_basenpc.cpp index 1fc945d9..c49ea00c 100644 --- a/game/server/ai_basenpc.cpp +++ b/game/server/ai_basenpc.cpp @@ -642,8 +642,8 @@ void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput ) RemoveActorFromScriptedScenes( this, false /*all scenes*/ ); } - else - DevMsg( "Unexpected double-death-cleanup\n" ); + //else + //DevMsg( "Unexpected double-death-cleanup\n" ); } void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info ) diff --git a/game/server/gameinterface.cpp b/game/server/gameinterface.cpp index 70367a05..da6ea568 100644 --- a/game/server/gameinterface.cpp +++ b/game/server/gameinterface.cpp @@ -1900,13 +1900,15 @@ void CServerGameDLL::GetMatchmakingTags( char *buf, size_t bufSize ) #ifdef INFESTED_DLL extern ConVar asw_marine_ff_absorption; extern ConVar asw_sentry_friendly_fire_scale; + extern ConVar asw_horde_override; + extern ConVar asw_wanderer_override; extern ConVar asw_skill; char * const bufBase = buf; int len = 0; // hardcore friendly fire - if ( asw_marine_ff_absorption.GetInt() != 1 || asw_sentry_friendly_fire_scale.GetFloat() != 0.0f ) + if ( CAlienSwarm::IsHardcoreFF() ) { Q_strncpy( buf, "HardcoreFF,", bufSize ); len = strlen( buf ); @@ -1914,6 +1916,15 @@ void CServerGameDLL::GetMatchmakingTags( char *buf, size_t bufSize ) bufSize -= len; } + // onslaught + if ( CAlienSwarm::IsOnslaught() ) + { + Q_strncpy( buf, "Onslaught,", bufSize ); + len = strlen( buf ); + buf += len; + bufSize -= len; + } + // difficulty level const char *szSkill = "Normal,"; switch( asw_skill.GetInt() ) @@ -1921,6 +1932,7 @@ void CServerGameDLL::GetMatchmakingTags( char *buf, size_t bufSize ) case 1: szSkill = "Easy,"; break; case 3: szSkill = "Hard,"; break; case 4: szSkill = "Insane,"; break; + case 5: szSkill = "Imba,"; break; } Q_strncpy( buf, szSkill, bufSize ); len = strlen( buf ); diff --git a/game/server/swarm/asw_alien.cpp b/game/server/swarm/asw_alien.cpp index 1c6b0f37..3b598677 100644 --- a/game/server/swarm/asw_alien.cpp +++ b/game/server/swarm/asw_alien.cpp @@ -29,6 +29,7 @@ #include "datacache/imdlcache.h" #include "asw_tesla_trap.h" #include "sendprop_priorities.h" +#include "asw_spawn_manager.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -151,6 +152,7 @@ IMPLEMENT_AUTO_LIST( IAlienAutoList ); CASW_Alien::CASW_Alien( void ) : m_BehaviorParms( DefLessFunc( const CUtlSymbol ) ) { + m_bRegisteredAsAwake = false; m_pszAlienModelName = NULL; m_bRunAtChasingPathEnds = true; m_bPerformingZigZag = false; @@ -493,6 +495,32 @@ void CASW_Alien::UpdateSleepState(bool bInPVS) } } } + if ( GetSleepState() == AISS_AWAKE ) + { + if ( !m_bRegisteredAsAwake ) + { + ASWSpawnManager()->OnAlienWokeUp( this ); + m_bRegisteredAsAwake = true; + } + } + else + { + if ( m_bRegisteredAsAwake ) + { + ASWSpawnManager()->OnAlienSleeping( this ); + m_bRegisteredAsAwake = false; + } + } +} + +void CASW_Alien::UpdateOnRemove() +{ + if ( m_bRegisteredAsAwake ) + { + m_bRegisteredAsAwake = false; + ASWSpawnManager()->OnAlienSleeping( this ); + } + BaseClass::UpdateOnRemove(); } void CASW_Alien::SetDistSwarmSense( float flDistSense ) diff --git a/game/server/swarm/asw_alien.h b/game/server/swarm/asw_alien.h index 2176bd25..10963c54 100644 --- a/game/server/swarm/asw_alien.h +++ b/game/server/swarm/asw_alien.h @@ -93,6 +93,8 @@ public: // make the aliens wake up when a marine gets within a certain distance void UpdateSleepState( bool bInPVS ); void UpdateEfficiency( bool bInPVS ); + virtual void UpdateOnRemove(); + bool m_bRegisteredAsAwake; float m_fLastSleepCheckTime; bool m_bVisibleWhenAsleep; diff --git a/game/server/swarm/asw_base_spawner.cpp b/game/server/swarm/asw_base_spawner.cpp index e34b18b2..14db72e5 100644 --- a/game/server/swarm/asw_base_spawner.cpp +++ b/game/server/swarm/asw_base_spawner.cpp @@ -316,10 +316,13 @@ CBaseEntity* CASW_Base_Spawner::GetAlienOrderTarget() bool CASW_Base_Spawner::IsValidOnThisSkillLevel() { - if (m_iMinSkillLevel > 0 && ASWGameRules()->GetSkillLevel() < m_iMinSkillLevel) + // treat difficulty 5 and 4 as the same + int nSkillLevel = ASWGameRules()->GetSkillLevel(); + nSkillLevel = clamp( nSkillLevel, 1, 4 ); + if (m_iMinSkillLevel > 0 && nSkillLevel < m_iMinSkillLevel) return false; if (m_iMaxSkillLevel > 0 && m_iMaxSkillLevel < 10 - && ASWGameRules()->GetSkillLevel() > m_iMaxSkillLevel) + && nSkillLevel > m_iMaxSkillLevel) return false; return true; } diff --git a/game/server/swarm/asw_buzzer.cpp b/game/server/swarm/asw_buzzer.cpp index 6adc9222..489f1fa5 100644 --- a/game/server/swarm/asw_buzzer.cpp +++ b/game/server/swarm/asw_buzzer.cpp @@ -2745,7 +2745,9 @@ float CASW_Buzzer::GetMaxEnginePower() if (ASWGameRules()) { - return ASWGameRules()->GetSkillLevel() + 0.1f; + int nSkillLevel = ASWGameRules()->GetSkillLevel(); + nSkillLevel = clamp( nSkillLevel, 1, 4 ); + return nSkillLevel + 0.1f; } return 2.0f; } diff --git a/game/server/swarm/asw_director.cpp b/game/server/swarm/asw_director.cpp index bb4dad51..5b766771 100644 --- a/game/server/swarm/asw_director.cpp +++ b/game/server/swarm/asw_director.cpp @@ -22,12 +22,12 @@ ConVar asw_director_debug("asw_director_debug", "0", FCVAR_CHEAT, "Displays dire extern ConVar asw_intensity_far_range; extern ConVar asw_spawning_enabled; -ConVar asw_horde_override( "asw_horde_override", "0", FCVAR_NONE, "Forces hordes to spawn" ); -ConVar asw_wanderer_override( "asw_wanderer_override", "0", FCVAR_NONE, "Forces wanderers to spawn" ); -ConVar asw_horde_interval_min("asw_horde_interval_min", "40", FCVAR_CHEAT, "Min time between hordes" ); -ConVar asw_horde_interval_max("asw_horde_interval_max", "60", FCVAR_CHEAT, "Min time between hordes" ); +extern ConVar asw_horde_override; +extern ConVar asw_wanderer_override; +ConVar asw_horde_interval_min("asw_horde_interval_min", "45", FCVAR_CHEAT, "Min time between hordes" ); +ConVar asw_horde_interval_max("asw_horde_interval_max", "65", FCVAR_CHEAT, "Min time between hordes" ); ConVar asw_horde_size_min("asw_horde_size_min", "9", FCVAR_CHEAT, "Min horde size" ); -ConVar asw_horde_size_max("asw_horde_size_max", "12", FCVAR_CHEAT, "Max horde size" ); +ConVar asw_horde_size_max("asw_horde_size_max", "14", FCVAR_CHEAT, "Max horde size" ); ConVar asw_director_relaxed_min_time("asw_director_relaxed_min_time", "25", FCVAR_CHEAT, "Min time that director stops spawning aliens"); ConVar asw_director_relaxed_max_time("asw_director_relaxed_max_time", "40", FCVAR_CHEAT, "Max time that director stops spawning aliens"); @@ -102,6 +102,11 @@ void CASW_Director::LevelInitPreEntity() void CASW_Director::LevelInitPostEntity() { Init(); + + if ( ASWSpawnManager() ) + { + ASWSpawnManager()->LevelInitPostEntity(); + } } void CASW_Director::FrameUpdatePreEntityThink() @@ -250,60 +255,76 @@ void CASW_Director::MarineTookDamage( CASW_Marine *pMarine, const CTakeDamageInf void CASW_Director::UpdateHorde() { - bool bHordesEnabled = m_bHordesEnabled || asw_horde_override.GetBool(); - if ( !bHordesEnabled || !ASWSpawnManager() ) - return; - if ( asw_director_debug.GetInt() > 0 ) { if ( m_bHordeInProgress ) { engine->Con_NPrintf( 11, "Horde in progress. Left to spawn = %d", ASWSpawnManager()->GetHordeToSpawn() ); } - else - { - engine->Con_NPrintf( 11, "Next Horde due: %f", m_HordeTimer.GetRemainingTime() ); - } + engine->Con_NPrintf( 12, "Next Horde due: %f", m_HordeTimer.GetRemainingTime() ); + + engine->Con_NPrintf( 15, "Awake aliens: %d\n", ASWSpawnManager()->GetAwakeAliens() ); + engine->Con_NPrintf( 16, "Awake drones: %d\n", ASWSpawnManager()->GetAwakeDrones() ); } - if ( !m_bHordeInProgress ) + bool bHordesEnabled = m_bHordesEnabled || asw_horde_override.GetBool(); + if ( !bHordesEnabled || !ASWSpawnManager() ) + return; + + if ( !m_HordeTimer.HasStarted() ) { - if ( !m_HordeTimer.HasStarted() ) + float flDuration = RandomFloat( asw_horde_interval_min.GetFloat(), asw_horde_interval_max.GetFloat() ); + if ( m_bFinale ) { - float flDuration = RandomFloat( asw_horde_interval_min.GetFloat(), asw_horde_interval_max.GetFloat() ); - if ( m_bFinale ) - { - flDuration = RandomFloat( 5.0f, 10.0f ); - } - Msg( "Will be spawning a horde in %f seconds\n", flDuration ); - m_HordeTimer.Start( flDuration ); + flDuration = RandomFloat( 5.0f, 10.0f ); } - else if ( m_HordeTimer.IsElapsed() ) + if ( asw_director_debug.GetBool() ) + { + Msg( "Will be spawning a horde in %f seconds\n", flDuration ); + } + m_HordeTimer.Start( flDuration ); + } + else if ( m_HordeTimer.IsElapsed() ) + { + if ( ASWSpawnManager()->GetAwakeDrones() < 25 ) { int iNumAliens = RandomInt( asw_horde_size_min.GetInt(), asw_horde_size_max.GetInt() ); + if ( ASWSpawnManager()->AddHorde( iNumAliens ) ) { - Msg("Created horde of size %d\n", iNumAliens); + if ( asw_director_debug.GetBool() ) + { + Msg("Created horde of size %d\n", iNumAliens); + } m_bHordeInProgress = true; if ( ASWGameRules() ) { ASWGameRules()->BroadcastSound( "Spawner.Horde" ); } + m_HordeTimer.Invalidate(); } else { - m_HordeTimer.Invalidate(); + // if we failed to find a horde position, try again shortly. + m_HordeTimer.Start( RandomFloat( 10.0f, 16.0f ) ); } } + else + { + // if there are currently too many awake aliens, then wait 10 seconds before trying again + m_HordeTimer.Start( 10.0f ); + } } } void CASW_Director::OnHordeFinishedSpawning() { - Msg("Horde finishes spawning\n"); + if ( asw_director_debug.GetBool() ) + { + Msg("Horde finishes spawning\n"); + } m_bHordeInProgress = false; - m_HordeTimer.Invalidate(); } void CASW_Director::UpdateSpawningState() @@ -418,7 +439,10 @@ void CASW_Director::UpdateWanderers() if ( ASWSpawnManager() ) { - ASWSpawnManager()->AddAlien(); + if ( ASWSpawnManager()->GetAwakeDrones() < 20 ) + { + ASWSpawnManager()->AddAlien(); + } } } } @@ -496,4 +520,36 @@ void CASW_Director::UpdateMarineInsideEscapeRoom( CASW_Marine *pMarine ) } m_bFiredEscapeRoom = true; +} + +void CASW_Director::OnMissionStarted() +{ + // if we have wanders turned on, spawn a couple of encounters + if ( asw_wanderer_override.GetBool() && ASWGameRules() ) + { + ASWSpawnManager()->SpawnRandomShieldbug(); + + int nParasites = 1; + switch( ASWGameRules()->GetSkillLevel() ) + { + case 1: nParasites = RandomInt( 4, 6 ); break; + default: + case 2: nParasites = RandomInt( 4, 6 ); break; + case 3: nParasites = RandomInt( 5, 7 ); break; + case 4: nParasites = RandomInt( 5, 9 ); break; + case 5: nParasites = RandomInt( 5, 10 ); break; + } + while ( nParasites > 0 ) + { + int nParasitesInThisPack = RandomInt( 3, 6 ); + if ( ASWSpawnManager()->SpawnRandomParasitePack( nParasitesInThisPack ) ) + { + nParasites -= nParasitesInThisPack; + } + else + { + break; + } + } + } } \ No newline at end of file diff --git a/game/server/swarm/asw_director.h b/game/server/swarm/asw_director.h index 7e43d895..a547bcaf 100644 --- a/game/server/swarm/asw_director.h +++ b/game/server/swarm/asw_director.h @@ -68,6 +68,8 @@ public: void OnMarineStartedHack( CASW_Marine *pMarine, CBaseEntity *pComputer ); void UpdateMarineInsideEscapeRoom( CASW_Marine *pMarine ); + void OnMissionStarted(); + // Spawning hordes of aliens void OnHordeFinishedSpawning(); diff --git a/game/server/swarm/asw_drone_advanced.cpp b/game/server/swarm/asw_drone_advanced.cpp index 5e6b6ba1..553ab346 100644 --- a/game/server/swarm/asw_drone_advanced.cpp +++ b/game/server/swarm/asw_drone_advanced.cpp @@ -306,6 +306,7 @@ float CASW_Drone_Advanced::GetIdealSpeed() const switch (ASWGameRules()->GetSkillLevel()) { + case 5: boost *= asw_alien_speed_scale_insane.GetFloat(); break; case 4: boost *= asw_alien_speed_scale_insane.GetFloat(); break; case 3: boost *= asw_alien_speed_scale_hard.GetFloat(); break; case 2: boost *= asw_alien_speed_scale_normal.GetFloat(); break; diff --git a/game/server/swarm/asw_egg.cpp b/game/server/swarm/asw_egg.cpp index 1ebfbab3..4eeee2a1 100644 --- a/game/server/swarm/asw_egg.cpp +++ b/game/server/swarm/asw_egg.cpp @@ -297,7 +297,7 @@ void CASW_Egg::AnimThink( void ) } else { - if (ASWGameRules() && ASWGameRules()->GetSkillLevel() == 4 ) + if (ASWGameRules() && ASWGameRules()->GetSkillLevel() >= 4 ) { m_fNextMarineCheckTime = gpGlobals->curtime + 1.5f; } diff --git a/game/server/swarm/asw_gameinterface.cpp b/game/server/swarm/asw_gameinterface.cpp index 1cb4d2b0..0ae3f401 100644 --- a/game/server/swarm/asw_gameinterface.cpp +++ b/game/server/swarm/asw_gameinterface.cpp @@ -32,6 +32,9 @@ void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities ) { + // precache even if not in the level, for onslaught mode + UTIL_PrecacheOther( "asw_shieldbug" ); + UTIL_PrecacheOther( "asw_parasite" ); } bool g_bOfflineGame = false; @@ -122,6 +125,38 @@ void CServerGameDLL::ApplyGameSettings( KeyValues *pKV ) { asw_skill.SetValue( 4 ); } + else if ( !Q_stricmp( szDifficulty, "imba" ) ) + { + asw_skill.SetValue( 5 ); + } + + extern ConVar asw_sentry_friendly_fire_scale; + extern ConVar asw_marine_ff_absorption; + int nHardcoreFF = pKV->GetInt( "game/hardcoreFF", 0 ); + if ( nHardcoreFF == 1 ) + { + asw_sentry_friendly_fire_scale.SetValue( 1.0f ); + asw_marine_ff_absorption.SetValue( 0 ); + } + else + { + asw_sentry_friendly_fire_scale.SetValue( 0.0f ); + asw_marine_ff_absorption.SetValue( 1 ); + } + + extern ConVar asw_horde_override; + extern ConVar asw_wanderer_override; + int nOnslaught = pKV->GetInt( "game/onslaught", 0 ); + if ( nOnslaught == 1 ) + { + asw_horde_override.SetValue( 1 ); + asw_wanderer_override.SetValue( 1 ); + } + else + { + asw_horde_override.SetValue( 0 ); + asw_wanderer_override.SetValue( 0 ); + } char const *szMapCommand = pKV->GetString( "map/mapcommand", "map" ); diff --git a/game/server/swarm/asw_hack_computer.cpp b/game/server/swarm/asw_hack_computer.cpp index cecdf0fd..6d53c35c 100644 --- a/game/server/swarm/asw_hack_computer.cpp +++ b/game/server/swarm/asw_hack_computer.cpp @@ -177,7 +177,7 @@ void CASW_Hack_Computer::SelectHackOption(int i) diff_factor = 1.55f; else if (iSkill == 3) diff_factor = 1.50f; - else if (iSkill == 4) + else if (iSkill >= 4) diff_factor = 1.45f; // estimate the time for a fast hack // try, time taken for each column to rotate through twice diff --git a/game/server/swarm/asw_map_scores.cpp b/game/server/swarm/asw_map_scores.cpp index 037c4c70..a4cfb01a 100644 --- a/game/server/swarm/asw_map_scores.cpp +++ b/game/server/swarm/asw_map_scores.cpp @@ -144,6 +144,7 @@ const char* CASW_Map_Scores::GetBestKillsKeyName(int iSkill) case 1: return "BestKillsEasy"; break; case 3: return "BestKillsHard"; break; case 4: return "BestKillsInsane"; break; + case 5: return "BestKillsImba"; break; default: break; } return "BestKillsNormal"; @@ -156,6 +157,7 @@ const char* CASW_Map_Scores::GetBestTimeKeyName(int iSkill) case 1: return "BestTimeEasy"; break; case 3: return "BestTimeHard"; break; case 4: return "BestTimeInsane"; break; + case 5: return "BestTimeImba"; break; default: break; } return "BestTimeNormal"; diff --git a/game/server/swarm/asw_marine.cpp b/game/server/swarm/asw_marine.cpp index 67354c1e..f7751590 100644 --- a/game/server/swarm/asw_marine.cpp +++ b/game/server/swarm/asw_marine.cpp @@ -345,13 +345,6 @@ BEGIN_DATADESC( CASW_Marine ) DEFINE_FIELD( m_bPowerupExpires, FIELD_BOOLEAN ), END_DATADESC() -void UpdateMatchmakingTags(); - -static void FriendlyFireCallback( IConVar *pConVar, const char *pOldValue, float flOldValue ) -{ - UpdateMatchmakingTags(); -} - extern ConVar weapon_showproficiency; extern ConVar asw_leadership_radius; extern ConVar asw_buzzer_poison_duration; @@ -381,7 +374,6 @@ ConVar asw_marine_ff("asw_marine_ff", "1", FCVAR_CHEAT, "Marine friendly fire se ConVar asw_marine_ff_guard_time("asw_marine_ff_guard_time", "5.0", FCVAR_CHEAT, "Amount of time firing is disabled for when activating friendly fire guard"); ConVar asw_marine_ff_dmg_base("asw_marine_ff_dmg_base", "1.0", FCVAR_CHEAT, "Amount of friendly fire damage on mission difficulty 5"); ConVar asw_marine_ff_dmg_step("asw_marine_ff_dmg_step", "0.2", FCVAR_CHEAT, "Amount friendly fire damage is modified per mission difficuly level away from 5"); -ConVar asw_marine_ff_absorption("asw_marine_ff_absorption", "1", FCVAR_NONE, "Friendly fire absorption style (0=none 1=ramp up 2=ramp down)", FriendlyFireCallback ); ConVar asw_marine_ff_absorption_decay_rate("asw_marine_ff_absorption_decay_rate", "0.33f", FCVAR_CHEAT, "Rate of FF absorption decay"); ConVar asw_marine_ff_absorption_build_rate("asw_marine_ff_absorption_build_rate", "0.25f", FCVAR_CHEAT, "Rate of FF absorption decay build up when being shot by friendlies"); ConVar asw_marine_burn_time_easy("asw_marine_burn_time_easy", "6", FCVAR_CHEAT, "Amount of time marine burns for when ignited on easy difficulty"); @@ -395,7 +387,8 @@ ConVar asw_marine_special_idle_chatter_chance("asw_marine_special_idle_chatter_c ConVar asw_force_ai_fire("asw_force_ai_fire", "0", FCVAR_CHEAT, "Forces all AI marines to fire constantly"); ConVar asw_realistic_death_chatter("asw_realistic_death_chatter", "0", FCVAR_NONE, "If true, only 1 nearby marine will shout about marine deaths"); ConVar asw_god( "asw_god", "0", FCVAR_CHEAT, "Set to 1 to make marines invulnerable" ); -ConVar asw_sentry_friendly_fire_scale( "asw_sentry_friendly_fire_scale", "0", FCVAR_NONE, "Damage scale for sentry gun friendly fire", FriendlyFireCallback ); +extern ConVar asw_sentry_friendly_fire_scale; +extern ConVar asw_marine_ff_absorption; ConVar asw_movement_direction_tolerance( "asw_movement_direction_tolerance", "30.0", FCVAR_CHEAT ); ConVar asw_movement_direction_interval( "asw_movement_direction_interval", "0.5", FCVAR_CHEAT ); @@ -4058,7 +4051,7 @@ void CASW_Marine::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity * flFlameLifetime *= asw_marine_burn_time_normal.GetFloat(); else if (iDiff == 3) flFlameLifetime *= asw_marine_burn_time_hard.GetFloat(); - else if (iDiff == 4) + else if (iDiff == 4 || iDiff == 5) flFlameLifetime *= asw_marine_burn_time_insane.GetFloat(); if ( m_flFirstBurnTime == 0 ) @@ -4127,7 +4120,8 @@ bool CASW_Marine::AllowedToIgnite( void ) if ( m_iJumpJetting.Get() != 0 ) return false; - if ( m_flFirstBurnTime > 0 && (gpGlobals->curtime - m_flFirstBurnTime) >= asw_marine_time_until_ignite.GetFloat() ) + float flBurnTime = ( asw_marine_ff_absorption.GetInt() > 0 ) ? asw_marine_time_until_ignite.GetFloat() : 0.2f; + if ( m_flFirstBurnTime > 0 && (gpGlobals->curtime - m_flFirstBurnTime) >= flBurnTime ) return true; // don't ignite, but play a flesh burn sound if we aren't on fire already diff --git a/game/server/swarm/asw_marine_schedule.cpp b/game/server/swarm/asw_marine_schedule.cpp index d9583a57..a08ba2bb 100644 --- a/game/server/swarm/asw_marine_schedule.cpp +++ b/game/server/swarm/asw_marine_schedule.cpp @@ -169,6 +169,7 @@ float CASW_Marine::GetCloseCombatSightRange() case 2: return ASW_CLOSE_COMBAT_SIGHT_RANGE_NORMAL; break; case 3: return ASW_CLOSE_COMBAT_SIGHT_RANGE_HARD; break; case 4: + case 5: default: return ASW_CLOSE_COMBAT_SIGHT_RANGE_INSANE; break; } } diff --git a/game/server/swarm/asw_parasite.cpp b/game/server/swarm/asw_parasite.cpp index f3b8c982..628900f1 100644 --- a/game/server/swarm/asw_parasite.cpp +++ b/game/server/swarm/asw_parasite.cpp @@ -1089,6 +1089,7 @@ void CASW_Parasite::UpdatePlaybackRate() float boost = asw_parasite_speedboost.GetFloat(); switch (ASWGameRules()->GetSkillLevel()) { + case 5: boost *= asw_alien_speed_scale_insane.GetFloat(); break; case 4: boost *= asw_alien_speed_scale_insane.GetFloat(); break; case 3: boost *= asw_alien_speed_scale_hard.GetFloat(); break; case 2: boost *= asw_alien_speed_scale_normal.GetFloat(); break; diff --git a/game/server/swarm/asw_player.cpp b/game/server/swarm/asw_player.cpp index 1aa10156..f74ac4d5 100644 --- a/game/server/swarm/asw_player.cpp +++ b/game/server/swarm/asw_player.cpp @@ -299,7 +299,7 @@ void ASW_DrawAwakeAI() int iVEfficient = 0; int iSEfficient = 0; int iNormal = 0; - int nprintIndex = 0; + int nprintIndex = 18; engine->Con_NPrintf( nprintIndex, "AI (awake/asleep) (normal/efficient/very efficient/super efficient/dormant)"); nprintIndex++; engine->Con_NPrintf( nprintIndex, "================================"); @@ -789,6 +789,76 @@ bool CASW_Player::ClientCommand( const CCommand &args ) ASWGameRules()->RequestSkillDown(this); return true; } + else if ( FStrEq( pcmd, "cl_hardcore_ff") ) + { + if ( args.ArgC() < 2 ) + { + Warning("Player sent a bad cl_hardcore_ff command\n"); + return false; + } + + if ( ASWGameResource() && ASWGameResource()->GetLeader() == this ) + { + bool bOldHardcoreMode = CAlienSwarm::IsHardcoreFF(); + int nHardcore = atoi( args[1] ); + nHardcore = clamp( nHardcore, 0, 1 ); + + extern ConVar asw_sentry_friendly_fire_scale; + extern ConVar asw_marine_ff_absorption; + asw_sentry_friendly_fire_scale.SetValue( nHardcore ); + asw_marine_ff_absorption.SetValue( 1 - nHardcore ); + + if ( CAlienSwarm::IsHardcoreFF() != bOldHardcoreMode ) + { + CReliableBroadcastRecipientFilter filter; + filter.RemoveRecipient( this ); // notify everyone except the player changing the setting + if ( nHardcore > 0 ) + { + UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_enabled_hardcoreff", GetPlayerName() ); + } + else + { + UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_disabled_hardcoreff", GetPlayerName() ); + } + } + } + return true; + } + else if ( FStrEq( pcmd, "cl_onslaught") ) + { + if ( args.ArgC() < 2 ) + { + Warning("Player sent a bad cl_onslaught command\n"); + return false; + } + + if ( ASWGameResource() && ASWGameResource()->GetLeader() == this ) + { + bool bOldOnslaughtMode = CAlienSwarm::IsOnslaught(); + int nOnslaught = atoi( args[1] ); + nOnslaught = clamp( nOnslaught, 0, 1 ); + + extern ConVar asw_horde_override; + extern ConVar asw_wanderer_override; + asw_horde_override.SetValue( nOnslaught ); + asw_wanderer_override.SetValue( nOnslaught ); + + if ( CAlienSwarm::IsOnslaught() != bOldOnslaughtMode ) + { + CReliableBroadcastRecipientFilter filter; + filter.RemoveRecipient( this ); // notify everyone except the player changing the setting + if ( nOnslaught > 0 ) + { + UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_enabled_onslaught", GetPlayerName() ); + } + else + { + UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_disabled_onslaught", GetPlayerName() ); + } + } + } + return true; + } else if ( FStrEq( pcmd, "cl_fixedskills") ) { /* diff --git a/game/server/swarm/asw_queen.cpp b/game/server/swarm/asw_queen.cpp index 997f1673..f5cfc034 100644 --- a/game/server/swarm/asw_queen.cpp +++ b/game/server/swarm/asw_queen.cpp @@ -1418,6 +1418,7 @@ void CASW_Queen::SetHealthByDifficultyLevel() case 2: health = asw_queen_health_normal.GetInt(); break; case 3: health = asw_queen_health_hard.GetInt(); break; case 4: health = asw_queen_health_insane.GetInt(); break; + case 5: health = asw_queen_health_insane.GetInt(); break; default: 5000; } } diff --git a/game/server/swarm/asw_spawn_manager.cpp b/game/server/swarm/asw_spawn_manager.cpp index 7dd43257..9a3ca453 100644 --- a/game/server/swarm/asw_spawn_manager.cpp +++ b/game/server/swarm/asw_spawn_manager.cpp @@ -15,6 +15,8 @@ #include "asw_objective_escape.h" #include "triggers.h" #include "datacache/imdlcache.h" +#include "ai_link.h" +#include "asw_alien.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -33,12 +35,10 @@ ConVar asw_batch_interval("asw_batch_interval", "5", FCVAR_CHEAT, "Time between ConVar asw_candidate_interval("asw_candidate_interval", "1.0", FCVAR_CHEAT, "Interval between updating candidate spawning nodes"); ConVar asw_horde_class( "asw_horde_class", "asw_drone", FCVAR_CHEAT, "Alien class used when spawning hordes" ); -// TODO: Notify the director when a horde is all killed? -// - currently you could try to spawn a 2nd horde while the first is still sitting out in the world - CASW_Spawn_Manager::CASW_Spawn_Manager() { - + m_nAwakeAliens = 0; + m_nAwakeDrones = 0; } CASW_Spawn_Manager::~CASW_Spawn_Manager() @@ -87,6 +87,8 @@ ASW_Alien_Class_Entry* CASW_Spawn_Manager::GetAlienClass( int i ) void CASW_Spawn_Manager::LevelInitPreEntity() { + m_nAwakeAliens = 0; + m_nAwakeDrones = 0; // init alien classes for ( int i = 0; i < GetNumAlienClasses(); i++ ) { @@ -109,6 +111,24 @@ void CASW_Spawn_Manager::LevelInitPostEntity() FindEscapeTriggers(); } +void CASW_Spawn_Manager::OnAlienWokeUp( CASW_Alien *pAlien ) +{ + m_nAwakeAliens++; + if ( pAlien && pAlien->Classify() == CLASS_ASW_DRONE ) + { + m_nAwakeDrones++; + } +} + +void CASW_Spawn_Manager::OnAlienSleeping( CASW_Alien *pAlien ) +{ + m_nAwakeAliens--; + if ( pAlien && pAlien->Classify() == CLASS_ASW_DRONE ) + { + m_nAwakeDrones--; + } +} + // finds all trigger_multiples linked to asw_objective_escape entities void CASW_Spawn_Manager::FindEscapeTriggers() { @@ -180,7 +200,7 @@ void CASW_Spawn_Manager::Update() if ( m_vecHordePosition != vec3_origin && ( !m_batchInterval.HasStarted() || m_batchInterval.IsElapsed() ) ) { int iToSpawn = MIN( m_iHordeToSpawn, asw_max_alien_batch.GetInt() ); - int iSpawned = SpawnAlienBatch( asw_horde_class.GetString(), iToSpawn, m_vecHordePosition, m_angHordeAngle, MARINE_NEAR_DISTANCE ); + int iSpawned = SpawnAlienBatch( asw_horde_class.GetString(), iToSpawn, m_vecHordePosition, m_angHordeAngle, 0 ); m_iHordeToSpawn -= iSpawned; if ( m_iHordeToSpawn <= 0 ) { @@ -189,6 +209,10 @@ void CASW_Spawn_Manager::Update() } else if ( iSpawned == 0 ) // if we failed to spawn any aliens, then try to find a new horde location { + if ( asw_director_debug.GetBool() ) + { + Msg( "Horde failed to spawn any aliens, trying new horde position.\n" ); + } if ( !FindHordePosition() ) // if we failed to find a new location, just abort this horde { m_iHordeToSpawn = 0; @@ -198,6 +222,17 @@ void CASW_Spawn_Manager::Update() } m_batchInterval.Start( asw_batch_interval.GetFloat() ); } + else if ( m_vecHordePosition == vec3_origin ) + { + Msg( "Warning: Had horde to spawn but no position, clearing.\n" ); + m_iHordeToSpawn = 0; + ASWDirector()->OnHordeFinishedSpawning(); + } + } + + if ( asw_director_debug.GetBool() ) + { + engine->Con_NPrintf( 14, "SM: Batch interval: %f pos = %f %f %f\n", m_batchInterval.HasStarted() ? m_batchInterval.GetRemainingTime() : -1, VectorExpand( m_vecHordePosition ) ); } if ( m_iAliensToSpawn > 0 ) @@ -209,6 +244,10 @@ void CASW_Spawn_Manager::Update() void CASW_Spawn_Manager::AddAlien() { + // don't stock up more than 10 wanderers at once + if ( m_iAliensToSpawn > 10 ) + return; + m_iAliensToSpawn++; } @@ -236,7 +275,7 @@ bool CASW_Spawn_Manager::SpawnAlientAtRandomNode() Vector vecMins, vecMaxs; GetAlienBounds( szAlienClass, vecMins, vecMaxs ); - int iMaxTries = 10; + int iMaxTries = 1; for ( int i=0 ; iBuildRoute( pNode->GetPosition( CANDIDATE_ALIEN_HULL ), pMarine->GetAbsOrigin(), NULL, 100 ); if ( !pRoute ) + { + if ( asw_director_debug.GetBool() ) + { + NDebugOverlay::Cross3D( pNode->GetOrigin(), 10.0f, 255, 128, 0, true, 20.0f ); + } continue; + } if ( bNorth && UTIL_ASW_DoorBlockingRoute( pRoute, true ) ) { @@ -265,9 +310,27 @@ bool CASW_Spawn_Manager::SpawnAlientAtRandomNode() { if ( SpawnAlienAt( szAlienClass, vecSpawnPos, vec3_angle ) ) { + if ( asw_director_debug.GetBool() ) + { + NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 255, 255, true, 20.0f ); + float flDist; + CASW_Marine *pMarine = UTIL_ASW_NearestMarine( vecSpawnPos, flDist ); + if ( pMarine ) + { + NDebugOverlay::Line( pMarine->GetAbsOrigin(), vecSpawnPos, 64, 64, 64, true, 60.0f ); + } + } + DeleteRoute( pRoute ); return true; } } + else + { + if ( asw_director_debug.GetBool() ) + { + NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 0, 0, true, 20.0f ); + } + } DeleteRoute( pRoute ); } return false; @@ -288,7 +351,13 @@ bool CASW_Spawn_Manager::AddHorde( int iHordeSize ) { if ( asw_director_debug.GetBool() ) { - NDebugOverlay::Cross3D( m_vecHordePosition, 50.0f, 255, 128, 0, true, 40.0f ); + NDebugOverlay::Cross3D( m_vecHordePosition, 50.0f, 255, 128, 0, true, 60.0f ); + float flDist; + CASW_Marine *pMarine = UTIL_ASW_NearestMarine( m_vecHordePosition, flDist ); + if ( pMarine ) + { + NDebugOverlay::Line( pMarine->GetAbsOrigin(), m_vecHordePosition, 255, 128, 0, true, 60.0f ); + } } } } @@ -378,11 +447,19 @@ void CASW_Spawn_Manager::UpdateCandidateNodes() if ( vecPos.y >= vecSouthMarine.y ) { + if ( asw_director_debug.GetInt() == 3 ) + { + NDebugOverlay::Box( vecPos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 32, 32, 128, 10, 60.0f ); + } m_northCandidateNodes.AddToTail( i ); } if ( vecPos.y <= vecNorthMarine.y ) { m_southCandidateNodes.AddToTail( i ); + if ( asw_director_debug.GetInt() == 3 ) + { + NDebugOverlay::Box( vecPos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 128, 32, 32, 10, 60.0f ); + } } } } @@ -410,9 +487,15 @@ bool CASW_Spawn_Manager::FindHordePosition() CUtlVector &candidateNodes = bNorth ? m_northCandidateNodes : m_southCandidateNodes; if ( candidateNodes.Count() <= 0 ) + { + if ( asw_director_debug.GetBool() ) + { + Msg( " Failed to find horde pos as there are no candidate nodes\n" ); + } return false; + } - int iMaxTries = 10; + int iMaxTries = 3; for ( int i=0 ; i(UTIL_ASW_NearestMarine( pNode->GetPosition( CANDIDATE_ALIEN_HULL ), flDistance )); if ( !pMarine ) + { + if ( asw_director_debug.GetBool() ) + { + Msg( " Failed to find horde pos as there is no nearest marine\n" ); + } return false; + } // check if there's a route from this node to the marine(s) AI_Waypoint_t *pRoute = ASWPathUtils()->BuildRoute( pNode->GetPosition( CANDIDATE_ALIEN_HULL ), pMarine->GetAbsOrigin(), NULL, 100 ); if ( !pRoute ) + { + if ( asw_director_debug.GetInt() >= 2 ) + { + Msg( " Discarding horde node %d as there's no route.\n", iChosen ); + } continue; + } if ( bNorth && UTIL_ASW_DoorBlockingRoute( pRoute, true ) ) { + if ( asw_director_debug.GetInt() >= 2 ) + { + Msg( " Discarding horde node %d as there's a door in the way.\n", iChosen ); + } DeleteRoute( pRoute ); continue; } m_vecHordePosition = pNode->GetPosition( CANDIDATE_ALIEN_HULL ) + Vector( 0, 0, 32 ); + + // spawn facing the nearest marine + Vector vecDir = pMarine->GetAbsOrigin() - m_vecHordePosition; + vecDir.z = 0; + vecDir.NormalizeInPlace(); + VectorAngles( vecDir, m_angHordeAngle ); + + if ( asw_director_debug.GetInt() >= 2 ) + { + Msg( " Accepting horde node %d.\n", iChosen ); + } DeleteRoute( pRoute ); return true; } + if ( asw_director_debug.GetBool() ) + { + Msg( " Failed to find horde pos as we tried 3 times to build routes to possible locations, but failed\n" ); + } + return false; } @@ -485,8 +600,9 @@ bool CASW_Spawn_Manager::GetAlienBounds( string_t iszAlienClass, Vector &vecMins } // spawn a group of aliens at the target point -int CASW_Spawn_Manager::SpawnAlienBatch( const char* szAlienClass, int iNumAliens, const Vector &vecPosition, const QAngle &angle, float flMarinesBeyondDist ) +int CASW_Spawn_Manager::SpawnAlienBatch( const char* szAlienClass, int iNumAliens, const Vector &vecPosition, const QAngle &angFacing, float flMarinesBeyondDist ) { + int iSpawned = 0; bool bCheckGround = true; Vector vecMins = NAI_Hull::Mins(HULL_MEDIUMBIG); @@ -499,7 +615,7 @@ int CASW_Spawn_Manager::SpawnAlienBatch( const char* szAlienClass, int iNumAlien // spawn one in the middle if ( ValidSpawnPoint( vecPosition, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) ) { - if ( SpawnAlienAt( szAlienClass, vecPosition, angle ) ) + if ( SpawnAlienAt( szAlienClass, vecPosition, angFacing ) ) iSpawned++; } @@ -507,6 +623,8 @@ int CASW_Spawn_Manager::SpawnAlienBatch( const char* szAlienClass, int iNumAlien Vector vecNewPos = vecPosition; for ( int i=1; i<=5 && iSpawned < iNumAliens; i++ ) { + QAngle angle = angFacing; + angle[YAW] += RandomFloat( -20, 20 ); // spawn aliens along top of box for ( int x=-i; x<=i && iSpawned < iNumAliens; x++ ) { @@ -591,6 +709,14 @@ CBaseEntity* CASW_Spawn_Manager::SpawnAlienAt(const char* szAlienClass, const Ve return NULL; } + // have drones unburrow by default, so we don't worry so much about them spawning onscreen + if ( !Q_strcmp( szAlienClass, "asw_drone" ) ) + { + pSpawnable->StartBurrowed(); + pSpawnable->SetUnburrowIdleActivity( NULL_STRING ); + pSpawnable->SetUnburrowActivity( NULL_STRING ); + } + DispatchSpawn( pEntity ); pEntity->Activate(); @@ -663,6 +789,308 @@ void CASW_Spawn_Manager::DeleteRoute( AI_Waypoint_t *pWaypointList ) } } +bool CASW_Spawn_Manager::SpawnRandomShieldbug() +{ + int iNumNodes = g_pBigAINet->NumNodes(); + if ( iNumNodes < 6 ) + return false; + + int nHull = HULL_WIDE_SHORT; + CUtlVector aAreas; + for ( int i = 0; i < 6; i++ ) + { + CAI_Node *pNode = NULL; + int nTries = 0; + while ( nTries < 5 && ( !pNode || pNode->GetType() != NODE_GROUND ) ) + { + pNode = g_pBigAINet->GetNode( RandomInt( 0, iNumNodes ) ); + nTries++; + } + + if ( pNode ) + { + CASW_Open_Area *pArea = FindNearbyOpenArea( pNode->GetOrigin(), HULL_MEDIUMBIG ); + if ( pArea && pArea->m_nTotalLinks > 30 ) + { + // test if there's room to spawn a shieldbug at that spot + if ( ValidSpawnPoint( pArea->m_pNode->GetPosition( nHull ), NAI_Hull::Mins( nHull ), NAI_Hull::Maxs( nHull ), true, false ) ) + { + aAreas.AddToTail( pArea ); + } + else + { + delete pArea; + } + } + } + // stop searching once we have 3 acceptable candidates + if ( aAreas.Count() >= 3 ) + break; + } + + // find area with the highest connectivity + CASW_Open_Area *pBestArea = NULL; + for ( int i = 0; i < aAreas.Count(); i++ ) + { + CASW_Open_Area *pArea = aAreas[i]; + if ( !pBestArea || pArea->m_nTotalLinks > pBestArea->m_nTotalLinks ) + { + pBestArea = pArea; + } + } + + if ( pBestArea ) + { + CBaseEntity *pAlien = SpawnAlienAt( "asw_shieldbug", pBestArea->m_pNode->GetPosition( nHull ), RandomAngle( 0, 360 ) ); + IASW_Spawnable_NPC *pSpawnable = dynamic_cast( pAlien ); + if ( pSpawnable ) + { + pSpawnable->SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL); + } + aAreas.PurgeAndDeleteElements(); + return true; + } + + aAreas.PurgeAndDeleteElements(); + return false; +} + +Vector TraceToGround( const Vector &vecPos ) +{ + trace_t tr; + UTIL_TraceLine( vecPos + Vector( 0, 0, 100 ), vecPos, MASK_NPCSOLID, NULL, ASW_COLLISION_GROUP_PARASITE, &tr ); + return tr.endpos; +} + +bool CASW_Spawn_Manager::SpawnRandomParasitePack( int nParasites ) +{ + int iNumNodes = g_pBigAINet->NumNodes(); + if ( iNumNodes < 6 ) + return false; + + int nHull = HULL_TINY; + CUtlVector aAreas; + for ( int i = 0; i < 6; i++ ) + { + CAI_Node *pNode = NULL; + int nTries = 0; + while ( nTries < 5 && ( !pNode || pNode->GetType() != NODE_GROUND ) ) + { + pNode = g_pBigAINet->GetNode( RandomInt( 0, iNumNodes ) ); + nTries++; + } + + if ( pNode ) + { + CASW_Open_Area *pArea = FindNearbyOpenArea( pNode->GetOrigin(), HULL_MEDIUMBIG ); + if ( pArea && pArea->m_nTotalLinks > 30 ) + { + // test if there's room to spawn a shieldbug at that spot + if ( ValidSpawnPoint( pArea->m_pNode->GetPosition( nHull ), NAI_Hull::Mins( nHull ), NAI_Hull::Maxs( nHull ), true, false ) ) + { + aAreas.AddToTail( pArea ); + } + else + { + delete pArea; + } + } + } + // stop searching once we have 3 acceptable candidates + if ( aAreas.Count() >= 3 ) + break; + } + + // find area with the highest connectivity + CASW_Open_Area *pBestArea = NULL; + for ( int i = 0; i < aAreas.Count(); i++ ) + { + CASW_Open_Area *pArea = aAreas[i]; + if ( !pBestArea || pArea->m_nTotalLinks > pBestArea->m_nTotalLinks ) + { + pBestArea = pArea; + } + } + + if ( pBestArea ) + { + for ( int i = 0; i < nParasites; i++ ) + { + CBaseEntity *pAlien = SpawnAlienAt( "asw_parasite", TraceToGround( pBestArea->m_pNode->GetPosition( nHull ) ), RandomAngle( 0, 360 ) ); + IASW_Spawnable_NPC *pSpawnable = dynamic_cast( pAlien ); + if ( pSpawnable ) + { + pSpawnable->SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL); + } + if ( asw_director_debug.GetBool() && pAlien ) + { + Msg( "Spawned parasite at %f %f %f\n", pAlien->GetAbsOrigin() ); + NDebugOverlay::Cross3D( pAlien->GetAbsOrigin(), 8.0f, 255, 0, 0, true, 20.0f ); + } + } + aAreas.PurgeAndDeleteElements(); + return true; + } + + aAreas.PurgeAndDeleteElements(); + return false; +} + +// heuristic to find reasonably open space - searches for areas with high node connectivity +CASW_Open_Area* CASW_Spawn_Manager::FindNearbyOpenArea( const Vector &vecSearchOrigin, int nSearchHull ) +{ + CBaseEntity *pStartEntity = gEntList.FindEntityByClassname( NULL, "info_player_start" ); + int iNumNodes = g_pBigAINet->NumNodes(); + CAI_Node *pHighestConnectivity = NULL; + int nHighestLinks = 0; + for ( int i=0 ; iGetNode( i ); + if ( !pNode || pNode->GetType() != NODE_GROUND ) + continue; + + Vector vecPos = pNode->GetOrigin(); + float flDist = vecPos.DistTo( vecSearchOrigin ); + if ( flDist > 400.0f ) + continue; + + // discard if node is too near start location + if ( pStartEntity && vecPos.DistTo( pStartEntity->GetAbsOrigin() ) < 1400.0f ) // NOTE: assumes all start points are clustered near one another + continue; + + // discard if node is inside an escape area + bool bInsideEscapeArea = false; + for ( int d=0; dCollisionProp()->IsPointInBounds( vecPos ) ) + { + bInsideEscapeArea = true; + break; + } + } + if ( bInsideEscapeArea ) + continue; + + // count links that drones could follow + int nLinks = pNode->NumLinks(); + int nValidLinks = 0; + for ( int k = 0; k < nLinks; k++ ) + { + CAI_Link *pLink = pNode->GetLinkByIndex( k ); + if ( !pLink ) + continue; + + if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) ) + continue; + + nValidLinks++; + } + if ( nValidLinks > nHighestLinks ) + { + nHighestLinks = nValidLinks; + pHighestConnectivity = pNode; + } + if ( asw_director_debug.GetBool() ) + { + NDebugOverlay::Text( vecPos, UTIL_VarArgs( "%d", nValidLinks ), false, 10.0f ); + } + } + + if ( !pHighestConnectivity ) + return NULL; + + // now, starting at the new node, find all nearby nodes with a minimum connectivity + CASW_Open_Area *pArea = new CASW_Open_Area(); + pArea->m_vecOrigin = pHighestConnectivity->GetOrigin(); + pArea->m_pNode = pHighestConnectivity; + int nMinLinks = nHighestLinks * 0.3f; + nMinLinks = MAX( nMinLinks, 4 ); + + pArea->m_aAreaNodes.AddToTail( pHighestConnectivity ); + if ( asw_director_debug.GetBool() ) + { + Msg( "minLinks = %d\n", nMinLinks ); + } + pArea->m_nTotalLinks = 0; + for ( int i=0 ; iGetNode( i ); + if ( !pNode || pNode->GetType() != NODE_GROUND ) + continue; + + Vector vecPos = pNode->GetOrigin(); + float flDist = vecPos.DistTo( pArea->m_vecOrigin ); + if ( flDist > 400.0f ) + continue; + + // discard if node is inside an escape area + bool bInsideEscapeArea = false; + for ( int d=0; dCollisionProp()->IsPointInBounds( vecPos ) ) + { + bInsideEscapeArea = true; + break; + } + } + if ( bInsideEscapeArea ) + continue; + + // count links that drones could follow + int nLinks = pNode->NumLinks(); + int nValidLinks = 0; + for ( int k = 0; k < nLinks; k++ ) + { + CAI_Link *pLink = pNode->GetLinkByIndex( k ); + if ( !pLink ) + continue; + + if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) ) + continue; + + nValidLinks++; + } + if ( nValidLinks >= nMinLinks ) + { + pArea->m_aAreaNodes.AddToTail( pNode ); + pArea->m_nTotalLinks += nValidLinks; + } + } + // highlight and measure bounds + Vector vecAreaMins = Vector( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecAreaMaxs = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + for ( int i = 0; i < pArea->m_aAreaNodes.Count(); i++ ) + { + vecAreaMins = VectorMin( vecAreaMins, pArea->m_aAreaNodes[i]->GetOrigin() ); + vecAreaMaxs = VectorMax( vecAreaMaxs, pArea->m_aAreaNodes[i]->GetOrigin() ); + + if ( asw_director_debug.GetBool() ) + { + if ( i == 0 ) + { + NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 20.0f, 255, 255, 64, true, 10.0f ); + } + else + { + NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 10.0f, 255, 128, 0, true, 10.0f ); + } + } + } + + Vector vecArea = ( vecAreaMaxs - vecAreaMins ); + float flArea = vecArea.x * vecArea.y; + + if ( asw_director_debug.GetBool() ) + { + Msg( "area mins = %f %f %f\n", VectorExpand( vecAreaMins ) ); + Msg( "area maxs = %f %f %f\n", VectorExpand( vecAreaMaxs ) ); + NDebugOverlay::Box( vec3_origin, vecAreaMins, vecAreaMaxs, 255, 128, 128, 10, 10.0f ); + Msg( "Total links = %d Area = %f\n", pArea->m_nTotalLinks, flArea ); + } + + return pArea; +} // creates a batch of aliens at the mouse cursor void asw_alien_batch_f( const CCommand& args ) @@ -715,4 +1143,15 @@ void asw_alien_horde_f( const CCommand& args ) Msg("Failed to add horde\n"); } } -static ConCommand asw_alien_horde("asw_alien_horde", asw_alien_horde_f, "Creates a horde of aliens somewhere nearby", FCVAR_GAMEDLL | FCVAR_CHEAT); \ No newline at end of file +static ConCommand asw_alien_horde("asw_alien_horde", asw_alien_horde_f, "Creates a horde of aliens somewhere nearby", FCVAR_GAMEDLL | FCVAR_CHEAT); + + +CON_COMMAND_F( asw_spawn_shieldbug, "Spawns a shieldbug somewhere randomly in the map", FCVAR_CHEAT ) +{ + ASWSpawnManager()->SpawnRandomShieldbug(); +} + +CON_COMMAND_F( asw_spawn_parasite_pack, "Spawns a group of parasites somewhere randomly in the map", FCVAR_CHEAT ) +{ + ASWSpawnManager()->SpawnRandomParasitePack( RandomInt( 3, 5 ) ); +} \ No newline at end of file diff --git a/game/server/swarm/asw_spawn_manager.h b/game/server/swarm/asw_spawn_manager.h index fd1e5dd8..db484b1d 100644 --- a/game/server/swarm/asw_spawn_manager.h +++ b/game/server/swarm/asw_spawn_manager.h @@ -7,6 +7,8 @@ class CAI_Network; class CTriggerMultiple; struct AI_Waypoint_t; +class CAI_Node; +class CASW_Alien; // The spawn manager can spawn aliens and groups of aliens @@ -20,6 +22,23 @@ public: int m_nHullType; }; +class CASW_Open_Area +{ +public: + CASW_Open_Area() + { + m_flArea = 0.0f; + m_nTotalLinks = 0; + m_vecOrigin = vec3_origin; + m_pNode = NULL; + } + float m_flArea; + int m_nTotalLinks; + Vector m_vecOrigin; + CAI_Node *m_pNode; + CUtlVector m_aAreaNodes; +}; + class CASW_Spawn_Manager { public: @@ -41,11 +60,20 @@ public: bool GetAlienBounds( const char *szAlienClass, Vector &vecMins, Vector &vecMaxs ); bool GetAlienBounds( string_t iszAlienClass, Vector &vecMins, Vector &vecMaxs ); - int GetHordeToSpawn() { return m_iHordeToSpawn; } + int GetHordeToSpawn() { return m_iHordeToSpawn; } + + void OnAlienWokeUp( CASW_Alien *pAlien ); + void OnAlienSleeping( CASW_Alien *pAlien ); + int GetAwakeAliens() { return m_nAwakeAliens; } + int GetAwakeDrones() { return m_nAwakeDrones; } int GetNumAlienClasses(); ASW_Alien_Class_Entry* GetAlienClass( int i ); + // spawns a shieldbug somewhere randomly in the map + bool SpawnRandomShieldbug(); + bool SpawnRandomParasitePack( int nParasites ); + private: void UpdateCandidateNodes(); bool FindHordePosition(); @@ -54,12 +82,18 @@ private: void FindEscapeTriggers(); void DeleteRoute( AI_Waypoint_t *pWaypointList ); + // finds an area with good node connectivity. Caller should take ownership of the CASW_Open_Area instance. + CASW_Open_Area* FindNearbyOpenArea( const Vector &vecSearchOrigin, int nSearchHull ); + CountdownTimer m_batchInterval; Vector m_vecHordePosition; QAngle m_angHordeAngle; int m_iHordeToSpawn; int m_iAliensToSpawn; + int m_nAwakeAliens; + int m_nAwakeDrones; + // maintaining a list of possible nodes to spawn aliens from CUtlVector m_northCandidateNodes; CUtlVector m_southCandidateNodes; diff --git a/game/server/swarm/asw_spawner.cpp b/game/server/swarm/asw_spawner.cpp index 3822c82d..69411121 100644 --- a/game/server/swarm/asw_spawner.cpp +++ b/game/server/swarm/asw_spawner.cpp @@ -153,6 +153,9 @@ void CASW_Spawner::AlienKilled( CBaseEntity *pVictim ) m_nCurrentLiveAliens--; + if (asw_debug_spawners.GetBool()) + Msg("%d AlienKilled NumLive = %d\n", entindex(), m_nCurrentLiveAliens ); + // If we're here, we're getting erroneous death messages from children we haven't created AssertMsg( m_nCurrentLiveAliens >= 0, "asw_spawner receiving child death notice but thinks has no children\n" ); @@ -293,6 +296,22 @@ bool CASW_Spawner::ApplyCarnageMode( float fScaler, float fInvScaler ) return false; } +int CASW_Spawner::DrawDebugTextOverlays() +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Num Live Aliens: %d", m_nCurrentLiveAliens ),0 ); + text_offset++; + NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Max Live Aliens: %d", m_nMaxLiveAliens ),0 ); + text_offset++; + NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Alien supply: %d", m_bInfiniteAliens ? -1 : m_nNumAliens ),0 ); + text_offset++; + } + return text_offset; +} + void ASW_ApplyCarnage_f(float fScaler) { if ( fScaler <= 0 ) diff --git a/game/server/swarm/asw_spawner.h b/game/server/swarm/asw_spawner.h index 0afa31b4..34f4a257 100644 --- a/game/server/swarm/asw_spawner.h +++ b/game/server/swarm/asw_spawner.h @@ -21,6 +21,7 @@ public: virtual void Spawn(); virtual void Precache(); virtual void InitAlienClassName(); + virtual int DrawDebugTextOverlays(); Class_T Classify() { return (Class_T) CLASS_ASW_SPAWNER; } diff --git a/game/shared/SoundEmitterSystem.cpp b/game/shared/SoundEmitterSystem.cpp index 3ae7e104..96d0c2d7 100644 --- a/game/shared/SoundEmitterSystem.cpp +++ b/game/shared/SoundEmitterSystem.cpp @@ -1208,6 +1208,9 @@ void SoundSystemPreloadSounds( void ) CON_COMMAND( sv_soundemitter_flush, "Flushes the sounds.txt system (server only)" ) { + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + // save the current soundscape // kill the system g_SoundEmitterSystem.Flush(); @@ -1222,12 +1225,18 @@ CON_COMMAND( sv_soundemitter_flush, "Flushes the sounds.txt system (server only) CON_COMMAND( sv_soundemitter_filecheck, "Report missing wave files for sounds and game_sounds files." ) { + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + int missing = soundemitterbase->CheckForMissingWavFiles( true ); DevMsg( "---------------------------\nTotal missing files %i\n", missing ); } CON_COMMAND( sv_findsoundname, "Find sound names which reference the specified wave files." ) -{ +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + if ( args.ArgC() != 2 ) return; diff --git a/game/shared/ai_responsesystem.cpp b/game/shared/ai_responsesystem.cpp index e8c4a435..71d4a1ad 100644 --- a/game/shared/ai_responsesystem.cpp +++ b/game/shared/ai_responsesystem.cpp @@ -639,6 +639,11 @@ IResponseSystem *g_pResponseSystem = &defaultresponsesytem; CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) { +#ifdef GAME_DLL + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; +#endif + defaultresponsesytem.ReloadAllResponseSystems(); } diff --git a/game/shared/swarm/asw_achievements.cpp b/game/shared/swarm/asw_achievements.cpp index 2a83d940..02fa1d01 100644 --- a/game/shared/swarm/asw_achievements.cpp +++ b/game/shared/swarm/asw_achievements.cpp @@ -762,7 +762,7 @@ class CAchievement_Unlock_All_Weapons : public CASW_Achievement { if ( !Q_stricmp( event->GetName(), "level_up" ) ) { - if ( event->GetInt( "level" ) >= ASW_LEVEL_CAP ) + if ( event->GetInt( "level" ) >= ASW_NUM_EXPERIENCE_LEVELS ) { IncrementCount(); } @@ -1077,5 +1077,73 @@ class CAchievement_Para_Hat : public CASW_Achievement }; DECLARE_ACHIEVEMENT_ORDER( CAchievement_Para_Hat, ACHIEVEMENT_ASW_PARA_HAT, "ASW_PARA_HAT", 5, 149 ); +class CAchievement_Imba_Campaign : public CASW_Achievement +{ + void Init() + { + SetFlags( ACH_SAVE_GLOBAL | ACH_HAS_COMPONENTS ); + SetStoreProgressInSteam( true ); + SetGoal( NELEMS( g_szAchievementMapNames ) ); + } + virtual void ListenForEvents( void ) + { + ListenForGameEvent( "mission_success" ); + } + + void FireGameEvent_Internal( IGameEvent *event ) + { + if ( !Q_stricmp( event->GetName(), "mission_success" ) && ASWGameRules() && ASWGameRules()->GetSkillLevel() >= 5 ) + { + if ( LocalPlayerWasSpectating() ) + return; + + const char *szMapName = event->GetString( "strMapName" ); + for ( int i = 0; i < NELEMS( g_szAchievementMapNames ); i++ ) + { + if ( !Q_stricmp( szMapName, g_szAchievementMapNames[i] ) ) + { + EnsureComponentBitSetAndEvaluate( i ); + break; + } + } + } + } +}; +DECLARE_ACHIEVEMENT_ORDER( CAchievement_Imba_Campaign, ACHIEVEMENT_ASW_IMBA_CAMPAIGN, "ASW_IMBA_CAMPAIGN", 5, 182 ); + +class CAchievement_Hardcore : public CASW_Achievement +{ + void Init() + { + SetFlags( ACH_SAVE_GLOBAL ); + SetGoal( 1 ); + } + + virtual void ListenForEvents( void ) + { + ListenForGameEvent( "mission_success" ); + } + + void FireGameEvent_Internal( IGameEvent *event ) + { + if ( !Q_stricmp( event->GetName(), "mission_success" ) && ASWGameRules() && ASWGameRules()->GetSkillLevel() >= 5 + && CAlienSwarm::IsHardcoreFF() && CAlienSwarm::IsOnslaught() ) + { + if ( LocalPlayerWasSpectating() ) + return; + + const char *szMapName = event->GetString( "strMapName" ); + for ( int i = 0; i < NELEMS( g_szAchievementMapNames ); i++ ) + { + if ( !Q_stricmp( szMapName, g_szAchievementMapNames[i] ) ) + { + IncrementCount(); + break; + } + } + } + } +}; +DECLARE_ACHIEVEMENT_ORDER( CAchievement_Hardcore, ACHIEVEMENT_ASW_HARDCORE, "ASW_HARDCORE", 5, 184 ); #endif \ No newline at end of file diff --git a/game/shared/swarm/asw_achievements.h b/game/shared/swarm/asw_achievements.h index 5498bc26..02e55846 100644 --- a/game/shared/swarm/asw_achievements.h +++ b/game/shared/swarm/asw_achievements.h @@ -68,7 +68,9 @@ enum ACHIEVEMENT_ASW_SPEEDRUN_TIMOR, ACHIEVEMENT_ASW_CAMPAIGN_NO_DEATHS, ACHIEVEMENT_ASW_MISSION_NO_DEATHS, - ACHIEVEMENT_ASW_PARA_HAT + ACHIEVEMENT_ASW_PARA_HAT, + ACHIEVEMENT_ASW_IMBA_CAMPAIGN, + ACHIEVEMENT_ASW_HARDCORE, }; #define ACH_LISTEN_ALIEN_DEATH_EVENTS 0x1000 diff --git a/game/shared/swarm/asw_gamerules.cpp b/game/shared/swarm/asw_gamerules.cpp index ca51269f..87f95f47 100644 --- a/game/shared/swarm/asw_gamerules.cpp +++ b/game/shared/swarm/asw_gamerules.cpp @@ -84,6 +84,7 @@ #include "EntityFlame.h" #include "asw_buffgrenade_projectile.h" #include "asw_achievements.h" + #include "asw_director.h" #endif #include "game_timescale_shared.h" #include "asw_gamerules.h" @@ -135,6 +136,19 @@ extern ConVar old_radius_damage; ConVar sv_vote_kick_ban_duration("sv_vote_kick_ban_duration", "5", 0, "How long should a kick vote ban someone from the server? (in minutes)"); ConVar sv_timeout_when_fully_connected( "sv_timeout_when_fully_connected", "30", FCVAR_NONE, "Once fully connected, player will be kicked if he doesn't send a network message within this interval." ); ConVar mm_swarm_state( "mm_swarm_state", "ingame", FCVAR_DEVELOPMENTONLY ); + + static void UpdateMatchmakingTagsCallback( IConVar *pConVar, const char *pOldValue, float flOldValue ) + { + // update sv_tags to force an update of the matchmaking tags + static ConVarRef sv_tags( "sv_tags" ); + + if ( sv_tags.IsValid() ) + { + char buffer[ 1024 ]; + Q_snprintf( buffer, sizeof( buffer ), "%s", sv_tags.GetString() ); + sv_tags.SetValue( buffer ); + } + } #else extern ConVar asw_controls; #endif @@ -153,6 +167,30 @@ ConVar asw_stim_time_scale("asw_stim_time_scale", "0.35", FCVAR_REPLICATED | FCV ConVar asw_time_scale_delay("asw_time_scale_delay", "0.15", FCVAR_REPLICATED | FCVAR_CHEAT, "Delay before timescale changes to give a chance for the client to comply and predict."); ConVar asw_ignore_need_two_player_requirement("asw_ignore_need_two_player_requirement", "0", FCVAR_REPLICATED, "If set to 1, ignores the mission setting that states two players are needed to start the mission."); ConVar mp_gamemode( "mp_gamemode", "campaign", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Current game mode, acceptable values are campaign and single_mission.", false, 0.0f, false, 0.0f ); +ConVar asw_sentry_friendly_fire_scale( "asw_sentry_friendly_fire_scale", "0", FCVAR_REPLICATED, "Damage scale for sentry gun friendly fire" +#ifdef GAME_DLL + ,UpdateMatchmakingTagsCallback ); +#else + ); +#endif +ConVar asw_marine_ff_absorption("asw_marine_ff_absorption", "1", FCVAR_REPLICATED, "Friendly fire absorption style (0=none 1=ramp up 2=ramp down)" +#ifdef GAME_DLL + ,UpdateMatchmakingTagsCallback ); +#else + ); +#endif +ConVar asw_horde_override( "asw_horde_override", "0", FCVAR_REPLICATED, "Forces hordes to spawn" +#ifdef GAME_DLL + ,UpdateMatchmakingTagsCallback ); +#else + ); +#endif +ConVar asw_wanderer_override( "asw_wanderer_override", "0", FCVAR_REPLICATED, "Forces wanderers to spawn" +#ifdef GAME_DLL + ,UpdateMatchmakingTagsCallback ); +#else + ); +#endif // ASW Weapons // Rifle @@ -251,7 +289,7 @@ ConVar asw_vote_kick_fraction("asw_vote_kick_fraction", "0.6", FCVAR_REPLICATED ConVar asw_vote_leader_fraction("asw_vote_leader_fraction", "0.6", FCVAR_REPLICATED | FCVAR_ARCHIVE, "Fraction of players needed to activate a leader vote"); ConVar asw_vote_map_fraction("asw_vote_map_fraction", "0.6", FCVAR_REPLICATED | FCVAR_ARCHIVE, "Fraction of players needed to activate a map vote"); ConVar asw_marine_collision("asw_marine_collision", "0", FCVAR_REPLICATED, "Whether marines collide with each other or not, in a multiplayer game"); -ConVar asw_skill( "asw_skill","2", FCVAR_REPLICATED | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Game skill level (1-4).", true, 1, true, 4 ); +ConVar asw_skill( "asw_skill","2", FCVAR_REPLICATED | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Game skill level (1-5).", true, 1, true, 5 ); ConVar asw_money( "asw_money", "0", FCVAR_REPLICATED, "Can players collect money?" ); ConVar asw_client_build_maps("asw_client_build_maps", "0", FCVAR_REPLICATED, "Whether clients compile random maps rather than getting sent them"); @@ -456,7 +494,7 @@ CAmmoDef *GetAmmoDef() def.AddAmmoType("ASW_P", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_asw_p", "sk_npc_dmg_asw_p", "sk_max_asw_p", BULLET_IMPULSE(200, 1225), 0 ); // mining laser def.AddAmmoType("ASW_ML", DMG_ENERGYBEAM, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_asw_ml", "sk_npc_dmg_asw_ml", "sk_max_asw_ml", BULLET_IMPULSE(200, 1225), 0 ); - // mining laser + // tesla gun - happy LJ? def.AddAmmoType("ASW_TG", DMG_ENERGYBEAM, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_asw_tg", "sk_npc_dmg_asw_tg", "sk_max_asw_tg", BULLET_IMPULSE(200, 1225), 0 ); // railgun def.AddAmmoType("ASW_RG", DMG_SONIC, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_asw_rg", "sk_npc_dmg_asw_rg", "sk_max_asw_rg", BULLET_IMPULSE(200, 1225), 0 ); @@ -627,19 +665,6 @@ const char * GenerateNewSaveGameName() return NULL; } -void UpdateMatchmakingTags() -{ - // update sv_tags to force an update of the matchmaking tags - static ConVarRef sv_tags( "sv_tags" ); - - if ( sv_tags.IsValid() ) - { - char buffer[ 1024 ]; - Q_snprintf( buffer, sizeof( buffer ), "%s", sv_tags.GetString() ); - sv_tags.SetValue( buffer ); - } -} - CAlienSwarm::CAlienSwarm() { Msg("CAlienSwarm created\n"); @@ -1610,6 +1635,11 @@ void CAlienSwarm::StartMission() m_Medals.OnStartMission(); + if ( ASWDirector() ) + { + ASWDirector()->OnMissionStarted(); + } + Msg("==STARTMISSION==\n"); SetGameState(ASW_GS_LAUNCHING); @@ -3319,7 +3349,7 @@ void CAlienSwarm::MissionComplete( bool bSuccess ) if (!MapScores()->IsModeUnlocked(mapName, GetSkillLevel(), ASW_SM_CARNAGE)) { // check for unlocking carnage (if we completed the mission on Insane) - bJustUnlockedCarnage = (GetSkillLevel() == 4); + bJustUnlockedCarnage = (GetSkillLevel() >= 4); //Msg("Checked just carnage unlock = %d\n", bJustUnlockedCarnage); } if (!MapScores()->IsModeUnlocked(mapName, GetSkillLevel(), ASW_SM_UBER)) @@ -5144,6 +5174,11 @@ void CAlienSwarm::OnSkillLevelChanged( int iNewLevel ) m_iMissionDifficulty = 10; szDifficulty = "insane"; } + else if (iNewLevel == 5) // imba + { + m_iMissionDifficulty = 13; + szDifficulty = "imba"; + } else // normal { m_iMissionDifficulty = 5; @@ -5200,7 +5235,7 @@ void CAlienSwarm::OnSkillLevelChanged( int iNewLevel ) } } - UpdateMatchmakingTags(); + UpdateMatchmakingTagsCallback( NULL, "0", 0.0f ); m_iSkillLevel = iNewLevel; } @@ -5226,12 +5261,28 @@ void CAlienSwarm::RequestSkill( CASW_Player *pPlayer, int nSkill ) if ( !( m_iGameState == ASW_GS_BRIEFING || m_iGameState == ASW_GS_DEBRIEF ) ) // don't allow skill change outside of briefing return; - if ( nSkill >= 1 && nSkill <= 4 && ASWGameResource() && ASWGameResource()->GetLeader() == pPlayer ) + if ( nSkill >= 1 && nSkill <= 5 && ASWGameResource() && ASWGameResource()->GetLeader() == pPlayer ) { ConVar *var = (ConVar *)cvar->FindVar( "asw_skill" ); if (var) { + int iOldSkill = var->GetInt(); + var->SetValue( nSkill ); + + if ( iOldSkill != var->GetInt() ) + { + CReliableBroadcastRecipientFilter filter; + filter.RemoveRecipient( pPlayer ); // notify everyone except the player changing the difficulty level + switch(var->GetInt()) + { + case 1: UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_set_difficulty_easy", pPlayer->GetPlayerName() ); break; + case 2: UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_set_difficulty_normal", pPlayer->GetPlayerName() ); break; + case 3: UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_set_difficulty_hard", pPlayer->GetPlayerName() ); break; + case 4: UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_set_difficulty_insane", pPlayer->GetPlayerName() ); break; + case 5: UTIL_ClientPrintFilter( filter, ASW_HUD_PRINTTALKANDCONSOLE, "#asw_set_difficulty_imba", pPlayer->GetPlayerName() ); break; + } + } } } } @@ -5241,7 +5292,7 @@ void CAlienSwarm::RequestSkillUp(CASW_Player *pPlayer) if (m_iGameState != ASW_GS_BRIEFING) // don't allow skill change outside of briefing return; - if (m_iSkillLevel < 4 && ASWGameResource() && ASWGameResource()->GetLeader() == pPlayer) + if (m_iSkillLevel < 5 && ASWGameResource() && ASWGameResource()->GetLeader() == pPlayer) { ConVar *var = (ConVar *)cvar->FindVar( "asw_skill" ); if (var) @@ -6598,6 +6649,8 @@ int CAlienSwarm::TotalInfestDamage() return 270; case 4: return 280; // BARELY survivable with Bastille and heal beacon + case 5: + return 280; } // ASv1 Total infest damage = 90 + difficulty * 20; @@ -6749,3 +6802,13 @@ const QAngle& CAlienSwarm::GetTopDownMovementAxis() static QAngle axis = ASW_MOVEMENT_AXIS; return axis; } + +bool CAlienSwarm::IsHardcoreFF() +{ + return ( asw_marine_ff_absorption.GetInt() != 1 || asw_sentry_friendly_fire_scale.GetFloat() != 0.0f ); +} + +bool CAlienSwarm::IsOnslaught() +{ + return ( asw_horde_override.GetBool() || asw_wanderer_override.GetBool() ); +} \ No newline at end of file diff --git a/game/shared/swarm/asw_gamerules.h b/game/shared/swarm/asw_gamerules.h index e432ef2a..73f85b87 100644 --- a/game/shared/swarm/asw_gamerules.h +++ b/game/shared/swarm/asw_gamerules.h @@ -234,9 +234,9 @@ public: { iLevel = 1; } - else if ( iLevel > 4 ) + else if ( iLevel > 5 ) { - iLevel = 4; + iLevel = 5; } m_iSkillLevel = iLevel; @@ -386,8 +386,8 @@ public: virtual void RefreshSkillData ( bool forceUpdate ); // difficulty - virtual int GetSkillLevel() { return m_iSkillLevel; } // skill level (expanded HL2 style: 1 = easy, 2 = normal, 3 = hard, 4 = insane ) - CNetworkVar(int, m_iSkillLevel); // 1 = easy, 2 = normal, 3 = hard, 4 = insane + virtual int GetSkillLevel() { return m_iSkillLevel; } // skill level (expanded HL2 style: 1 = easy, 2 = normal, 3 = hard, 4 = insane, 5 = imba ) + CNetworkVar(int, m_iSkillLevel); // 1 = easy, 2 = normal, 3 = hard, 4 = insane, 5 = imba int GetMissionDifficulty() { return m_iMissionDifficulty; } // overall difficulty of the mission from 2-10, based on skill level and campaign modifier CNetworkVar(int, m_iMissionDifficulty); CNetworkVar(bool, m_bCheated); @@ -475,6 +475,8 @@ public: bool IsIntroMap() { return m_bIsIntro; } bool IsOutroMap() { return m_bIsOutro; } bool IsLobbyMap() { return m_bIsLobby; } + static bool IsHardcoreFF(); + static bool IsOnslaught(); bool m_bIsTutorial; bool m_bIsIntro; diff --git a/game/shared/swarm/asw_marine_shared.cpp b/game/shared/swarm/asw_marine_shared.cpp index bfa1e615..82e15afe 100644 --- a/game/shared/swarm/asw_marine_shared.cpp +++ b/game/shared/swarm/asw_marine_shared.cpp @@ -296,6 +296,7 @@ float CASW_Marine::MaxSpeed() // adjust the speed by difficulty level switch (ASWGameRules()->GetSkillLevel()) { + case 5: speedscale *= asw_marine_speed_scale_insane.GetFloat(); break; case 4: speedscale *= asw_marine_speed_scale_insane.GetFloat(); break; case 3: speedscale *= asw_marine_speed_scale_hard.GetFloat(); break; case 2: speedscale *= asw_marine_speed_scale_normal.GetFloat(); break; diff --git a/game/shared/swarm/asw_player_experience.cpp b/game/shared/swarm/asw_player_experience.cpp index 85c2b71e..c8775041 100644 --- a/game/shared/swarm/asw_player_experience.cpp +++ b/game/shared/swarm/asw_player_experience.cpp @@ -100,15 +100,28 @@ int g_iXPAward[ ASW_NUM_XP_TYPES ]= 50, // ASW_XP_HACKING }; +// scalar applied to XP required to level, based on your current promotion +float g_flPromotionXPScale[ ASW_PROMOTION_CAP + 1 ]= +{ + 1.0f, + 1.0f, + 1.0f, + 1.0f, + 2.0f, + 4.0f, + 6.0f, +}; + #define ASW_MISSION_XP_AWARD_ON_FAILURE 750 // XP award divided up between objectives // NOTE: If you change this, update the labels in CExperienceReport::OnThink too -float g_flXPDifficultyScale[4]= +float g_flXPDifficultyScale[5]= { 0.5f, // easy 1.0f, // normal 1.2f, // hard 1.4f, // insane + 1.5f, // imba }; // Weapon unlocks @@ -153,17 +166,18 @@ ASW_Weapon_Unlock g_WeaponUnlocks[]= ASW_Weapon_Unlock( "asw_weapon_night_vision", 23 ),// ASW_Weapon_Unlock( "asw_weapon_sentry_cannon", 24 ), ASW_Weapon_Unlock( "asw_weapon_smart_bomb", 25 ),// - ASW_Weapon_Unlock( "asw_weapon_grenade_launcher", 26 ), // ASW_LEVEL_CAP + ASW_Weapon_Unlock( "asw_weapon_grenade_launcher", 26 ), // ASW_NUM_EXPERIENCE_LEVELS }; // given an Experience total, this tells you the player's level -int LevelFromXP( int iExperience ) +int LevelFromXP( int iExperience, int iPromotion ) { - iExperience = MIN( iExperience, ASW_XP_CAP ); + iExperience = MIN( iExperience, ASW_XP_CAP * g_flPromotionXPScale[ iPromotion ] ); for ( int i = 0; i < NELEMS( g_iLevelExperience ); i++ ) { - if ( iExperience < g_iLevelExperience[ i ] ) + int iRequiredXP = (int) ( g_flPromotionXPScale[ iPromotion ] * g_iLevelExperience[ i ] ); + if ( iExperience < iRequiredXP ) { return i; } @@ -368,13 +382,13 @@ void CASW_Player::CalculateEarnedXP() int CASW_Player::GetLevel() { - return LevelFromXP( GetExperience() ); + return LevelFromXP( GetExperience(), GetPromotion() ); } int CASW_Player::GetLevelBeforeDebrief() { - return LevelFromXP( GetExperienceBeforeDebrief() ); + return LevelFromXP( GetExperienceBeforeDebrief(), GetPromotion() ); } void CASW_Player::RequestExperience() @@ -460,7 +474,7 @@ void CASW_Player::AwardExperience() Msg( "%s: AwardExperience: Pre XP is %d\n", IsServerDll() ? "S" : "C", m_iExperience ); m_iExperience += m_iEarnedXP[ ASW_XP_TOTAL ]; - m_iExperience = MIN( m_iExperience, ASW_XP_CAP ); + m_iExperience = MIN( m_iExperience, ASW_XP_CAP * g_flPromotionXPScale[ GetPromotion() ] ); #ifdef CLIENT_DLL if ( IsLocalPlayer() ) @@ -646,7 +660,7 @@ void CASW_Player::Steam_OnUserStatsReceived( UserStatsReceived_t *pUserStatsRece if( GetLocalASWPlayer() == this ) g_ASW_Steamstats.FetchStats( steamID, this ); - if ( IsLocalPlayer() && GetLevel() >= ASW_LEVEL_CAP ) + if ( IsLocalPlayer() && GetLevel() >= ASW_NUM_EXPERIENCE_LEVELS ) { CAchievementMgr *pAchievementMgr = dynamic_cast( engine->GetAchievementMgr() ); if ( !pAchievementMgr ) @@ -694,7 +708,7 @@ ConCommand asw_debug_xp( "asw_debug_xp", asw_debug_xp_f, "Lists XP details for l void CASW_Player::AcceptPromotion() { - if ( GetExperience() < ASW_XP_CAP ) + if ( GetExperience() < ASW_XP_CAP * g_flPromotionXPScale[ GetPromotion() ] ) return; if ( GetPromotion() >= ASW_PROMOTION_CAP ) diff --git a/game/shared/swarm/asw_player_shared.h b/game/shared/swarm/asw_player_shared.h index ba14f044..45fd3712 100644 --- a/game/shared/swarm/asw_player_shared.h +++ b/game/shared/swarm/asw_player_shared.h @@ -26,11 +26,13 @@ enum ASW_USE_HOLD_RELEASE_FULL, }; +#define ASW_PROMOTION_CAP 6 #define ASW_NUM_EXPERIENCE_LEVELS 26 extern int g_iLevelExperience[ ASW_NUM_EXPERIENCE_LEVELS ]; +extern float g_flPromotionXPScale[ ASW_PROMOTION_CAP + 1 ]; -int LevelFromXP( int iExperience ); +int LevelFromXP( int iExperience, int iPromotion ); enum CASW_Earned_XP_t { diff --git a/game/shared/swarm/asw_shareddefs.h b/game/shared/swarm/asw_shareddefs.h index f05261e7..e5111311 100644 --- a/game/shared/swarm/asw_shareddefs.h +++ b/game/shared/swarm/asw_shareddefs.h @@ -583,8 +583,6 @@ enum }; #define ASW_XP_CAP 42250 -#define ASW_LEVEL_CAP 26 -#define ASW_PROMOTION_CAP 3 extern ConVar asw_visrange_generic; @@ -602,4 +600,13 @@ public: #endif +enum CASW_Flock_Leader_State +{ + ASW_FL_CHASING = 0, + ASW_FL_CHARGING, + ASW_FL_FLEEING, + + NUM_FLOCK_LEADER_STATES, +}; + #endif // ASW_SHAREDDEFS_H diff --git a/game/shared/swarm/asw_util_shared.cpp b/game/shared/swarm/asw_util_shared.cpp index cdad33ff..771a31cf 100644 --- a/game/shared/swarm/asw_util_shared.cpp +++ b/game/shared/swarm/asw_util_shared.cpp @@ -954,7 +954,7 @@ float UTIL_ASW_CalcFastDoorHackTime(int iNumRows, int iNumColumns, int iNumWires ideal_time *= 1.05f; // 5% slower on easy mode else if (iSkill == 3) ideal_time *= 0.95f; // 5% faster on hard mode - else if (iSkill == 4) + else if (iSkill == 4 || iSkill == 5) ideal_time *= 0.90f; // 10% faster on insane mode return ideal_time; diff --git a/game/shared/swarm/asw_weapon_jump_jet.cpp b/game/shared/swarm/asw_weapon_jump_jet.cpp index b858dc9f..79cb1a82 100644 --- a/game/shared/swarm/asw_weapon_jump_jet.cpp +++ b/game/shared/swarm/asw_weapon_jump_jet.cpp @@ -115,7 +115,7 @@ void CASW_Weapon_Jump_Jet::PrimaryAttack( void ) void CASW_Weapon_Jump_Jet::DoJumpJet() { CASW_Marine *pMarine = GetMarine(); - if ( !pMarine ) + if ( !pMarine || pMarine->m_iJumpJetting != JJ_NONE ) return; pMarine->m_iJumpJetting = JJ_JUMP_JETS; diff --git a/game/shared/swarm/asw_weapon_shared.cpp b/game/shared/swarm/asw_weapon_shared.cpp index 468d7090..6ce17ac9 100644 --- a/game/shared/swarm/asw_weapon_shared.cpp +++ b/game/shared/swarm/asw_weapon_shared.cpp @@ -885,6 +885,7 @@ bool CASW_Weapon::ASWReload( int iClipSize1, int iClipSize2, int iActivity ) case 2: flFastReloadWidth = random->RandomFloat( 0.10f, 0.1f ); break; // easy/normal case 3: flFastReloadWidth = random->RandomFloat( 0.08f, 0.12f ); break; // hard case 4: flFastReloadWidth = random->RandomFloat( 0.06f, 0.10f ); break; // insane + case 5: flFastReloadWidth = random->RandomFloat( 0.055f, 0.09f ); break; // imba } // scale by marine skills flFastReloadWidth *= MarineSkills()->GetSkillBasedValueByMarine( pMarine, ASW_MARINE_SKILL_RELOADING, ASW_MARINE_SUBSKILL_RELOADING_FAST_WIDTH_SCALE ); diff --git a/lib/public/vmpi.lib b/lib/public/vmpi.lib index 10632469b48331284b50d2297ea2d594af42d2f4..98f49679c36b30b895ad42738833913e8ca76761 100644 GIT binary patch delta 32795 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 delta 31429 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 gwWZc8c>ojtR3#1ru*rX_Qhk4MfUm 4 ) + && platform[ nLen - 4 ] == CORRECT_PATH_SEPARATOR + && !Q_stricmp( "bin", platform + ( nLen - 3 ) ) + ) + { + Q_StripLastDir( platform, sizeof( platform ) ); + Q_StripTrailingSlash( platform ); + } + // go into platform folder + Q_strncat( platform, "/platform", MAX_PATH, MAX_PATH ); + } } pFileSystem->AddSearchPath( platform, "PLATFORM" );