diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/cstrike | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/cstrike')
86 files changed, 31341 insertions, 0 deletions
diff --git a/game/shared/cstrike/achievements_cs.cpp b/game/shared/cstrike/achievements_cs.cpp new file mode 100644 index 0000000..9996589 --- /dev/null +++ b/game/shared/cstrike/achievements_cs.cpp @@ -0,0 +1,727 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include <time.h> + +#ifdef CLIENT_DLL + +#include "achievementmgr.h" +#include "baseachievement.h" +#include "cs_achievement_constants.h" +#include "c_cs_team.h" +#include "c_cs_player.h" +#include "c_cs_playerresource.h" +#include "cs_gamerules.h" +#include "achievements_cs.h" +#include "cs_gamestats_shared.h" +#include "cs_client_gamestats.h" + + +// [dwenger] Necessary for stats display +#include "cs_achievements_and_stats_interface.h" + +// [dwenger] Necessary for sorting achievements by award time +#include <vgui/ISystem.h> +#include "../../src/public/vgui_controls/Controls.h" + + +ConVar achievements_easymode( "achievement_easymode", "0", FCVAR_CLIENTDLL | FCVAR_DEVELOPMENTONLY, + "Enables all stat-based achievements to be earned at 10% of goals" ); + +// global achievement mgr for CS +CAchievementMgr g_AchievementMgrCS; + +// [dwenger] Necessary for achievement / stats panel +CSAchievementsAndStatsInterface g_AchievementsAndStatsInterfaceCS; + + +CCSBaseAchievement::CCSBaseAchievement() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Determines the timestamp when the achievement is awarded +//----------------------------------------------------------------------------- +void CCSBaseAchievement::OnAchieved() +{ +// __time32_t unlockTime; +// _time32(&unlockTime); +// SetUnlockTime(unlockTime); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the time values when the achievement was awarded. +//----------------------------------------------------------------------------- +bool CCSBaseAchievement::GetAwardTime( int& year, int& month, int& day, int& hour, int& minute, int& second ) +{ + time_t t = GetUnlockTime(); + + if ( t != 0 ) + { + struct tm structuredTime; + + Plat_localtime(&t, &structuredTime); + + year = structuredTime.tm_year + 1900; + month = structuredTime.tm_mon + 1; // 0..11 + day = structuredTime.tm_mday; + hour = structuredTime.tm_hour; + minute = structuredTime.tm_min; + second = structuredTime.tm_sec; + + return true; + } + + return false; +} + +void CCSBaseAchievement::GetSettings( KeyValues* pNodeOut ) +{ + BaseClass::GetSettings(pNodeOut); + + pNodeOut->SetInt("unlockTime", GetUnlockTime()); +} + +void CCSBaseAchievement::ApplySettings( /* const */ KeyValues* pNodeIn ) +{ + BaseClass::ApplySettings(pNodeIn); + + SetUnlockTime(pNodeIn->GetInt("unlockTime")); +} + + +/** +* Meta Achievement base class methods +*/ +CAchievement_Meta::CAchievement_Meta() : + m_CallbackUserAchievement( this, &CAchievement_Meta::Steam_OnUserAchievementStored ) +{ +} + +void CAchievement_Meta::Init() +{ + SetFlags( ACH_SAVE_GLOBAL ); + SetGoal( 1 ); +} + +void CAchievement_Meta::Steam_OnUserAchievementStored( UserAchievementStored_t *pUserAchievementStored ) +{ + if ( IsAchieved() ) + return; + + int iAchieved = 0; + + FOR_EACH_VEC(m_requirements, i) + { + IAchievement* pAchievement = (IAchievement*)m_pAchievementMgr->GetAchievementByID(m_requirements[i]); + Assert ( pAchievement ); + + if ( pAchievement->IsAchieved() ) + iAchieved++; + else + break; + } + + if ( iAchieved == m_requirements.Count() ) + { + AwardAchievement(); + } +} + +void CAchievement_Meta::AddRequirement( int nAchievementId ) +{ + m_requirements.AddToTail(nAchievementId); +} + +#if 0 +bool CheckWinNoEnemyCaps( IGameEvent *event, int iRole ); + +// Grace period that we allow a player to start after level init and still consider them to be participating for the full round. This is fairly generous +// because it can in some cases take a client several minutes to connect with respect to when the server considers the game underway +#define CS_FULL_ROUND_GRACE_PERIOD ( 4 * 60.0f ) + +bool IsLocalCSPlayerClass( int iClass ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSBaseAchievementFullRound::Init() +{ + m_iFlags |= ACH_FILTER_FULL_ROUND_ONLY; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSBaseAchievementFullRound::ListenForEvents() +{ + ListenForGameEvent( "teamplay_round_win" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSBaseAchievementFullRound::FireGameEvent_Internal( IGameEvent *event ) +{ + if ( 0 == Q_strcmp( event->GetName(), "teamplay_round_win" ) ) + { + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + // is the player currently on a game team? + int iTeam = pLocalPlayer->GetTeamNumber(); + if ( iTeam >= FIRST_GAME_TEAM ) + { + float flRoundTime = event->GetFloat( "round_time", 0 ); + if ( flRoundTime > 0 ) + { + Event_OnRoundComplete( flRoundTime, event ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCSBaseAchievementFullRound::PlayerWasInEntireRound( float flRoundTime ) +{ + float flTeamplayStartTime = m_pAchievementMgr->GetTeamplayStartTime(); + if ( flTeamplayStartTime > 0 ) + { + // has the player been present and on a game team since the start of this round (minus a grace period)? + if ( flTeamplayStartTime < ( gpGlobals->curtime - flRoundTime ) + CS_FULL_ROUND_GRACE_PERIOD ) + return true; + } + return false; +} +#endif + +//Base class for all achievements to kill x players with a given weapon +class CAchievement_StatGoal: public CCSBaseAchievement +{ +public: + void SetStatId(CSStatType_t stat) + { + m_StatId = stat; + } +private: + virtual void Init() + { + SetFlags( ACH_SAVE_GLOBAL ); + } + + void OnPlayerStatsUpdate() + { + // when stats are updated by server, use most recent stat value + const StatsCollection_t& roundStats = g_CSClientGameStats.GetLifetimeStats(); + + int iOldCount = GetCount(); + SetCount(roundStats[m_StatId]); + if ( GetCount() != iOldCount ) + { + m_pAchievementMgr->SetDirty(true); + } + + int iGoal = GetGoal(); + if (!IsAchieved() && iGoal > 0 ) + { + if (achievements_easymode.GetBool()) + { + iGoal /= 10; + if ( iGoal == 0 ) + iGoal = 1; + } + + if ( GetCount() >= iGoal ) + { + AwardAchievement(); + } + } + } + CSStatType_t m_StatId; +}; + +#define DECLARE_ACHIEVEMENT_STATGOAL( achievementID, achievementName, iPointValue, iStatId, iGoal ) \ + static CBaseAchievement *Create_##achievementID( void ) \ +{ \ + CAchievement_StatGoal *pAchievement = new CAchievement_StatGoal(); \ + pAchievement->SetAchievementID( achievementID ); \ + pAchievement->SetName( achievementName ); \ + pAchievement->SetPointValue( iPointValue ); \ + pAchievement->SetHideUntilAchieved( false ); \ + pAchievement->SetStatId(iStatId); \ + pAchievement->SetGoal( iGoal ); \ + return pAchievement; \ +}; \ +static CBaseAchievementHelper g_##achievementID##_Helper( Create_##achievementID ); + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsLow, "KILL_ENEMY_LOW", 10, CSSTAT_KILLS, 25); //25 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsMed, "KILL_ENEMY_MED", 10, CSSTAT_KILLS, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsHigh, "KILL_ENEMY_HIGH", 10, CSSTAT_KILLS, 10000); //10000 + +DECLARE_ACHIEVEMENT_STATGOAL(CSWinRoundsLow, "WIN_ROUNDS_LOW", 10, CSSTAT_ROUNDS_WON, 10); //10 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinRoundsMed, "WIN_ROUNDS_MED", 10, CSSTAT_ROUNDS_WON, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinRoundsHigh, "WIN_ROUNDS_HIGH", 10, CSSTAT_ROUNDS_WON, 5000); //5000 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinPistolRoundsLow, "WIN_PISTOLROUNDS_LOW", 10, CSSTAT_PISTOLROUNDS_WON, 5); //5 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinPistolRoundsMed, "WIN_PISTOLROUNDS_MED", 10, CSSTAT_PISTOLROUNDS_WON, 25); //25 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinPistolRoundsHigh, "WIN_PISTOLROUNDS_HIGH", 10, CSSTAT_PISTOLROUNDS_WON, 250); //250 + +DECLARE_ACHIEVEMENT_STATGOAL(CSMoneyEarnedLow, "EARN_MONEY_LOW", 10, CSSTAT_MONEY_EARNED, 125000); //125000 +DECLARE_ACHIEVEMENT_STATGOAL(CSMoneyEarnedMed, "EARN_MONEY_MED", 10, CSSTAT_MONEY_EARNED, 2500000); //2500000 +DECLARE_ACHIEVEMENT_STATGOAL(CSMoneyEarnedHigh, "EARN_MONEY_HIGH", 10, CSSTAT_MONEY_EARNED, 50000000); //50000000 + +DECLARE_ACHIEVEMENT_STATGOAL(CSGiveDamageLow, "GIVE_DAMAGE_LOW", 10, CSSTAT_DAMAGE, 2500); //2500 +DECLARE_ACHIEVEMENT_STATGOAL(CSGiveDamageMed, "GIVE_DAMAGE_MED", 10, CSSTAT_DAMAGE, 50000); //50000 +DECLARE_ACHIEVEMENT_STATGOAL(CSGiveDamageHigh, "GIVE_DAMAGE_HIGH", 10, CSSTAT_DAMAGE, 1000000); //1000000 + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsDeagle, "KILL_ENEMY_DEAGLE", 5, CSSTAT_KILLS_DEAGLE, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsUSP, "KILL_ENEMY_USP", 5, CSSTAT_KILLS_USP, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsGlock, "KILL_ENEMY_GLOCK", 5, CSSTAT_KILLS_GLOCK, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsP228, "KILL_ENEMY_P228", 5, CSSTAT_KILLS_P228, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsElite, "KILL_ENEMY_ELITE", 5, CSSTAT_KILLS_ELITE, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsFiveSeven, "KILL_ENEMY_FIVESEVEN", 5, CSSTAT_KILLS_FIVESEVEN, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsAWP, "KILL_ENEMY_AWP", 5, CSSTAT_KILLS_AWP, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsAK47, "KILL_ENEMY_AK47", 5, CSSTAT_KILLS_AK47, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsM4A1, "KILL_ENEMY_M4A1", 5, CSSTAT_KILLS_M4A1, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsAUG, "KILL_ENEMY_AUG", 5, CSSTAT_KILLS_AUG, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsSG552, "KILL_ENEMY_SG552", 5, CSSTAT_KILLS_SG552, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsSG550, "KILL_ENEMY_SG550", 5, CSSTAT_KILLS_SG550, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsGALIL, "KILL_ENEMY_GALIL", 5, CSSTAT_KILLS_GALIL, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsFAMAS, "KILL_ENEMY_FAMAS", 5, CSSTAT_KILLS_FAMAS, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsScout, "KILL_ENEMY_SCOUT", 5, CSSTAT_KILLS_SCOUT, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsG3SG1, "KILL_ENEMY_G3SG1", 5, CSSTAT_KILLS_G3SG1, 500); //500 + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsP90, "KILL_ENEMY_P90", 5, CSSTAT_KILLS_P90, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsMP5NAVY, "KILL_ENEMY_MP5NAVY", 5, CSSTAT_KILLS_MP5NAVY, 1000); //1000 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsTMP, "KILL_ENEMY_TMP", 5, CSSTAT_KILLS_TMP, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsMAC10, "KILL_ENEMY_MAC10", 5, CSSTAT_KILLS_MAC10, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsUMP45, "KILL_ENEMY_UMP45", 5, CSSTAT_KILLS_UMP45, 1000); //1000 + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsM3, "KILL_ENEMY_M3", 5, CSSTAT_KILLS_M3, 200); //200 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsXM1014, "KILL_ENEMY_XM1014", 5, CSSTAT_KILLS_XM1014, 200); //200 + +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsM249, "KILL_ENEMY_M249", 5, CSSTAT_KILLS_M249, 500); //500 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsKnife, "KILL_ENEMY_KNIFE", 5, CSSTAT_KILLS_KNIFE, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSEnemyKillsHEGrenade, "KILL_ENEMY_HEGRENADE", 5, CSSTAT_KILLS_HEGRENADE, 500); //500 + +DECLARE_ACHIEVEMENT_STATGOAL(CSHeadshots, "HEADSHOTS", 5, CSSTAT_KILLS_HEADSHOT, 250); //250 +DECLARE_ACHIEVEMENT_STATGOAL(CSKillsEnemyWeapon, "KILLS_ENEMY_WEAPON", 5, CSSTAT_KILLS_ENEMY_WEAPON, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSKillEnemyBlinded, "KILL_ENEMY_BLINDED", 5, CSSTAT_KILLS_ENEMY_BLINDED, 25); //25 + +DECLARE_ACHIEVEMENT_STATGOAL(CSDefuseBombsLow, "BOMB_DEFUSE_LOW", 5, CSSTAT_NUM_BOMBS_DEFUSED, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSPlantBombsLow, "BOMB_PLANT_LOW", 5, CSSTAT_NUM_BOMBS_PLANTED, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSRescueHostagesLow, "RESCUE_HOSTAGES_LOW", 5, CSSTAT_NUM_HOSTAGES_RESCUED, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSRescueHostagesMid, "RESCUE_HOSTAGES_MED", 5, CSSTAT_NUM_HOSTAGES_RESCUED, 500); //500 + +DECLARE_ACHIEVEMENT_STATGOAL(CSWinKnifeFightsLow, "WIN_KNIFE_FIGHTS_LOW", 5, CSSTAT_KILLS_KNIFE_FIGHT, 1); //1 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinKnifeFightsHigh, "WIN_KNIFE_FIGHTS_HIGH", 5, CSSTAT_KILLS_KNIFE_FIGHT, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSDecalSprays, "DECAL_SPRAYS", 5, CSSTAT_DECAL_SPRAYS, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSNightvisionDamage, "NIGHTVISION_DAMAGE", 5, CSSTAT_NIGHTVISION_DAMAGE, 5000); //5000 + +DECLARE_ACHIEVEMENT_STATGOAL(CSKillSnipers, "KILL_SNIPERS", 5, CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_ASSAULT, "WIN_MAP_CS_ASSAULT", 5, CSSTAT_MAP_WINS_CS_ASSAULT, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_COMPOUND, "WIN_MAP_CS_COMPOUND", 5, CSSTAT_MAP_WINS_CS_COMPOUND, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_HAVANA, "WIN_MAP_CS_HAVANA", 5, CSSTAT_MAP_WINS_CS_HAVANA, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_ITALY, "WIN_MAP_CS_ITALY", 5, CSSTAT_MAP_WINS_CS_ITALY, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_MILITIA, "WIN_MAP_CS_MILITIA", 5, CSSTAT_MAP_WINS_CS_MILITIA, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapCS_OFFICE, "WIN_MAP_CS_OFFICE", 5, CSSTAT_MAP_WINS_CS_OFFICE, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_AZTEC, "WIN_MAP_DE_AZTEC", 5, CSSTAT_MAP_WINS_DE_AZTEC, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_CBBLE, "WIN_MAP_DE_CBBLE", 5, CSSTAT_MAP_WINS_DE_CBBLE, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_CHATEAU, "WIN_MAP_DE_CHATEAU", 5, CSSTAT_MAP_WINS_DE_CHATEAU, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_DUST2, "WIN_MAP_DE_DUST2", 5, CSSTAT_MAP_WINS_DE_DUST2, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_DUST, "WIN_MAP_DE_DUST", 5, CSSTAT_MAP_WINS_DE_DUST, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_INFERNO, "WIN_MAP_DE_INFERNO", 5, CSSTAT_MAP_WINS_DE_INFERNO, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_NUKE, "WIN_MAP_DE_NUKE", 5, CSSTAT_MAP_WINS_DE_NUKE, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_PIRANESI, "WIN_MAP_DE_PIRANESI", 5, CSSTAT_MAP_WINS_DE_PIRANESI, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_PORT, "WIN_MAP_DE_PORT", 5, CSSTAT_MAP_WINS_DE_PORT, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_PRODIGY, "WIN_MAP_DE_PRODIGY", 5, CSSTAT_MAP_WINS_DE_PRODIGY, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_TIDES, "WIN_MAP_DE_TIDES", 5, CSSTAT_MAP_WINS_DE_TIDES, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSWinMapDE_TRAIN, "WIN_MAP_DE_TRAIN", 5, CSSTAT_MAP_WINS_DE_TRAIN, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSDonateWeapons, "DONATE_WEAPONS", 5, CSSTAT_WEAPONS_DONATED, 100); //100 + +DECLARE_ACHIEVEMENT_STATGOAL(CSDominationsLow, "DOMINATIONS_LOW", 5, CSSTAT_DOMINATIONS, 1); //1 +DECLARE_ACHIEVEMENT_STATGOAL(CSDominationsHigh, "DOMINATIONS_HIGH", 5, CSSTAT_DOMINATIONS, 10); //10 +DECLARE_ACHIEVEMENT_STATGOAL(CSDominationOverkillsLow, "DOMINATION_OVERKILLS_LOW", 5, CSSTAT_DOMINATION_OVERKILLS, 1); //1 +DECLARE_ACHIEVEMENT_STATGOAL(CSDominationOverkillsHigh, "DOMINATION_OVERKILLS_HIGH",5, CSSTAT_DOMINATION_OVERKILLS, 100); //100 +DECLARE_ACHIEVEMENT_STATGOAL(CSRevengesLow, "REVENGES_LOW", 5, CSSTAT_REVENGES, 1); //1 +DECLARE_ACHIEVEMENT_STATGOAL(CSRevengesHigh, "REVENGES_HIGH", 5, CSSTAT_REVENGES, 20); //20 + + +//----------------------------------------------------------------------------- +// Purpose: Generic server awarded achievement +//----------------------------------------------------------------------------- +class CAchievementCS_ServerAwarded : public CCSBaseAchievement +{ + virtual void Init() + { + SetGoal(1); + SetFlags( ACH_SAVE_GLOBAL ); + } + + // server fires an event for this achievement, no other code within achievement necessary +}; + +#define DECLARE_ACHIEVEMENT_SERVERAWARDED(achievementID, achievementName, iPointValue) \ + static CBaseAchievement *Create_##achievementID( void ) \ +{ \ + CAchievementCS_ServerAwarded *pAchievement = new CAchievementCS_ServerAwarded( ); \ + pAchievement->SetAchievementID( achievementID ); \ + pAchievement->SetName( achievementName ); \ + pAchievement->SetPointValue( iPointValue ); \ + return pAchievement; \ +}; \ +static CBaseAchievementHelper g_##achievementID##_Helper( Create_##achievementID ); + + + +// server triggered achievements +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSBombDefuseCloseCall, "BOMB_DEFUSE_CLOSE_CALL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSDefuseAndNeededKit, "BOMB_DEFUSE_NEEDED_KIT", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKilledDefuser, "KILL_BOMB_DEFUSER", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinBombPlant, "WIN_BOMB_PLANT", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinBombDefuse, "WIN_BOMB_DEFUSE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSPlantBombWithin25Seconds, "BOMB_PLANT_IN_25_SECONDS", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSRescueAllHostagesInARound, "RESCUE_ALL_HOSTAGES", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemyWithFormerGun, "KILL_WITH_OWN_GUN", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillingSpree, "KILLING_SPREE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillTwoWithOneShot, "KILL_TWO_WITH_ONE_SHOT", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemyReloading, "KILL_ENEMY_RELOADING", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillsWithMultipleGuns, "KILLS_WITH_MULTIPLE_GUNS", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSPosthumousGrenadeKill, "DEAD_GRENADE_KILL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemyTeam, "KILL_ENEMY_TEAM", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSLastPlayerAlive, "LAST_PLAYER_ALIVE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemyLastBullet, "KILL_ENEMY_LAST_BULLET", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillingSpreeEnder, "KILLING_SPREE_ENDER", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemiesWhileBlind, "KILL_ENEMIES_WHILE_BLIND", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemiesWhileBlindHard, "KILL_ENEMIES_WHILE_BLIND_HARD", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSDamageNoKill, "DAMAGE_NO_KILL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillLowDamage, "KILL_LOW_DAMAGE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKilledRescuer, "KILL_HOSTAGE_RESCUER", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSurviveGrenade, "SURVIVE_GRENADE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKilledDefuserWithGrenade, "KILLED_DEFUSER_WITH_GRENADE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSurvivedHeadshotDueToHelmet, "SURVIVED_HEADSHOT_DUE_TO_HELMET", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillSniperWithSniper, "KILL_SNIPER_WITH_SNIPER", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillSniperWithKnife, "KILL_SNIPER_WITH_KNIFE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSHipShot, "HIP_SHOT", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillWhenAtLowHealth, "KILL_WHEN_AT_LOW_HEALTH", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSGrenadeMultikill, "GRENADE_MULTIKILL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSBombMultikill, "BOMB_MULTIKILL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSPistolRoundKnifeKill, "PISTOL_ROUND_KNIFE_KILL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSFastRoundWin, "FAST_ROUND_WIN", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSurviveManyAttacks, "SURVIVE_MANY_ATTACKS", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSGooseChase, "GOOSE_CHASE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinBombPlantAfterRecovery, "WIN_BOMB_PLANT_AFTER_RECOVERY", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSLosslessExtermination, "LOSSLESS_EXTERMINATION", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSFlawlessVictory, "FLAWLESS_VICTORY", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinDualDuel, "WIN_DUAL_DUEL", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSFastHostageRescue, "FAST_HOSTAGE_RESCUE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSBreakWindows, "BREAK_WINDOWS", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSBreakProps, "BREAK_PROPS", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSUnstoppableForce, "UNSTOPPABLE_FORCE", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSImmovableObject, "IMMOVABLE_OBJECT", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSHeadshotsInRound, "HEADSHOTS_IN_ROUND", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillWhileInAir, "KILL_WHILE_IN_AIR", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillEnemyInAir, "KILL_ENEMY_IN_AIR", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillerAndEnemyInAir, "KILLER_AND_ENEMY_IN_AIR", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSilentWin, "SILENT_WIN", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSBloodlessVictory, "BLOODLESS_VICTORY", 5); +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinRoundsWithoutBuying, "WIN_ROUNDS_WITHOUT_BUYING", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSDefuseDefense, "DEFUSE_DEFENSE", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSKillBombPickup, "KILL_BOMB_PICKUP", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSameUniform, "SAME_UNIFORM", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSConcurrentDominations, "CONCURRENT_DOMINATIONS", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSDominationOverkillsMatch, "DOMINATION_OVERKILLS_MATCH", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSExtendedDomination, "EXTENDED_DOMINATION", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSCauseFriendlyFireWithFlashbang, "CAUSE_FRIENDLY_FIRE_WITH_FLASHBANG", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSWinClanMatch, "WIN_CLAN_MATCH", 5) +DECLARE_ACHIEVEMENT_SERVERAWARDED(CSSnipeTwoFromSameSpot, "SNIPE_TWO_FROM_SAME_SPOT", 5 ) + + + + +//----------------------------------------------------------------------------- +// Meta Achievements +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: Get all the pistol achievements +//----------------------------------------------------------------------------- +class CAchievementCS_PistolMaster : public CAchievement_Meta +{ + DECLARE_CLASS( CAchievementCS_PistolMaster, CAchievement_Meta ); + virtual void Init() + { + BaseClass::Init(); + AddRequirement(CSEnemyKillsDeagle); + AddRequirement(CSEnemyKillsUSP); + AddRequirement(CSEnemyKillsGlock); + AddRequirement(CSEnemyKillsP228); + AddRequirement(CSEnemyKillsElite); + AddRequirement(CSEnemyKillsFiveSeven); + } +}; +DECLARE_ACHIEVEMENT(CAchievementCS_PistolMaster, CSMetaPistol, "META_PISTOL", 10); + +//----------------------------------------------------------------------------- +// Purpose: Get all the rifle achievements +//----------------------------------------------------------------------------- +class CAchievementCS_RifleMaster : public CAchievement_Meta +{ + DECLARE_CLASS( CAchievementCS_RifleMaster, CAchievement_Meta ); + virtual void Init() + { + BaseClass::Init(); + AddRequirement(CSEnemyKillsAWP); + AddRequirement(CSEnemyKillsAK47); + AddRequirement(CSEnemyKillsM4A1); + AddRequirement(CSEnemyKillsAUG); + AddRequirement(CSEnemyKillsSG552); + AddRequirement(CSEnemyKillsSG550); + AddRequirement(CSEnemyKillsGALIL); + AddRequirement(CSEnemyKillsFAMAS); + AddRequirement(CSEnemyKillsScout); + AddRequirement(CSEnemyKillsG3SG1); + } +}; +DECLARE_ACHIEVEMENT(CAchievementCS_RifleMaster, CSMetaRifle, "META_RIFLE", 10); + +//----------------------------------------------------------------------------- +// Purpose: Get all the SMG achievements +//----------------------------------------------------------------------------- +class CAchievementCS_SubMachineGunMaster : public CAchievement_Meta +{ + DECLARE_CLASS( CAchievementCS_SubMachineGunMaster, CAchievement_Meta ); + virtual void Init() + { + BaseClass::Init(); + AddRequirement(CSEnemyKillsP90); + AddRequirement(CSEnemyKillsMP5NAVY); + AddRequirement(CSEnemyKillsTMP); + AddRequirement(CSEnemyKillsMAC10); + AddRequirement(CSEnemyKillsUMP45); + } +}; +DECLARE_ACHIEVEMENT(CAchievementCS_SubMachineGunMaster, CSMetaSMG, "META_SMG", 10); + +//----------------------------------------------------------------------------- +// Purpose: Get all the Shotgun achievements +//----------------------------------------------------------------------------- +class CAchievementCS_ShotgunMaster : public CAchievement_Meta +{ + DECLARE_CLASS( CAchievementCS_ShotgunMaster, CAchievement_Meta ); + virtual void Init() + { + BaseClass::Init(); + AddRequirement(CSEnemyKillsM3); + AddRequirement(CSEnemyKillsXM1014); + } +}; +DECLARE_ACHIEVEMENT(CAchievementCS_ShotgunMaster, CSMetaShotgun, "META_SHOTGUN", 10); + +//----------------------------------------------------------------------------- +// Purpose: Get every weapon achievement +//----------------------------------------------------------------------------- +class CAchievementCS_WeaponMaster : public CAchievement_Meta +{ + DECLARE_CLASS( CAchievementCS_WeaponMaster, CAchievement_Meta ); + virtual void Init() + { + BaseClass::Init(); + AddRequirement(CSMetaPistol); + AddRequirement(CSMetaRifle); + AddRequirement(CSMetaSMG); + AddRequirement(CSMetaShotgun); + AddRequirement(CSEnemyKillsM249); + AddRequirement(CSEnemyKillsKnife); + AddRequirement(CSEnemyKillsHEGrenade); + } +}; +DECLARE_ACHIEVEMENT(CAchievementCS_WeaponMaster, CSMetaWeaponMaster, "META_WEAPONMASTER", 50); + + + +class CAchievementCS_KillWithAllWeapons : public CCSBaseAchievement +{ + void Init() + { + SetFlags( ACH_SAVE_GLOBAL ); + SetGoal( 1 ); + } + + void OnPlayerStatsUpdate() + { + const StatsCollection_t& roundStats = g_CSClientGameStats.GetLifetimeStats(); + + //Loop through all weapons we care about and make sure we got a kill with each. + for (int i = 0; WeaponName_StatId_Table[i].killStatId != CSSTAT_UNDEFINED; ++i) + { + CSStatType_t statId = WeaponName_StatId_Table[i].killStatId; + + if ( roundStats[statId] == 0) + { + return; + } + } + + //If we haven't bailed yet, award the achievement. + IncrementCount(); + } +}; +DECLARE_ACHIEVEMENT( CAchievementCS_KillWithAllWeapons, CSKillWithEveryWeapon, "KILL_WITH_EVERY_WEAPON", 5 ); + +class CAchievementCS_FriendsSameUniform : public CCSBaseAchievement +{ + void Init() + { + SetFlags(ACH_SAVE_GLOBAL); + SetGoal(1); + } + + void ListenForEvents() + { + ListenForGameEvent( "round_start" ); + } + + void FireGameEvent_Internal( IGameEvent *event ) + { + if ( Q_strcmp( event->GetName(), "round_start" ) == 0 ) + { + int localPlayerIndex = GetLocalPlayerIndex(); + C_CSPlayer* pLocalPlayer = ToCSPlayer(UTIL_PlayerByIndex(localPlayerIndex)); + + // Initialize all to 1, since the local player doesn't get counted as we loop. + int numPlayersOnTeam = 1; + int numFriendsOnTeam = 1; + int numMatchingFriendsOnTeam = 1; + + if (pLocalPlayer) + { + int localPlayerClass = pLocalPlayer->PlayerClass(); + int localPlayerTeam = pLocalPlayer->GetTeamNumber(); + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + if ( i != localPlayerIndex) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if (pPlayer) + { + if (pPlayer->GetTeamNumber() == localPlayerTeam) + { + ++numPlayersOnTeam; + if (pLocalPlayer->HasPlayerAsFriend(pPlayer) ) + { + ++numFriendsOnTeam; + if ( pPlayer->PlayerClass() == localPlayerClass ) + ++numMatchingFriendsOnTeam; + } + } + } + } + } + + if (numMatchingFriendsOnTeam >= AchievementConsts::FriendsSameUniform_MinPlayers ) + { + AwardAchievement(); + } + } + } + } +}; +DECLARE_ACHIEVEMENT( CAchievementCS_FriendsSameUniform, CSFriendsSameUniform, "FRIENDS_SAME_UNIFORM", 5 ); + + + +class CAchievementCS_AvengeFriend : public CCSBaseAchievement +{ + void Init() + { + SetFlags(ACH_SAVE_GLOBAL); + SetGoal(1); + } + + void ListenForEvents() + { + ListenForGameEvent( "player_avenged_teammate" ); + } + + void FireGameEvent_Internal( IGameEvent *event ) + { + if ( Q_strcmp( event->GetName(), "player_avenged_teammate" ) == 0 ) + { + int localPlayerIndex = GetLocalPlayerIndex(); + C_CSPlayer* pLocalPlayer = ToCSPlayer(UTIL_PlayerByIndex(localPlayerIndex)); + + //for debugging + //int eventId = event->GetInt( "avenger_id" ); + //int localUserId = pLocalPlayer->GetUserID(); + + if (pLocalPlayer && pLocalPlayer->GetUserID() == event->GetInt( "avenger_id" )) + { + int avengedPlayerIndex = engine->GetPlayerForUserID( event->GetInt( "avenged_player_id" ) ); + + if ( avengedPlayerIndex > 0 ) + { + C_CSPlayer* pAvengedPlayer = ToCSPlayer(UTIL_PlayerByIndex(avengedPlayerIndex)); + if (pAvengedPlayer && pLocalPlayer->HasPlayerAsFriend(pAvengedPlayer)) + { + AwardAchievement(); + } + } + } + } + } +}; +DECLARE_ACHIEVEMENT( CAchievementCS_AvengeFriend, CSAvengeFriend, "AVENGE_FRIEND", 5 ); + + + +class CAchievementCS_CollectHolidayGifts : public CCSBaseAchievement +{ + void Init() + { + SetFlags( ACH_SAVE_GLOBAL ); + SetGoal( 3 ); + SetStoreProgressInSteam( true ); + } + + virtual void ListenForEvents() + { + ListenForGameEvent( "christmas_gift_grab" ); + } + + void FireGameEvent_Internal( IGameEvent *event ) + { + if ( !UTIL_IsHolidayActive( 3 /*kHoliday_Christmas*/ ) ) + return; + + if ( Q_strcmp( event->GetName(), "christmas_gift_grab" ) == 0 ) + { + int iPlayer = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); + CBaseEntity *pPlayer = UTIL_PlayerByIndex( iPlayer ); + + if ( pPlayer && pPlayer == C_CSPlayer::GetLocalCSPlayer() ) + { + IncrementCount(); + } + } + } +}; +DECLARE_ACHIEVEMENT( CAchievementCS_CollectHolidayGifts, CSCollectHolidayGifts, "COLLECT_GIFTS", 5 ); + +#endif // CLIENT_DLL + diff --git a/game/shared/cstrike/achievements_cs.h b/game/shared/cstrike/achievements_cs.h new file mode 100644 index 0000000..817a73e --- /dev/null +++ b/game/shared/cstrike/achievements_cs.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + + +#include "cbase.h" + +#ifdef CLIENT_DLL + +bool CheckWinNoEnemyCaps( IGameEvent *event, int iRole ); +bool IsLocalCSPlayerClass( int iClass ); +bool GameRulesAllowsAchievements( void ); + +//---------------------------------------------------------------------------------------------------------------- +// Base class for all CS achievements +class CCSBaseAchievement : public CBaseAchievement +{ + DECLARE_CLASS( CCSBaseAchievement, CBaseAchievement ); +public: + + CCSBaseAchievement(); + + virtual void GetSettings( KeyValues* pNodeOut ); // serialize + virtual void ApplySettings( /* const */ KeyValues* pNodeIn ); // unserialize + + // [dwenger] Necessary for sorting achievements by award time + virtual void OnAchieved(); + bool GetAwardTime( int& year, int& month, int& day, int& hour, int& minute, int& second ); + + int64 GetSortKey() const { return GetUnlockTime(); } +}; + + +//---------------------------------------------------------------------------------------------------------------- +// Helper class for achievements that check that the player was playing on a game team for the full round +class CCSBaseAchievementFullRound : public CCSBaseAchievement +{ + DECLARE_CLASS( CCSBaseAchievementFullRound, CCSBaseAchievement ); +public: + virtual void Init() ; + virtual void ListenForEvents(); + void FireGameEvent_Internal( IGameEvent *event ); + bool PlayerWasInEntireRound( float flRoundTime ); + + virtual void Event_OnRoundComplete( float flRoundTime, IGameEvent *event ) = 0 ; +}; + + +//---------------------------------------------------------------------------------------------------------------- +// Helper class for achievements based on other achievements +class CAchievement_Meta : public CCSBaseAchievement +{ + DECLARE_CLASS( CAchievement_Meta, CCSBaseAchievement ); +public: + CAchievement_Meta(); + virtual void Init(); + +#if !defined(NO_STEAM) + STEAM_CALLBACK( CAchievement_Meta, Steam_OnUserAchievementStored, UserAchievementStored_t, m_CallbackUserAchievement ); +#endif + +protected: + void AddRequirement( int nAchievementId ); + +private: + CUtlVector<int> m_requirements; +}; + + + +extern CAchievementMgr g_AchievementMgrCS; // global achievement mgr for CS + +#endif // CLIENT_DLL
\ No newline at end of file diff --git a/game/shared/cstrike/basecsgrenade_projectile.cpp b/game/shared/cstrike/basecsgrenade_projectile.cpp new file mode 100644 index 0000000..5d6641b --- /dev/null +++ b/game/shared/cstrike/basecsgrenade_projectile.cpp @@ -0,0 +1,346 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "basecsgrenade_projectile.h" + +float GetCurrentGravity( void ); + + +#ifdef CLIENT_DLL + + #include "c_cs_player.h" + +#else + + #include "bot_manager.h" + #include "cs_player.h" + #include "soundent.h" + #include "te_effect_dispatch.h" + #include "KeyValues.h" + + BEGIN_DATADESC( CBaseCSGrenadeProjectile ) + DEFINE_THINKFUNC( DangerSoundThink ), + END_DATADESC() + +#endif + + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseCSGrenadeProjectile, DT_BaseCSGrenadeProjectile ) + +BEGIN_NETWORK_TABLE( CBaseCSGrenadeProjectile, DT_BaseCSGrenadeProjectile ) + #ifdef CLIENT_DLL + RecvPropVector( RECVINFO( m_vInitialVelocity ) ) + #else + SendPropVector( SENDINFO( m_vInitialVelocity ), + 20, // nbits + 0, // flags + -3000, // low value + 3000 // high value + ) + #endif +END_NETWORK_TABLE() + + +#ifdef CLIENT_DLL + + + void CBaseCSGrenadeProjectile::PostDataUpdate( DataUpdateType_t type ) + { + BaseClass::PostDataUpdate( type ); + + if ( type == DATA_UPDATE_CREATED ) + { + // Now stick our initial velocity into the interpolation history + CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator(); + + interpolator.ClearHistory(); + float changeTime = GetLastChangeTime( LATCH_SIMULATION_VAR ); + + // Add a sample 1 second back. + Vector vCurOrigin = GetLocalOrigin() - m_vInitialVelocity; + interpolator.AddToHead( changeTime - 1.0, &vCurOrigin, false ); + + // Add the current sample. + vCurOrigin = GetLocalOrigin(); + interpolator.AddToHead( changeTime, &vCurOrigin, false ); + } + } + + int CBaseCSGrenadeProjectile::DrawModel( int flags ) + { + // During the first half-second of our life, don't draw ourselves if he's + // still playing his throw animation. + // (better yet, we could draw ourselves in his hand). + if ( GetThrower() != C_BasePlayer::GetLocalPlayer() ) + { + if ( gpGlobals->curtime - m_flSpawnTime < 0.5 ) + { + C_CSPlayer *pPlayer = dynamic_cast<C_CSPlayer*>( GetThrower() ); + if ( pPlayer && pPlayer->m_PlayerAnimState->IsThrowingGrenade() ) + { + return 0; + } + } + } + + return BaseClass::DrawModel( flags ); + } + + void CBaseCSGrenadeProjectile::Spawn() + { + m_flSpawnTime = gpGlobals->curtime; + BaseClass::Spawn(); + } + +#else + + void CBaseCSGrenadeProjectile::PostConstructor( const char *className ) + { + BaseClass::PostConstructor( className ); + TheBots->AddGrenade( this ); + } + + CBaseCSGrenadeProjectile::~CBaseCSGrenadeProjectile() + { + TheBots->RemoveGrenade( this ); + } + + void CBaseCSGrenadeProjectile::Spawn( void ) + { + BaseClass::Spawn(); + + SetSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); + SetSolid( SOLID_BBOX ); // So it will collide with physics props! + + // smaller, cube bounding box so we rest on the ground + SetSize( Vector ( -2, -2, -2 ), Vector ( 2, 2, 2 ) ); + } + + void CBaseCSGrenadeProjectile::DangerSoundThink( void ) + { + if (!IsInWorld()) + { + Remove( ); + return; + } + + if( gpGlobals->curtime > m_flDetonateTime ) + { + Detonate(); + return; + } + + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2 ); + + SetNextThink( gpGlobals->curtime + 0.2 ); + + if (GetWaterLevel() != 0) + { + SetAbsVelocity( GetAbsVelocity() * 0.5 ); + } + } + + //Sets the time at which the grenade will explode + void CBaseCSGrenadeProjectile::SetDetonateTimerLength( float timer ) + { + m_flDetonateTime = gpGlobals->curtime + timer; + } + + void CBaseCSGrenadeProjectile::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ) + { + //Assume all surfaces have the same elasticity + float flSurfaceElasticity = 1.0; + + //Don't bounce off of players with perfect elasticity + if( trace.m_pEnt && trace.m_pEnt->IsPlayer() ) + { + flSurfaceElasticity = 0.3; + } + + // if its breakable glass and we kill it, don't bounce. + // give some damage to the glass, and if it breaks, pass + // through it. + bool breakthrough = false; + + if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable" ) ) + { + breakthrough = true; + } + + if( trace.m_pEnt && FClassnameIs( trace.m_pEnt, "func_breakable_surf" ) ) + { + breakthrough = true; + } + + if (breakthrough) + { + CTakeDamageInfo info( this, this, 10, DMG_CLUB ); + trace.m_pEnt->DispatchTraceAttack( info, GetAbsVelocity(), &trace ); + + ApplyMultiDamage(); + + if( trace.m_pEnt->m_iHealth <= 0 ) + { + // slow our flight a little bit + Vector vel = GetAbsVelocity(); + + vel *= 0.4; + + SetAbsVelocity( vel ); + return; + } + } + + float flTotalElasticity = GetElasticity() * flSurfaceElasticity; + flTotalElasticity = clamp( flTotalElasticity, 0.0f, 0.9f ); + + // NOTE: A backoff of 2.0f is a reflection + Vector vecAbsVelocity; + PhysicsClipVelocity( GetAbsVelocity(), trace.plane.normal, vecAbsVelocity, 2.0f ); + vecAbsVelocity *= flTotalElasticity; + + // Get the total velocity (player + conveyors, etc.) + VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); + float flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); + + // Stop if on ground. + if ( trace.plane.normal.z > 0.7f ) // Floor + { + // Verify that we have an entity. + CBaseEntity *pEntity = trace.m_pEnt; + Assert( pEntity ); + + SetAbsVelocity( vecAbsVelocity ); + + if ( flSpeedSqr < ( 30 * 30 ) ) + { + if ( pEntity->IsStandable() ) + { + SetGroundEntity( pEntity ); + } + + // Reset velocities. + SetAbsVelocity( vec3_origin ); + SetLocalAngularVelocity( vec3_angle ); + + //align to the ground so we're not standing on end + QAngle angle; + VectorAngles( trace.plane.normal, angle ); + + // rotate randomly in yaw + angle[1] = random->RandomFloat( 0, 360 ); + + // TODO: rotate around trace.plane.normal + + SetAbsAngles( angle ); + } + else + { + Vector vecDelta = GetBaseVelocity() - vecAbsVelocity; + Vector vecBaseDir = GetBaseVelocity(); + VectorNormalize( vecBaseDir ); + float flScale = vecDelta.Dot( vecBaseDir ); + + VectorScale( vecAbsVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, vecVelocity ); + VectorMA( vecVelocity, ( 1.0f - trace.fraction ) * gpGlobals->frametime, GetBaseVelocity() * flScale, vecVelocity ); + PhysicsPushEntity( vecVelocity, &trace ); + } + } + else + { + // If we get *too* slow, we'll stick without ever coming to rest because + // we'll get pushed down by gravity faster than we can escape from the wall. + if ( flSpeedSqr < ( 30 * 30 ) ) + { + // Reset velocities. + SetAbsVelocity( vec3_origin ); + SetLocalAngularVelocity( vec3_angle ); + } + else + { + SetAbsVelocity( vecAbsVelocity ); + } + } + + BounceSound(); + + // tell the bots a grenade has bounced + CCSPlayer *player = ToCSPlayer(GetThrower()); + if ( player ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "grenade_bounce" ); + if ( event ) + { + event->SetInt( "userid", player->GetUserID() ); + event->SetFloat( "x", GetAbsOrigin().x ); + event->SetFloat( "y", GetAbsOrigin().y ); + event->SetFloat( "z", GetAbsOrigin().z ); + gameeventmanager->FireEvent( event ); + } + } + } + + void CBaseCSGrenadeProjectile::SetupInitialTransmittedGrenadeVelocity( const Vector &velocity ) + { + m_vInitialVelocity = velocity; + } + + #define MAX_WATER_SURFACE_DISTANCE 512 + + void CBaseCSGrenadeProjectile::Splash() + { + Vector centerPoint = GetAbsOrigin(); + Vector normal( 0, 0, 1 ); + + // Find our water surface by tracing up till we're out of the water + trace_t tr; + Vector vecTrace( 0, 0, MAX_WATER_SURFACE_DISTANCE ); + UTIL_TraceLine( centerPoint, centerPoint + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); + + // If we didn't start in water, we're above it + if ( tr.startsolid == false ) + { + // Look downward to find the surface + vecTrace.Init( 0, 0, -MAX_WATER_SURFACE_DISTANCE ); + UTIL_TraceLine( centerPoint, centerPoint + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); + + // If we hit it, setup the explosion + if ( tr.fraction < 1.0f ) + { + centerPoint = tr.endpos; + } + else + { + //NOTENOTE: We somehow got into a splash without being near water? + Assert( 0 ); + } + } + else if ( tr.fractionleftsolid ) + { + // Otherwise we came out of the water at this point + centerPoint = centerPoint + (vecTrace * tr.fractionleftsolid); + } + else + { + // Use default values, we're really deep + } + + CEffectData data; + data.m_vOrigin = centerPoint; + data.m_vNormal = normal; + data.m_flScale = random->RandomFloat( 1.0f, 2.0f ); + + if ( GetWaterType() & CONTENTS_SLIME ) + { + data.m_fFlags |= FX_WATER_IN_SLIME; + } + + DispatchEffect( "gunshotsplash", data ); + } + +#endif // !CLIENT_DLL diff --git a/game/shared/cstrike/basecsgrenade_projectile.h b/game/shared/cstrike/basecsgrenade_projectile.h new file mode 100644 index 0000000..56ebd47 --- /dev/null +++ b/game/shared/cstrike/basecsgrenade_projectile.h @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef BASECSGRENADE_PROJECTILE_H +#define BASECSGRENADE_PROJECTILE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "basegrenade_shared.h" + + +#ifdef CLIENT_DLL + #define CBaseCSGrenadeProjectile C_BaseCSGrenadeProjectile +#else + class CCSWeaponInfo; +#endif + + +class CBaseCSGrenadeProjectile : public CBaseGrenade +{ +public: + DECLARE_CLASS( CBaseCSGrenadeProjectile, CBaseGrenade ); + DECLARE_NETWORKCLASS(); + + virtual void Spawn(); + + +public: + + // This gets sent to the client and placed in the client's interpolation history + // so the projectile starts out moving right off the bat. + CNetworkVector( m_vInitialVelocity ); + + +#ifdef CLIENT_DLL + CBaseCSGrenadeProjectile() {} + CBaseCSGrenadeProjectile( const CBaseCSGrenadeProjectile& ) {} + virtual int DrawModel( int flags ); + virtual void PostDataUpdate( DataUpdateType_t type ); + + float m_flSpawnTime; +#else + DECLARE_DATADESC(); + + virtual void PostConstructor( const char *className ); + virtual ~CBaseCSGrenadeProjectile(); + + //Constants for all CS Grenades + static inline float GetGrenadeGravity() { return 0.4f; } + static inline const float GetGrenadeFriction() { return 0.2f; } + static inline const float GetGrenadeElasticity() { return 0.45f; } + + //Think function to emit danger sounds for the AI + void DangerSoundThink( void ); + + virtual float GetShakeAmplitude( void ) { return 0.0f; } + virtual void Splash(); + + // Specify what velocity we want the grenade to have on the client immediately. + // Without this, the entity wouldn't have an interpolation history initially, so it would + // sit still until it had gotten a few updates from the server. + void SetupInitialTransmittedGrenadeVelocity( const Vector &velocity ); + + // [jpaquin] give grenade projectiles a link back to the type + // of weapon they are + CCSWeaponInfo *m_pWeaponInfo; + +protected: + + //Set the time to detonate ( now + timer ) + void SetDetonateTimerLength( float timer ); + +private: + + //Custom collision to allow for constant elasticity on hit surfaces + virtual void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ); + + float m_flDetonateTime; +#endif +}; + + +#endif // BASECSGRENADE_PROJECTILE_H diff --git a/game/shared/cstrike/bot/bot.cpp b/game/shared/cstrike/bot/bot.cpp new file mode 100644 index 0000000..4b17ddf --- /dev/null +++ b/game/shared/cstrike/bot/bot.cpp @@ -0,0 +1,109 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), Leon Hartwig, 2003 + +#include "cbase.h" +#include "basegrenade_shared.h" + +#include "bot.h" +#include "bot_util.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +/// @todo Remove this nasty hack - CreateFakeClient() calls CBot::Spawn, which needs the profile and team +const BotProfile *g_botInitProfile = NULL; +int g_botInitTeam = 0; + +// +// NOTE: Because CBot had to be templatized, the code was moved into bot.h +// + + +//-------------------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------------------- + +ActiveGrenade::ActiveGrenade( CBaseGrenade *grenadeEntity ) +{ + m_entity = grenadeEntity; + m_detonationPosition = grenadeEntity->GetAbsOrigin(); + m_dieTimestamp = 0.0f; + m_radius = HEGrenadeRadius; + + m_isSmoke = FStrEq( grenadeEntity->GetClassname(), "smokegrenade_projectile" ); + if ( m_isSmoke ) + { + m_radius = SmokeGrenadeRadius; + } + + m_isFlashbang = FStrEq( grenadeEntity->GetClassname(), "flashbang_projectile" ); + if ( m_isFlashbang ) + { + m_radius = FlashbangGrenadeRadius; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Called when the grenade in the world goes away + */ +void ActiveGrenade::OnEntityGone( void ) +{ + if (m_isSmoke) + { + // smoke lingers after grenade is gone + const float smokeLingerTime = 4.0f; + m_dieTimestamp = gpGlobals->curtime + smokeLingerTime; + } + + m_entity = NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +void ActiveGrenade::Update( void ) +{ + if (m_entity != NULL) + { + m_detonationPosition = m_entity->GetAbsOrigin(); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this grenade is valid + */ +bool ActiveGrenade::IsValid( void ) const +{ + if ( m_isSmoke ) + { + if ( m_entity == NULL && gpGlobals->curtime > m_dieTimestamp ) + { + return false; + } + } + else + { + if ( m_entity == NULL ) + { + return false; + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +const Vector &ActiveGrenade::GetPosition( void ) const +{ + // smoke grenades can vanish before the smoke itself does - refer to the detonation position + if (m_entity == NULL) + return GetDetonationPosition(); + + return m_entity->GetAbsOrigin(); +} + diff --git a/game/shared/cstrike/bot/bot.h b/game/shared/cstrike/bot/bot.h new file mode 100644 index 0000000..3067c2c --- /dev/null +++ b/game/shared/cstrike/bot/bot.h @@ -0,0 +1,1053 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// +// Author: Michael S. Booth ([email protected]), 2003 +// +// NOTE: The CS Bot code uses Doxygen-style comments. If you run Doxygen over this code, it will +// auto-generate documentation. Visit www.doxygen.org to download the system for free. +// + +#ifndef BOT_H +#define BOT_H + +#include "cbase.h" +#include "in_buttons.h" +#include "movehelper_server.h" +#include "mathlib/mathlib.h" + +#include "bot_manager.h" +#include "bot_util.h" +#include "bot_constants.h" +#include "nav_mesh.h" +#include "gameinterface.h" +#include "weapon_csbase.h" +#include "shared_util.h" +#include "util.h" +#include "shareddefs.h" + +#include "tier0/vprof.h" + +class BotProfile; + + +extern bool AreBotsAllowed(); + + +//-------------------------------------------------------------------------------------------------------- +// BOTPORT: Convert everything to assume "origin" means "feet" + +// +// Utility function to get "centroid" or center of player or player equivalent +// +inline Vector GetCentroid( const CBaseEntity *player ) +{ + Vector centroid = player->GetAbsOrigin(); + + const Vector &mins = player->WorldAlignMins(); + const Vector &maxs = player->WorldAlignMaxs(); + + centroid.z += (maxs.z - mins.z)/2.0f; + + //centroid.z += HalfHumanHeight; + + return centroid; +} + + +CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername ); + +/// @todo Remove this nasty hack - CreateFakeClient() calls CBot::Spawn, which needs the profile +extern const BotProfile *g_botInitProfile; +extern int g_botInitTeam; +extern int g_nClientPutInServerOverrides; + +//-------------------------------------------------------------------------------------------------------- +template < class T > T * CreateBot( const BotProfile *profile, int team ) +{ + if ( !AreBotsAllowed() ) + return NULL; + + if ( UTIL_ClientsInGame() >= gpGlobals->maxClients ) + { + CONSOLE_ECHO( "Unable to create bot: Server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients ); + return NULL; + } + + // set the bot's name + char botName[64]; + UTIL_ConstructBotNetName( botName, 64, profile ); + + // This is a backdoor we use so when the engine calls ClientPutInServer (from CreateFakeClient), + // expecting the game to make an entity for the fake client, we can make our special bot class + // instead of a CCSPlayer. + g_nClientPutInServerOverrides = 0; + ClientPutInServerOverride( ClientPutInServerOverride_Bot ); + + // get an edict for the bot + // NOTE: This will ultimately invoke CBot::Spawn(), so set the profile now + g_botInitProfile = profile; + g_botInitTeam = team; + edict_t *botEdict = engine->CreateFakeClient( botName ); + + ClientPutInServerOverride( NULL ); + Assert( g_nClientPutInServerOverrides == 1 ); + + + if ( botEdict == NULL ) + { + CONSOLE_ECHO( "Unable to create bot: CreateFakeClient() returned null.\n" ); + return NULL; + } + + + // create an instance of the bot's class and bind it to the edict + T *bot = dynamic_cast< T * >( CBaseEntity::Instance( botEdict ) ); + + if ( bot == NULL ) + { + Assert( false ); + Error( "Could not allocate and bind entity to bot edict.\n" ); + return NULL; + } + + bot->ClearFlags(); + bot->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + return bot; +} + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +/** + * The base bot class from which bots for specific games are derived + * A template is needed here because the CBot class must be derived from CBasePlayer, + * but also may need to be derived from a more specific player class, such as CCSPlayer + */ +template < class PlayerType > +class CBot : public PlayerType +{ +public: + DECLARE_CLASS( CBot, PlayerType ); + + CBot( void ); ///< constructor initializes all values to zero + virtual ~CBot(); + virtual bool Initialize( const BotProfile *profile, int team ); ///< (EXTEND) prepare bot for action + + unsigned int GetID( void ) const { return m_id; } ///< return bot's unique ID + + virtual bool IsBot( void ) const { return true; } + virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages + + virtual void Spawn( void ); ///< (EXTEND) spawn the bot into the game + + virtual void Upkeep( void ) = 0; ///< lightweight maintenance, invoked frequently + virtual void Update( void ) = 0; ///< heavyweight algorithms, invoked less often + + + virtual void Run( void ); + virtual void Walk( void ); + virtual bool IsRunning( void ) const { return m_isRunning; } + + virtual void Crouch( void ); + virtual void StandUp( void ); + bool IsCrouching( void ) const { return m_isCrouching; } + + void PushPostureContext( void ); ///< push the current posture context onto the top of the stack + void PopPostureContext( void ); ///< restore the posture context to the next context on the stack + + virtual void MoveForward( void ); + virtual void MoveBackward( void ); + virtual void StrafeLeft( void ); + virtual void StrafeRight( void ); + + #define MUST_JUMP true + virtual bool Jump( bool mustJump = false ); ///< returns true if jump was started + bool IsJumping( void ); ///< returns true if we are in the midst of a jump + float GetJumpTimestamp( void ) const { return m_jumpTimestamp; } ///< return time last jump began + + virtual void ClearMovement( void ); ///< zero any MoveForward(), Jump(), etc + + const Vector &GetViewVector( void ); ///< return the actual view direction + + + //------------------------------------------------------------------------------------ + // Weapon interface + // + virtual void UseEnvironment( void ); + virtual void PrimaryAttack( void ); + virtual void ClearPrimaryAttack( void ); + virtual void TogglePrimaryAttack( void ); + virtual void SecondaryAttack( void ); + virtual void Reload( void ); + + float GetActiveWeaponAmmoRatio( void ) const; ///< returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + bool IsActiveWeaponClipEmpty( void ) const; ///< return true if active weapon has any empty clip + bool IsActiveWeaponOutOfAmmo( void ) const; ///< return true if active weapon has no ammo at all + bool IsActiveWeaponRecoilHigh( void ) const; ///< return true if active weapon's bullet spray has become large and inaccurate + bool IsUsingScope( void ); ///< return true if looking thru weapon's scope + + + //------------------------------------------------------------------------------------ + // Event hooks + // + + /// invoked when injured by something (EXTEND) - returns the amount of damage inflicted + virtual int OnTakeDamage( const CTakeDamageInfo &info ) + { + return PlayerType::OnTakeDamage( info ); + } + + /// invoked when killed (EXTEND) + virtual void Event_Killed( const CTakeDamageInfo &info ) + { + PlayerType::Event_Killed( info ); + } + + bool IsEnemy( CBaseEntity *ent ) const; ///< returns TRUE if given entity is our enemy + int GetEnemiesRemaining( void ) const; ///< return number of enemies left alive + int GetFriendsRemaining( void ) const; ///< return number of friends left alive + + bool IsPlayerFacingMe( CBasePlayer *enemy ) const; ///< return true if player is facing towards us + bool IsPlayerLookingAtMe( CBasePlayer *enemy, float cosTolerance = 0.9f ) const; ///< returns true if other player is pointing right at us + bool IsLookingAtPosition( const Vector &pos, float angleTolerance = 20.0f ) const; ///< returns true if looking (roughly) at given position + + bool IsLocalPlayerWatchingMe( void ) const; ///< return true if local player is observing this bot + + void PrintIfWatched( PRINTF_FORMAT_STRING const char *format, ... ) const; ///< output message to console if we are being watched by the local player + + virtual void UpdatePlayer( void ); ///< update player physics, movement, weapon firing commands, etc + virtual void BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse ); + virtual void SetModel( const char *modelName ); + + int Save( CSave &save ) const { return 0; } + int Restore( CRestore &restore ) const { return 0; } + virtual void Think( void ) { } + + const BotProfile *GetProfile( void ) const { return m_profile; } ///< return our personality profile + + virtual bool ClientCommand( const CCommand &args ); ///< Do a "client command" - useful for invoking menu choices, etc. + virtual int Cmd_Argc( void ); ///< Returns the number of tokens in the command string + virtual char *Cmd_Argv( int argc ); ///< Retrieves a specified token + +private: + CUtlVector< char * > m_args; + +protected: + const BotProfile *m_profile; ///< the "personality" profile of this bot + +private: + friend class CBotManager; + + unsigned int m_id; ///< unique bot ID + + CUserCmd m_userCmd; + bool m_isRunning; ///< run/walk mode + bool m_isCrouching; ///< true if crouching (ducking) + float m_forwardSpeed; + float m_strafeSpeed; + float m_verticalSpeed; + int m_buttonFlags; ///< bitfield of movement buttons + + float m_jumpTimestamp; ///< time when we last began a jump + + Vector m_viewForward; ///< forward view direction (only valid when GetViewVector() is used) + + /// the PostureContext represents the current settings of walking and crouching + struct PostureContext + { + bool isRunning; + bool isCrouching; + }; + enum { MAX_POSTURE_STACK = 8 }; + PostureContext m_postureStack[ MAX_POSTURE_STACK ]; + int m_postureStackIndex; ///< index of top of stack + + void ResetCommand( void ); + //byte ThrottledMsec( void ) const; + +protected: + virtual float GetMoveSpeed( void ); ///< returns current movement speed (for walk/run) +}; + + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- +// +// Inlines +// + +//-------------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::SetModel( const char *modelName ) +{ + BaseClass::SetModel( modelName ); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline float CBot<T>::GetMoveSpeed( void ) +{ + return this->MaxSpeed(); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::Run( void ) +{ + m_isRunning = true; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::Walk( void ) +{ + m_isRunning = false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsActiveWeaponRecoilHigh( void ) const +{ + const QAngle &angles = const_cast< CBot<T> * >( this )->GetPunchAngle(); + const float highRecoil = -1.5f; + return (angles.x < highRecoil); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::PushPostureContext( void ) +{ + if (m_postureStackIndex == MAX_POSTURE_STACK) + { + PrintIfWatched( "PushPostureContext() overflow error!\n" ); + return; + } + + m_postureStack[ m_postureStackIndex ].isRunning = m_isRunning; + m_postureStack[ m_postureStackIndex ].isCrouching = m_isCrouching; + ++m_postureStackIndex; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::PopPostureContext( void ) +{ + if (m_postureStackIndex == 0) + { + PrintIfWatched( "PopPostureContext() underflow error!\n" ); + m_isRunning = true; + m_isCrouching = false; + return; + } + + --m_postureStackIndex; + m_isRunning = m_postureStack[ m_postureStackIndex ].isRunning; + m_isCrouching = m_postureStack[ m_postureStackIndex ].isCrouching; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsPlayerFacingMe( CBasePlayer *other ) const +{ + Vector toOther = other->GetAbsOrigin() - this->GetAbsOrigin(); + + Vector otherForward; + AngleVectors( other->EyeAngles() + other->GetPunchAngle(), &otherForward ); + + if (DotProduct( otherForward, toOther ) < 0.0f) + return true; + + return false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsPlayerLookingAtMe( CBasePlayer *other, float cosTolerance ) const +{ + Vector toOther = other->GetAbsOrigin() - this->GetAbsOrigin(); + toOther.NormalizeInPlace(); + + Vector otherForward; + AngleVectors( other->EyeAngles() + other->GetPunchAngle(), &otherForward ); + + // other player must be pointing nearly right at us to be "looking at" us + if (DotProduct( otherForward, toOther ) < -cosTolerance) + return true; + + return false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline const Vector &CBot<T>::GetViewVector( void ) +{ + AngleVectors( this->EyeAngles() + this->GetPunchAngle(), &m_viewForward ); + return m_viewForward; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsLookingAtPosition( const Vector &pos, float angleTolerance ) const +{ + // forced to do this since many methods in CBaseEntity are not const, but should be + CBot< T > *me = const_cast< CBot< T > * >( this ); + + Vector to = pos - me->EyePosition(); + + QAngle idealAngles; + VectorAngles( to, idealAngles ); + + QAngle viewAngles = me->EyeAngles(); + + float deltaYaw = AngleNormalize( idealAngles.y - viewAngles.y ); + float deltaPitch = AngleNormalize( idealAngles.x - viewAngles.x ); + + if (fabs( deltaYaw ) < angleTolerance && abs( deltaPitch ) < angleTolerance) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline CBot< PlayerType >::CBot( void ) +{ + // the profile will be attached after this instance is constructed + m_profile = NULL; + + // assign this bot a unique ID + static unsigned int nextID = 1; + + // wraparound (highly unlikely) + if (nextID == 0) + ++nextID; + + m_id = nextID; + ++nextID; + + m_postureStackIndex = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline CBot< PlayerType >::~CBot( void ) +{ +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Prepare bot for action + */ +template < class PlayerType > +inline bool CBot< PlayerType >::Initialize( const BotProfile *profile, int team ) +{ + m_profile = profile; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Spawn( void ) +{ + // initialize the bot (thus setting its profile) + if (m_profile == NULL) + Initialize( g_botInitProfile, g_botInitTeam ); + + // let the base class set some things up + PlayerType::Spawn(); + + // Make sure everyone knows we are a bot + this->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + // Bots use their own thinking mechanism + this->SetThink( NULL ); + + m_isRunning = true; + m_isCrouching = false; + m_postureStackIndex = 0; + + m_jumpTimestamp = 0.0f; + + // Command interface variable initialization + ResetCommand(); +} + + +/* +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::BotThink( void ) +{ +float g_flBotFullThinkInterval = 1.0 / 15.0; // full AI at lower frequency (was 10 in GoldSrc) + + + Upkeep(); + + if (gpGlobals->curtime >= m_flNextFullBotThink) + { + m_flNextFullBotThink = gpGlobals->curtime + g_flBotFullThinkInterval; + + ResetCommand(); + Update(); + } + + UpdatePlayer(); +} +*/ + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::MoveForward( void ) +{ + m_forwardSpeed = GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_FORWARD ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_BACK ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::MoveBackward( void ) +{ + m_forwardSpeed = -GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_BACK ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_FORWARD ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StrafeLeft( void ) +{ + m_strafeSpeed = -GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_MOVELEFT ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_MOVERIGHT ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StrafeRight( void ) +{ + m_strafeSpeed = GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_MOVERIGHT ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_MOVELEFT ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline bool CBot< PlayerType >::Jump( bool mustJump ) +{ + if (IsJumping() || IsCrouching()) + return false; + + if (!mustJump) + { + const float minJumpInterval = 0.9f; // 1.5f; + if (gpGlobals->curtime - m_jumpTimestamp < minJumpInterval) + return false; + } + + // still need sanity check for jumping frequency + const float sanityInterval = 0.3f; + if (gpGlobals->curtime - m_jumpTimestamp < sanityInterval) + return false; + + // jump + SETBITS( m_buttonFlags, IN_JUMP ); + m_jumpTimestamp = gpGlobals->curtime; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Zero any MoveForward(), Jump(), etc + */ +template < class PlayerType > +void CBot< PlayerType >::ClearMovement( void ) +{ + m_forwardSpeed = 0.0; + m_strafeSpeed = 0.0; + m_verticalSpeed = 100.0; // stay at the top of water, so we don't drown. TODO: swim logic + m_buttonFlags &= ~(IN_FORWARD | IN_BACK | IN_LEFT | IN_RIGHT | IN_JUMP); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns true if we are in the midst of a jump + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsJumping( void ) +{ + // if long time after last jump, we can't be jumping + if (gpGlobals->curtime - m_jumpTimestamp > 3.0f) + return false; + + // if we just jumped, we're still jumping + if (gpGlobals->curtime - m_jumpTimestamp < 0.9f) // 1.0f + return true; + + // a little after our jump, we're jumping until we hit the ground + if (FBitSet( this->GetFlags(), FL_ONGROUND )) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Crouch( void ) +{ + m_isCrouching = true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StandUp( void ) +{ + m_isCrouching = false; +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::UseEnvironment( void ) +{ + SETBITS( m_buttonFlags, IN_USE ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::PrimaryAttack( void ) +{ + SETBITS( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::ClearPrimaryAttack( void ) +{ + CLEARBITS( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::TogglePrimaryAttack( void ) +{ + if (FBitSet( m_buttonFlags, IN_ATTACK )) + { + CLEARBITS( m_buttonFlags, IN_ATTACK ); + } + else + { + SETBITS( m_buttonFlags, IN_ATTACK ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::SecondaryAttack( void ) +{ + SETBITS( m_buttonFlags, IN_ATTACK2 ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Reload( void ) +{ + SETBITS( m_buttonFlags, IN_RELOAD ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + */ +template < class PlayerType > +inline float CBot< PlayerType >::GetActiveWeaponAmmoRatio( void ) const +{ + CWeaponCSBase *weapon = this->GetActiveCSWeapon(); + + if (weapon == NULL) + return 0.0f; + + // weapons with no ammo are always full + if (weapon->Clip1() < 0) + return 1.0f; + + return (float)weapon->Clip1() / (float)weapon->GetMaxClip1(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has an empty clip + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsActiveWeaponClipEmpty( void ) const +{ + CWeaponCSBase *gun = this->GetActiveCSWeapon(); + + if (gun && gun->Clip1() == 0) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has no ammo at all + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsActiveWeaponOutOfAmmo( void ) const +{ + CWeaponCSBase *weapon = this->GetActiveCSWeapon(); + + if (weapon == NULL) + return true; + + return !weapon->HasAnyAmmo(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if looking thru weapon's scope + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsUsingScope( void ) +{ + // if our field of view is less than 90, we're looking thru a scope (maybe only true for CS...) + if (this->GetFOV() < this->GetDefaultFOV()) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Fill in a CUserCmd with our data + */ +template < class PlayerType > +inline void CBot< PlayerType >::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse ) +{ + Q_memset( &cmd, 0, sizeof( cmd ) ); + cmd.command_number = gpGlobals->tickcount; + cmd.forwardmove = forwardmove; + cmd.sidemove = sidemove; + cmd.upmove = upmove; + cmd.buttons = buttons; + cmd.impulse = impulse; + + VectorCopy( viewangles, cmd.viewangles ); + cmd.random_seed = random->RandomInt( 0, 0x7fffffff ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Update player physics, movement, weapon firing commands, etc + */ +template < class PlayerType > +inline void CBot< PlayerType >::UpdatePlayer( void ) +{ + if (m_isCrouching) + { + SETBITS( m_buttonFlags, IN_DUCK ); + } + else if (!m_isRunning) + { + SETBITS( m_buttonFlags, IN_SPEED ); + } + + if ( this->IsEFlagSet(EFL_BOT_FROZEN) ) + { + m_buttonFlags = 0; // Freeze. + m_forwardSpeed = 0; + m_strafeSpeed = 0; + m_verticalSpeed = 0; + } + + // Fill in a CUserCmd with our data + this->BuildUserCmd( m_userCmd, this->EyeAngles(), m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, m_buttonFlags, 0 ); + + // Save off the CUserCmd to execute later + this->ProcessUsercmds( &m_userCmd, 1, 1, 0, false ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::ResetCommand( void ) +{ + m_forwardSpeed = 0.0; + m_strafeSpeed = 0.0; + m_verticalSpeed = 100.0; // stay at the top of water, so we don't drown. TODO: swim logic + m_buttonFlags = 0; +} + + +//-------------------------------------------------------------------------------------------------------------- +/* +template < class PlayerType > +inline byte CBot< PlayerType >::ThrottledMsec( void ) const +{ + int iNewMsec; + + // Estimate Msec to use for this command based on time passed from the previous command + iNewMsec = (int)( (gpGlobals->curtime - m_flPreviousCommandTime) * 1000 ); + if (iNewMsec > 255) // Doh, bots are going to be slower than they should if this happens. + iNewMsec = 255; // Upgrade that CPU or use less bots! + + return (byte)iNewMsec; +} +*/ + +//-------------------------------------------------------------------------------------------------------------- +/** + * Do a "client command" - useful for invoking menu choices, etc. + */ +template < class PlayerType > +inline bool CBot< PlayerType >::ClientCommand( const CCommand &args ) +{ + // Remove old args + int i; + for ( i=0; i<m_args.Count(); ++i ) + { + delete[] m_args[i]; + } + m_args.RemoveAll(); + + // parse individual args + const char *cmd = args.GetCommandString(); + while (1) + { + // skip whitespace up to a /n + while (*cmd && *cmd <= ' ' && *cmd != '\n') + { + cmd++; + } + + if (*cmd == '\n') + { // a newline seperates commands in the buffer + cmd++; + break; + } + + if (!*cmd) + break; + + cmd = SharedParse (cmd); + if (!cmd) + break; + + m_args.AddToTail( CloneString( SharedGetToken() ) ); + } + + // and pass to the base class + return PlayerType::ClientCommand( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns the number of tokens in the command string + */ +template < class PlayerType > +inline int CBot< PlayerType >::Cmd_Argc() +{ + return m_args.Count(); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Retrieves a specified token + */ +template < class PlayerType > +inline char * CBot< PlayerType >::Cmd_Argv( int argc ) +{ + if ( argc < 0 || argc >= m_args.Count() ) + return NULL; + return m_args[argc]; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns TRUE if given entity is our enemy + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsEnemy( CBaseEntity *ent ) const +{ + // only Players (real and AI) can be enemies + if (!ent->IsPlayer()) + return false; + + // corpses are no threat + if (!ent->IsAlive()) + return false; + + CBasePlayer *player = static_cast<CBasePlayer *>( ent ); + + // if they are on our team, they are our friends + if (player->GetTeamNumber() == this->GetTeamNumber()) + return false; + + // yep, we hate 'em + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of enemies left alive + */ +template < class PlayerType > +inline int CBot< PlayerType >::GetEnemiesRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (!IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of friends left alive + */ +template < class PlayerType > +inline int CBot< PlayerType >::GetFriendsRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + if (player == static_cast<CBaseEntity *>( const_cast<CBot *>( this ) )) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if the local player is currently in observer mode watching this bot. + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsLocalPlayerWatchingMe( void ) const +{ + if ( engine->IsDedicatedServer() ) + return false; + + CBasePlayer *player = UTIL_GetListenServerHost(); + if ( player == NULL ) + return false; + + if ( cv_bot_debug_target.GetInt() > 0 ) + { + return this->entindex() == cv_bot_debug_target.GetInt(); + } + + if ( player->IsObserver() || !player->IsAlive() ) + { + if ( const_cast< CBot< PlayerType > * >(this) == player->GetObserverTarget() ) + { + switch( player->GetObserverMode() ) + { + case OBS_MODE_IN_EYE: + case OBS_MODE_CHASE: + return true; + } + } + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Output message to console if we are being watched by the local player + */ +template < class PlayerType > +inline void CBot< PlayerType >::PrintIfWatched( PRINTF_FORMAT_STRING const char *format, ... ) const +{ + if (cv_bot_debug.GetInt() == 0) + { + return; + } + + if ((IsLocalPlayerWatchingMe() && (cv_bot_debug.GetInt() == 1 || cv_bot_debug.GetInt() == 3)) || + (cv_bot_debug.GetInt() == 2 || cv_bot_debug.GetInt() == 4)) + { + va_list varg; + char buffer[ CBotManager::MAX_DBG_MSG_SIZE ]; + const char *name = const_cast< CBot< PlayerType > * >( this )->GetPlayerName(); + + va_start( varg, format ); + vsprintf( buffer, format, varg ); + va_end( varg ); + + // prefix the console message with the bot's name (this can be NULL if bot was just added) + ClientPrint( UTIL_GetListenServerHost(), + HUD_PRINTCONSOLE, + UTIL_VarArgs( "%s: %s", + (name) ? name : "(NULL netname)", buffer ) ); + + TheBots->AddDebugMessage( buffer ); + } +} + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- + +extern void InstallBotControl( void ); +extern void RemoveBotControl( void ); +extern void Bot_ServerCommand( void ); +extern void Bot_RegisterCvars( void ); + +extern bool IsSpotOccupied( CBaseEntity *me, const Vector &pos ); // if a player is at the given spot, return true +extern const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange = 1000.0f, bool isSniper = false, bool useNearest = false ); +extern const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper = false ); +extern const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange = 1000.0f, int avoidTeam = 0 ); + + +#endif // BOT_H diff --git a/game/shared/cstrike/bot/bot_constants.h b/game/shared/cstrike/bot/bot_constants.h new file mode 100644 index 0000000..94f5090 --- /dev/null +++ b/game/shared/cstrike/bot/bot_constants.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Matthew D. Campbell ([email protected]), 2003 + +#ifndef BOT_CONSTANTS_H +#define BOT_CONSTANTS_H + +/// version number is MAJOR.MINOR +#define BOT_VERSION_MAJOR 1 +#define BOT_VERSION_MINOR 50 + +//-------------------------------------------------------------------------------------------------------- +/** + * Difficulty levels + */ +enum BotDifficultyType +{ + BOT_EASY = 0, + BOT_NORMAL = 1, + BOT_HARD = 2, + BOT_EXPERT = 3, + + NUM_DIFFICULTY_LEVELS +}; + +#ifdef DEFINE_DIFFICULTY_NAMES + const char *BotDifficultyName[] = + { + "EASY", "NORMAL", "HARD", "EXPERT", NULL + }; +#else + extern const char *BotDifficultyName[]; +#endif + +#endif // BOT_CONSTANTS_H diff --git a/game/shared/cstrike/bot/bot_hide.cpp b/game/shared/cstrike/bot/bot_hide.cpp new file mode 100644 index 0000000..9a2b02c --- /dev/null +++ b/game/shared/cstrike/bot/bot_hide.cpp @@ -0,0 +1,490 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// bot_hide.cpp +// Mechanisms for using Hiding Spots in the Navigation Mesh +// Author: Michael Booth, 2003-2004 + +#include "cbase.h" +#include "bot.h" +#include "cs_nav_pathfind.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//-------------------------------------------------------------------------------------------------------------- +/** + * If a player is at the given spot, return true + */ +bool IsSpotOccupied( CBaseEntity *me, const Vector &pos ) +{ + const float closeRange = 75.0f; // 50 + + // is there a player in this spot + float range; + CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range ); + + if (player != me) + { + if (player && range < closeRange) + return true; + } + + // is there is a hostage in this spot + // BOTPORT: Implement hostage manager + /* + if (g_pHostages) + { + CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range ); + if (hostage && hostage != me && range < closeRange) + return true; + } + */ + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +class CollectHidingSpotsFunctor +{ +public: + CollectHidingSpotsFunctor( CBaseEntity *me, const Vector &origin, float range, int flags, Place place = UNDEFINED_PLACE ) : m_origin( origin ) + { + m_me = me; + m_count = 0; + m_range = range; + m_flags = (unsigned char)flags; + m_place = place; + m_totalWeight = 0; + } + + enum { MAX_SPOTS = 256 }; + + bool operator() ( CNavArea *area ) + { + // if a place is specified, only consider hiding spots from areas in that place + if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place) + return true; + + // collect all the hiding spots in this area + const HidingSpotVector *pSpots = area->GetHidingSpots(); + + FOR_EACH_VEC( (*pSpots), it ) + { + const HidingSpot *spot = (*pSpots)[ it ]; + + // if we've filled up, stop searching + if (m_count == MAX_SPOTS) + { + return false; + } + + // make sure hiding spot is in range + if (m_range > 0.0f) + { + if ((spot->GetPosition() - m_origin).IsLengthGreaterThan( m_range )) + { + continue; + } + } + + // if a Player is using this hiding spot, don't consider it + if (IsSpotOccupied( m_me, spot->GetPosition() )) + { + // player is in hiding spot + /// @todo Check if player is moving or sitting still + continue; + } + + if (spot->GetArea() && (spot->GetArea()->GetAttributes() & NAV_MESH_DONT_HIDE)) + { + // the area has been marked as DONT_HIDE since the last analysis, so let's ignore it + continue; + } + + // only collect hiding spots with matching flags + if (m_flags & spot->GetFlags()) + { + m_hidingSpot[ m_count ] = &spot->GetPosition(); + m_hidingSpotWeight[ m_count ] = m_totalWeight; + + // if it's an 'avoid' area, give it a low weight + if ( spot->GetArea() && ( spot->GetArea()->GetAttributes() & NAV_MESH_AVOID ) ) + { + m_totalWeight += 1; + } + else + { + m_totalWeight += 2; + } + + ++m_count; + } + } + + return (m_count < MAX_SPOTS); + } + + /** + * Remove the spot at index "i" + */ + void RemoveSpot( int i ) + { + if (m_count == 0) + return; + + for( int j=i+1; j<m_count; ++j ) + m_hidingSpot[j-1] = m_hidingSpot[j]; + + --m_count; + } + + + int GetRandomHidingSpot( void ) + { + int weight = RandomInt( 0, m_totalWeight-1 ); + for ( int i=0; i<m_count-1; ++i ) + { + // if the next spot's starting weight is over the target weight, this spot is the one + if ( m_hidingSpotWeight[i+1] >= weight ) + { + return i; + } + } + + // if we didn't find any, it's the last one + return m_count - 1; + } + + CBaseEntity *m_me; + const Vector &m_origin; + float m_range; + + const Vector *m_hidingSpot[ MAX_SPOTS ]; + int m_hidingSpotWeight[ MAX_SPOTS ]; + int m_totalWeight; + int m_count; + + unsigned char m_flags; + + Place m_place; +}; + +/** + * Do a breadth-first search to find a nearby hiding spot and return it. + * Don't pick a hiding spot that a Player is currently occupying. + * @todo Clean up this mess + */ +const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange, bool isSniper, bool useNearest ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( pos ); + if (startArea == NULL) + return NULL; + + // collect set of nearby hiding spots + if (isSniper) + { + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IDEAL_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count) + { + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::GOOD_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count) + { + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IN_COVER ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count == 0) + { + // no hiding spots at all - if we're not a sniper, try to find a sniper spot to use instead + if (!isSniper) + { + return FindNearbyHidingSpot( me, pos, maxRange, true, useNearest ); + } + + return NULL; + } + + if (useNearest) + { + // return closest hiding spot + const Vector *closest = NULL; + float closeRangeSq = 9999999999.9f; + for( int i=0; i<collector.m_count; ++i ) + { + float rangeSq = (*collector.m_hidingSpot[i] - pos).LengthSqr(); + if (rangeSq < closeRangeSq) + { + closeRangeSq = rangeSq; + closest = collector.m_hidingSpot[i]; + } + } + + return closest; + } + + // select a hiding spot at random + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Select a random hiding spot among the nav areas that are tagged with the given place + */ +const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper ) +{ + // collect set of nearby hiding spots + if (isSniper) + { + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IDEAL_SNIPER_SPOT, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::GOOD_SNIPER_SPOT, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IN_COVER, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count == 0) + return NULL; + + // select a hiding spot at random + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Select a nearby retreat spot. + * Don't pick a hiding spot that a Player is currently occupying. + * If "avoidTeam" is nonzero, avoid getting close to members of that team. + */ +const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange, int avoidTeam ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( start ); + if (startArea == NULL) + return NULL; + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER ); + SearchSurroundingAreas( startArea, start, collector, maxRange ); + + if (collector.m_count == 0) + return NULL; + + // find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover + for( int i=0; i<collector.m_count; ++i ) + { + // check if we would have to cross a line of fire to reach this hiding spot + if (IsCrossingLineOfFire( start, *collector.m_hidingSpot[i], me )) + { + collector.RemoveSpot( i ); + + // back up a step, so iteration won't skip a spot + --i; + + continue; + } + + // check if there is someone on the avoidTeam near this hiding spot + if (avoidTeam) + { + float range; + if (UTIL_GetClosestPlayer( *collector.m_hidingSpot[i], avoidTeam, &range )) + { + const float dangerRange = 150.0f; + if (range < dangerRange) + { + // there is an avoidable player too near this spot - remove it + collector.RemoveSpot( i ); + + // back up a step, so iteration won't skip a spot + --i; + + continue; + } + } + } + } + + if (collector.m_count <= 0) + return NULL; + + // all remaining spots are ok - pick one at random + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Functor to collect all hiding spots in range that we can reach before the enemy arrives. + * NOTE: This only works for the initial rush. + */ +class CollectArriveFirstSpotsFunctor +{ +public: + CollectArriveFirstSpotsFunctor( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float range, int flags ) : m_searchOrigin( searchOrigin ) + { + m_me = me; + m_count = 0; + m_range = range; + m_flags = (unsigned char)flags; + m_enemyArriveTime = enemyArriveTime; + } + + enum { MAX_SPOTS = 256 }; + + bool operator() ( CNavArea *area ) + { + // collect all the hiding spots in this area + const HidingSpotVector *pSpots = area->GetHidingSpots(); + + FOR_EACH_VEC( (*pSpots), it ) + { + const HidingSpot *spot = (*pSpots)[ it ]; + + // make sure hiding spot is in range + if (m_range > 0.0f) + { + if ((spot->GetPosition() - m_searchOrigin).IsLengthGreaterThan( m_range )) + { + continue; + } + } + + // if a Player is using this hiding spot, don't consider it + if (IsSpotOccupied( m_me, spot->GetPosition() )) + { + // player is in hiding spot + /// @todo Check if player is moving or sitting still + continue; + } + + // only collect hiding spots with matching flags + if (!(m_flags & spot->GetFlags())) + { + continue; + } + + // only collect this hiding spot if we can reach it before the enemy arrives + // NOTE: This assumes the area is fairly small and the difference of moving to the corner vs the center is small + const float settleTime = 1.0f; + if (spot->GetArea()->GetEarliestOccupyTime( m_me->GetTeamNumber() ) + settleTime < m_enemyArriveTime) + { + m_hidingSpot[ m_count++ ] = spot; + } + } + + // if we've filled up, stop searching + if (m_count == MAX_SPOTS) + return false; + + return true; + } + + CBaseEntity *m_me; + const Vector &m_searchOrigin; + + float m_range; + float m_enemyArriveTime; + unsigned char m_flags; + + const HidingSpot *m_hidingSpot[ MAX_SPOTS ]; + int m_count; +}; + + +/** + * Select a hiding spot that we can reach before the enemy arrives. + * NOTE: This only works for the initial rush. + */ +const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( searchOrigin ); + if (startArea == NULL) + return NULL; + + // collect set of nearby hiding spots + if (isSniper) + { + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IDEAL_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::GOOD_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IN_COVER | HidingSpot::EXPOSED ); + SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange ); + + if (collector.m_count == 0) + return NULL; + + // select a hiding spot at random + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; +} + diff --git a/game/shared/cstrike/bot/bot_manager.cpp b/game/shared/cstrike/bot/bot_manager.cpp new file mode 100644 index 0000000..ce15823 --- /dev/null +++ b/game/shared/cstrike/bot/bot_manager.cpp @@ -0,0 +1,402 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#include "cbase.h" + +#include "bot.h" +#include "bot_manager.h" +#include "nav_area.h" +#include "bot_util.h" +#include "basegrenade_shared.h" + +#include "cs_bot.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +float g_BotUpkeepInterval = 0.0f; +float g_BotUpdateInterval = 0.0f; + + +//-------------------------------------------------------------------------------------------------------------- +CBotManager::CBotManager() +{ + InitBotTrig(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CBotManager::~CBotManager() +{ +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when the round is restarting + */ +void CBotManager::RestartRound( void ) +{ + DestroyAllGrenades(); + ClearDebugMessages(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked at the start of each frame + */ +void CBotManager::StartFrame( void ) +{ + VPROF_BUDGET( "CBotManager::StartFrame", VPROF_BUDGETGROUP_NPCS ); + + ValidateActiveGrenades(); + + // debug smoke grenade visualization + if (cv_bot_debug.GetInt() == 5) + { + Vector edge, lastEdge; + + FOR_EACH_LL( m_activeGrenadeList, it ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + const Vector &pos = ag->GetDetonationPosition(); + + UTIL_DrawBeamPoints( pos, pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 ); + + lastEdge = Vector( ag->GetRadius() + pos.x, pos.y, pos.z ); + float angle; + for( angle=0.0f; angle <= 180.0f; angle += 22.5f ) + { + edge.x = ag->GetRadius() * BotCOS( angle ) + pos.x; + edge.y = pos.y; + edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z; + + UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 ); + + lastEdge = edge; + } + + lastEdge = Vector( pos.x, ag->GetRadius() + pos.y, pos.z ); + for( angle=0.0f; angle <= 180.0f; angle += 22.5f ) + { + edge.x = pos.x; + edge.y = ag->GetRadius() * BotCOS( angle ) + pos.y; + edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z; + + UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 ); + + lastEdge = edge; + } + } + } + + // set frame duration + g_BotUpkeepInterval = m_frameTimer.GetElapsedTime(); + m_frameTimer.Start(); + + g_BotUpdateInterval = (g_BotUpdateSkipCount+1) * g_BotUpkeepInterval; + + // + // Process each active bot + // + for( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (!player) + continue; + + // Hack for now so the temp bot code works. The temp bots are very useful for debugging + // because they can be setup to mimic the player's usercmds. + if (player->IsBot() && IsEntityValid( player ) ) + { + // EVIL: Messes up vtables + //CBot< CBasePlayer > *bot = static_cast< CBot< CBasePlayer > * >( player ); + CCSBot *bot = dynamic_cast< CCSBot * >( player ); + + if ( bot ) + { + bot->Upkeep(); + + if (((gpGlobals->tickcount + bot->entindex()) % g_BotUpdateSkipCount) == 0) + { + bot->ResetCommand(); + bot->Update(); + } + + bot->UpdatePlayer(); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add an active grenade to the bot's awareness + */ +void CBotManager::AddGrenade( CBaseGrenade *grenade ) +{ + ActiveGrenade *ag = new ActiveGrenade( grenade ); + m_activeGrenadeList.AddToTail( ag ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * The grenade entity in the world is going away + */ +void CBotManager::RemoveGrenade( CBaseGrenade *grenade ) +{ + FOR_EACH_LL( m_activeGrenadeList, it ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + if (ag->IsEntity( grenade )) + { + ag->OnEntityGone(); + return; + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * The grenade entity has changed its radius + */ +void CBotManager::SetGrenadeRadius( CBaseGrenade *grenade, float radius ) +{ + FOR_EACH_LL( m_activeGrenadeList, it ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + if (ag->IsEntity( grenade )) + { + ag->SetRadius( radius ); + return; + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Destroy any invalid active grenades + */ +void CBotManager::ValidateActiveGrenades( void ) +{ + int it = m_activeGrenadeList.Head(); + + while( it != m_activeGrenadeList.InvalidIndex() ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + int current = it; + it = m_activeGrenadeList.Next( it ); + + // lazy validation + if (!ag->IsValid()) + { + m_activeGrenadeList.Remove( current ); + delete ag; + continue; + } + else + { + ag->Update(); + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +void CBotManager::DestroyAllGrenades( void ) +{ + m_activeGrenadeList.PurgeAndDeleteElements(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if position is inside a smoke cloud + */ +bool CBotManager::IsInsideSmokeCloud( const Vector *pos ) +{ + int it = m_activeGrenadeList.Head(); + + while( it != m_activeGrenadeList.InvalidIndex() ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + int current = it; + it = m_activeGrenadeList.Next( it ); + + // lazy validation + if (!ag->IsValid()) + { + m_activeGrenadeList.Remove( current ); + delete ag; + continue; + } + + if (ag->IsSmoke()) + { + const Vector &smokeOrigin = ag->GetDetonationPosition(); + + if ((smokeOrigin - *pos).IsLengthLessThan( ag->GetRadius() )) + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if line intersects smoke volume + * Determine the length of the line of sight covered by each smoke cloud, + * and sum them (overlap is additive for obstruction). + * If the overlap exceeds the threshold, the bot can't see through. + */ +bool CBotManager::IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat ) +{ + VPROF_BUDGET( "CBotManager::IsLineBlockedBySmoke", VPROF_BUDGETGROUP_NPCS ); + + float totalSmokedLength = 0.0f; // distance along line of sight covered by smoke + + // compute unit vector and length of line of sight segment + Vector sightDir = to - from; + float sightLength = sightDir.NormalizeInPlace(); + + FOR_EACH_LL( m_activeGrenadeList, it ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + const float smokeRadiusSq = ag->GetRadius() * ag->GetRadius() * grenadeBloat * grenadeBloat; + + if (ag->IsSmoke()) + { + const Vector &smokeOrigin = ag->GetDetonationPosition(); + + Vector toGrenade = smokeOrigin - from; + + float alongDist = DotProduct( toGrenade, sightDir ); + + // compute closest point to grenade along line of sight ray + Vector close; + + // constrain closest point to line segment + if (alongDist < 0.0f) + close = from; + else if (alongDist >= sightLength) + close = to; + else + close = from + sightDir * alongDist; + + // if closest point is within smoke radius, the line overlaps the smoke cloud + Vector toClose = close - smokeOrigin; + float lengthSq = toClose.LengthSqr(); + + if (lengthSq < smokeRadiusSq) + { + // some portion of the ray intersects the cloud + + float fromSq = toGrenade.LengthSqr(); + float toSq = (smokeOrigin - to).LengthSqr(); + + if (fromSq < smokeRadiusSq) + { + if (toSq < smokeRadiusSq) + { + // both 'from' and 'to' lie within the cloud + // entire length is smoked + totalSmokedLength += (to - from).Length(); + } + else + { + // 'from' is inside the cloud, 'to' is outside + // compute half of total smoked length as if ray crosses entire cloud chord + float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq ); + + if (alongDist > 0.0f) + { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - from).Length(); + } + else + { + // ray starts after 'close' + totalSmokedLength += halfSmokedLength - (close - from).Length(); + } + + } + } + else if (toSq < smokeRadiusSq) + { + // 'from' is outside the cloud, 'to' is inside + // compute half of total smoked length as if ray crosses entire cloud chord + float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq ); + + Vector v = to - smokeOrigin; + if (DotProduct( v, sightDir ) > 0.0f) + { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - to).Length(); + } + else + { + // ray ends before 'close' + totalSmokedLength += halfSmokedLength - (close - to).Length(); + } + } + else + { + // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it + // determine the length of the chord that crosses the cloud + float smokedLength = 2.0f * (float)sqrt( smokeRadiusSq - lengthSq ); + + totalSmokedLength += smokedLength; + } + } + } + } + + // define how much smoke a bot can see thru + const float maxSmokedLength = 0.7f * SmokeGrenadeRadius; + + // return true if the total length of smoke-covered line-of-sight is too much + return (totalSmokedLength > maxSmokedLength); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBotManager::ClearDebugMessages( void ) +{ + m_debugMessageCount = 0; + m_currentDebugMessage = -1; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add a new debug message to the message history + */ +void CBotManager::AddDebugMessage( const char *msg ) +{ + if (++m_currentDebugMessage >= MAX_DBG_MSGS) + { + m_currentDebugMessage = 0; + } + + if (m_debugMessageCount < MAX_DBG_MSGS) + { + ++m_debugMessageCount; + } + + Q_strncpy( m_debugMessage[ m_currentDebugMessage ].m_string, msg, MAX_DBG_MSG_SIZE ); + m_debugMessage[ m_currentDebugMessage ].m_age.Start(); +} diff --git a/game/shared/cstrike/bot/bot_manager.h b/game/shared/cstrike/bot/bot_manager.h new file mode 100644 index 0000000..b7d2bd1 --- /dev/null +++ b/game/shared/cstrike/bot/bot_manager.h @@ -0,0 +1,195 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#ifndef BASE_CONTROL_H +#define BASE_CONTROL_H + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +extern float g_BotUpkeepInterval; ///< duration between bot upkeeps +extern float g_BotUpdateInterval; ///< duration between bot updates +const int g_BotUpdateSkipCount = 2; ///< number of upkeep periods to skip update + +class CNavArea; + +/// TODO: move CS-specific defines into CSBot files +enum +{ + SmokeGrenadeRadius = 155, + FlashbangGrenadeRadius = 115, + HEGrenadeRadius = 115, +}; + +//-------------------------------------------------------------------------------------------------------------- +class CBaseGrenade; + +/** + * An ActiveGrenade is a representation of a grenade in the world + * NOTE: Currently only used for smoke grenade line-of-sight testing + * @todo Use system allow bots to avoid HE and Flashbangs + */ +class ActiveGrenade +{ +public: + ActiveGrenade( CBaseGrenade *grenadeEntity ); + + void OnEntityGone( void ); ///< called when the grenade in the world goes away + void Update( void ); ///< called every frame + bool IsValid( void ) const ; ///< return true if this grenade is valid + + bool IsEntity( CBaseGrenade *grenade ) const { return (grenade == m_entity) ? true : false; } + CBaseGrenade *GetEntity( void ) const { return m_entity; } + + const Vector &GetDetonationPosition( void ) const { return m_detonationPosition; } + const Vector &GetPosition( void ) const; + bool IsSmoke( void ) const { return m_isSmoke; } + bool IsFlashbang( void ) const { return m_isFlashbang; } + CBaseGrenade *GetGrenade( void ) { return m_entity; } + float GetRadius( void ) const { return m_radius; } + void SetRadius( float radius ) { m_radius = radius; } + +private: + CBaseGrenade *m_entity; ///< the entity + Vector m_detonationPosition; ///< the location where the grenade detonated (smoke) + float m_dieTimestamp; ///< time this should go away after m_entity is NULL + bool m_isSmoke; ///< true if this is a smoke grenade + bool m_isFlashbang; ///< true if this is a flashbang grenade + float m_radius; +}; + +typedef CUtlLinkedList<ActiveGrenade *> ActiveGrenadeList; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * This class manages all active bots, propagating events to them and updating them. + */ +class CBotManager +{ +public: + CBotManager(); + virtual ~CBotManager(); + + CBasePlayer *AllocateAndBindBotEntity( edict_t *ed ); ///< allocate the appropriate entity for the bot and bind it to the given edict + virtual CBasePlayer *AllocateBotEntity( void ) = 0; ///< factory method to allocate the appropriate entity for the bot + + virtual void ClientDisconnect( CBaseEntity *entity ) = 0; + virtual bool ClientCommand( CBasePlayer *player, const CCommand &args ) = 0; + + virtual void ServerActivate( void ) = 0; + virtual void ServerDeactivate( void ) = 0; + virtual bool ServerCommand( const char * pcmd ) = 0; + + virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins + virtual void StartFrame( void ); ///< (EXTEND) called each frame + + virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const = 0; ///< return priority of player (0 = max pri) + + + void AddGrenade( CBaseGrenade *grenade ); ///< add an active grenade to the bot's awareness + void RemoveGrenade( CBaseGrenade *grenade ); ///< the grenade entity in the world is going away + void SetGrenadeRadius( CBaseGrenade *grenade, float radius ); ///< the radius of the grenade entity (or associated smoke cloud) + void ValidateActiveGrenades( void ); ///< destroy any invalid active grenades + void DestroyAllGrenades( void ); + bool IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat = 1.0f ); ///< return true if line intersects smoke volume, with grenade radius increased by the grenadeBloat factor + bool IsInsideSmokeCloud( const Vector *pos ); ///< return true if position is inside a smoke cloud + + // + // Invoke functor on all active grenades. + // If any functor call return false, return false. Otherwise, return true. + // + template < typename T > + bool ForEachGrenade( T &func ) + { + int it = m_activeGrenadeList.Head(); + + while( it != m_activeGrenadeList.InvalidIndex() ) + { + ActiveGrenade *ag = m_activeGrenadeList[ it ]; + + int current = it; + it = m_activeGrenadeList.Next( it ); + + // lazy validation + if (!ag->IsValid()) + { + m_activeGrenadeList.Remove( current ); + delete ag; + continue; + } + else + { + if (func( ag ) == false) + { + return false; + } + } + } + + return true; + } + + enum { MAX_DBG_MSG_SIZE = 1024 }; + struct DebugMessage + { + char m_string[ MAX_DBG_MSG_SIZE ]; + IntervalTimer m_age; + }; + + // debug message history ------------------------------------------------------------------------------- + int GetDebugMessageCount( void ) const; ///< get number of debug messages in history + const DebugMessage *GetDebugMessage( int which = 0 ) const; ///< return the debug message emitted by the bot (0 = most recent) + void ClearDebugMessages( void ); + void AddDebugMessage( const char *msg ); + + +private: + ActiveGrenadeList m_activeGrenadeList;///< the list of active grenades the bots are aware of + + enum { MAX_DBG_MSGS = 6 }; + DebugMessage m_debugMessage[ MAX_DBG_MSGS ]; ///< debug message history + int m_debugMessageCount; + int m_currentDebugMessage; + + IntervalTimer m_frameTimer; ///< for measuring each frame's duration +}; + + +inline CBasePlayer *CBotManager::AllocateAndBindBotEntity( edict_t *ed ) +{ + CBasePlayer::s_PlayerEdict = ed; + return AllocateBotEntity(); +} + +inline int CBotManager::GetDebugMessageCount( void ) const +{ + return m_debugMessageCount; +} + +inline const CBotManager::DebugMessage *CBotManager::GetDebugMessage( int which ) const +{ + if (which >= m_debugMessageCount) + return NULL; + + int i = m_currentDebugMessage - which; + if (i < 0) + i += MAX_DBG_MSGS; + + return &m_debugMessage[ i ]; +} + + + + + +// global singleton to create and control bots +extern CBotManager *TheBots; + + +#endif diff --git a/game/shared/cstrike/bot/bot_profile.cpp b/game/shared/cstrike/bot/bot_profile.cpp new file mode 100644 index 0000000..13da6b0 --- /dev/null +++ b/game/shared/cstrike/bot/bot_profile.cpp @@ -0,0 +1,704 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#include "cbase.h" + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +#define DEFINE_DIFFICULTY_NAMES +#include "bot_profile.h" +#include "shared_util.h" + +#include "bot.h" +#include "bot_util.h" +#include "cs_bot.h" // BOTPORT: Remove this CS dependency + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +BotProfileManager *TheBotProfiles = NULL; + + +//-------------------------------------------------------------------------------------------------------- +/** + * Generates a filename-decorated skin name + */ +static const char * GetDecoratedSkinName( const char *name, const char *filename ) +{ + const int BufLen = _MAX_PATH + 64; + static char buf[BufLen]; + Q_snprintf( buf, sizeof( buf ), "%s/%s", filename, name ); + return buf; +} + +//-------------------------------------------------------------------------------------------------------------- +const char* BotProfile::GetWeaponPreferenceAsString( int i ) const +{ + if ( i < 0 || i >= m_weaponPreferenceCount ) + return NULL; + + return WeaponIDToAlias( m_weaponPreference[ i ] ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this profile has a primary weapon preference + */ +bool BotProfile::HasPrimaryPreference( void ) const +{ + for( int i=0; i<m_weaponPreferenceCount; ++i ) + { + if (IsPrimaryWeapon( m_weaponPreference[i] )) + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this profile has a pistol weapon preference + */ +bool BotProfile::HasPistolPreference( void ) const +{ + for( int i=0; i<m_weaponPreferenceCount; ++i ) + if (IsSecondaryWeapon( m_weaponPreference[i] )) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this profile is valid for the specified team + */ +bool BotProfile::IsValidForTeam( int team ) const +{ + return ( team == TEAM_UNASSIGNED || m_teams == TEAM_UNASSIGNED || team == m_teams ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** +* Return true if this profile inherits from the specified template +*/ +bool BotProfile::InheritsFrom( const char *name ) const +{ + if ( WildcardMatch( name, GetName() ) ) + return true; + + for ( int i=0; i<m_templates.Count(); ++i ) + { + const BotProfile *queryTemplate = m_templates[i]; + if ( queryTemplate->InheritsFrom( name ) ) + { + return true; + } + } + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Constructor + */ +BotProfileManager::BotProfileManager( void ) +{ + m_nextSkin = 0; + for (int i=0; i<NumCustomSkins; ++i) + { + m_skins[i] = NULL; + m_skinFilenames[i] = NULL; + m_skinModelnames[i] = NULL; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Load the bot profile database + */ +void BotProfileManager::Init( const char *filename, unsigned int *checksum ) +{ + FileHandle_t file = filesystem->Open( filename, "r" ); + + if (!file) + { + if ( true ) // UTIL_IsGame( "czero" ) ) + { + CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename ); + } + return; + } + + int dataLength = filesystem->Size( filename ); + char *dataPointer = new char[ dataLength ]; + int dataReadLength = filesystem->Read( dataPointer, dataLength, file ); + filesystem->Close( file ); + if ( dataReadLength > 0 ) + { + // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and + // return fewer bytes than we were expecting. + dataPointer[ dataReadLength - 1 ] = 0; + } + + const char *dataFile = dataPointer; + + // compute simple checksum + if (checksum) + { + *checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength ); + } + + BotProfile defaultProfile; + + // + // Parse the BotProfile.db into BotProfile instances + // + while( true ) + { + dataFile = SharedParse( dataFile ); + if (!dataFile) + break; + + char *token = SharedGetToken(); + + bool isDefault = (!stricmp( token, "Default" )); + bool isTemplate = (!stricmp( token, "Template" )); + bool isCustomSkin = (!stricmp( token, "Skin" )); + + if ( isCustomSkin ) + { + const int BufLen = 64; + char skinName[BufLen]; + + // get skin name + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + Q_snprintf( skinName, sizeof( skinName ), "%s", token ); + + // get attribute name + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + if (stricmp( "Model", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); + delete [] dataPointer; + return; + } + + // eat '=' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + if (strcmp( "=", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + delete [] dataPointer; + return; + } + + // get attribute value + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + + const char *decoratedName = GetDecoratedSkinName( skinName, filename ); + bool skinExists = GetCustomSkinIndex( decoratedName ) > 0; + if ( m_nextSkin < NumCustomSkins && !skinExists ) + { + // decorate the name + m_skins[ m_nextSkin ] = CloneString( decoratedName ); + + // construct the model filename + m_skinModelnames[ m_nextSkin ] = CloneString( token ); + m_skinFilenames[ m_nextSkin ] = new char[ strlen(token)*2 + strlen("models/player//.mdl") + 1 ]; + Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token ); + ++m_nextSkin; + } + + // eat 'End' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + if (strcmp( "End", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + delete [] dataPointer; + return; + } + + continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc. + } + + // encountered a new profile + BotProfile *profile; + + if (isDefault) + { + profile = &defaultProfile; + } + else + { + profile = new BotProfile; + + // always inherit from Default + *profile = defaultProfile; + } + + // do inheritance in order of appearance + if (!isTemplate && !isDefault) + { + const BotProfile *inherit = NULL; + + // template names are separated by "+" + while(true) + { + char *c = strchr( token, '+' ); + if (c) + *c = '\000'; + + // find the given template name + FOR_EACH_LL( m_templateList, it ) + { + BotProfile *profile = m_templateList[ it ]; + if (!stricmp( profile->GetName(), token )) + { + inherit = profile; + break; + } + } + + if (inherit == NULL) + { + CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token ); + delete [] dataPointer; + return; + } + + // inherit the data + profile->Inherit( inherit, &defaultProfile ); + + if (c == NULL) + break; + + token = c+1; + } + } + + + // get name of this profile + if (!isDefault) + { + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename ); + delete [] dataPointer; + return; + } + profile->m_name = CloneString( SharedGetToken() ); + + /** + * HACK HACK + * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's + * preference towards silencers based on his name. + */ + if ( profile->m_name[0] % 2 ) + { + profile->m_prefersSilencer = true; + } + } + + // read attributes for this profile + bool isFirstWeaponPref = true; + while( true ) + { + // get next token + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + + // check for End delimiter + if (!stricmp( token, "End" )) + break; + + // found attribute name - keep it + char attributeName[64]; + strcpy( attributeName, token ); + + // eat '=' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + delete [] dataPointer; + return; + } + + token = SharedGetToken(); + if (strcmp( "=", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + delete [] dataPointer; + return; + } + + // get attribute value + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); + delete [] dataPointer; + return; + } + token = SharedGetToken(); + + // store value in appropriate attribute + if (!stricmp( "Aggression", attributeName )) + { + profile->m_aggression = (float)atof(token) / 100.0f; + } + else if (!stricmp( "Skill", attributeName )) + { + profile->m_skill = (float)atof(token) / 100.0f; + } + else if (!stricmp( "Skin", attributeName )) + { + profile->m_skin = atoi(token); + if ( profile->m_skin == 0 ) + { + // atoi() failed - try to look up a custom skin by name + profile->m_skin = GetCustomSkinIndex( token, filename ); + } + } + else if (!stricmp( "Teamwork", attributeName )) + { + profile->m_teamwork = (float)atof(token) / 100.0f; + } + else if (!stricmp( "Cost", attributeName )) + { + profile->m_cost = atoi(token); + } + else if (!stricmp( "VoicePitch", attributeName )) + { + profile->m_voicePitch = atoi(token); + } + else if (!stricmp( "VoiceBank", attributeName )) + { + profile->m_voiceBank = FindVoiceBankIndex( token ); + } + else if (!stricmp( "WeaponPreference", attributeName )) + { + // weapon preferences override parent prefs + if (isFirstWeaponPref) + { + isFirstWeaponPref = false; + profile->m_weaponPreferenceCount = 0; + } + + if (!stricmp( token, "none" )) + { + profile->m_weaponPreferenceCount = 0; + } + else + { + if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS) + { + profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID( token ); + } + } + } + else if (!stricmp( "ReactionTime", attributeName )) + { + profile->m_reactionTime = (float)atof(token); + +#ifndef GAMEUI_EXPORTS + // subtract off latency due to "think" update rate. + // In GameUI, we don't really care. + //profile->m_reactionTime -= g_BotUpdateInterval; +#endif + + } + else if (!stricmp( "AttackDelay", attributeName )) + { + profile->m_attackDelay = (float)atof(token); + } + else if (!stricmp( "Difficulty", attributeName )) + { + // override inheritance + profile->m_difficultyFlags = 0; + + // parse bit flags + while(true) + { + char *c = strchr( token, '+' ); + if (c) + *c = '\000'; + + for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i ) + if (!stricmp( BotDifficultyName[i], token )) + profile->m_difficultyFlags |= (1 << i); + + if (c == NULL) + break; + + token = c+1; + } + } + else if (!stricmp( "Team", attributeName )) + { + if ( !stricmp( token, "T" ) ) + { + profile->m_teams = TEAM_TERRORIST; + } + else if ( !stricmp( token, "CT" ) ) + { + profile->m_teams = TEAM_CT; + } + else + { + profile->m_teams = TEAM_UNASSIGNED; + } + } + else + { + CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName ); + } + } + + if (!isDefault) + { + if (isTemplate) + { + // add to template list + m_templateList.AddToTail( profile ); + } + else + { + // add profile to the master list + m_profileList.AddToTail( profile ); + } + } + } + + delete [] dataPointer; +} + +//-------------------------------------------------------------------------------------------------------------- +BotProfileManager::~BotProfileManager( void ) +{ + Reset(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Free all bot profiles + */ +void BotProfileManager::Reset( void ) +{ + m_profileList.PurgeAndDeleteElements(); + m_templateList.PurgeAndDeleteElements(); + + int i; + + for (i=0; i<NumCustomSkins; ++i) + { + if ( m_skins[i] ) + { + delete[] m_skins[i]; + m_skins[i] = NULL; + } + if ( m_skinFilenames[i] ) + { + delete[] m_skinFilenames[i]; + m_skinFilenames[i] = NULL; + } + if ( m_skinModelnames[i] ) + { + delete[] m_skinModelnames[i]; + m_skinModelnames[i] = NULL; + } + } + + for ( i=0; i<m_voiceBanks.Count(); ++i ) + { + delete[] m_voiceBanks[i]; + } + m_voiceBanks.RemoveAll(); +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns custom skin name at a particular index + */ +const char * BotProfileManager::GetCustomSkin( int index ) +{ + if ( index < FirstCustomSkin || index > LastCustomSkin ) + { + return NULL; + } + + return m_skins[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns custom skin filename at a particular index + */ +const char * BotProfileManager::GetCustomSkinFname( int index ) +{ + if ( index < FirstCustomSkin || index > LastCustomSkin ) + { + return NULL; + } + + return m_skinFilenames[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns custom skin modelname at a particular index + */ +const char * BotProfileManager::GetCustomSkinModelname( int index ) +{ + if ( index < FirstCustomSkin || index > LastCustomSkin ) + { + return NULL; + } + + return m_skinModelnames[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given) + */ +int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename ) +{ + const char * skinName = name; + if ( filename ) + { + skinName = GetDecoratedSkinName( name, filename ); + } + + for (int i=0; i<NumCustomSkins; ++i) + { + if ( m_skins[i] ) + { + if ( !stricmp( skinName, m_skins[i] ) ) + { + return FirstCustomSkin + i; + } + } + } + return 0; +} + + +//-------------------------------------------------------------------------------------------------------- +/** + * return index of the (custom) bot phrase db, inserting it if needed + */ +int BotProfileManager::FindVoiceBankIndex( const char *filename ) +{ + int index = 0; + + for ( int i=0; i<m_voiceBanks.Count(); ++i ) + { + if ( !stricmp( filename, m_voiceBanks[i] ) ) + { + return index; + } + } + + m_voiceBanks.AddToTail( CloneString( filename ) ); + return index; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return random unused profile that matches the given difficulty level + */ +const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const +{ + // count up valid profiles + CUtlVector< const BotProfile * > profiles; + FOR_EACH_LL( m_profileList, it ) + { + const BotProfile *profile = m_profileList[ it ]; + + // Match difficulty + if ( !profile->IsDifficulty( difficulty ) ) + continue; + + // Prevent duplicate names + if ( UTIL_IsNameTaken( profile->GetName() ) ) + continue; + + // Match team choice + if ( !profile->IsValidForTeam( team ) ) + continue; + + // Match desired weapon + if ( weaponType != WEAPONTYPE_UNKNOWN ) + { + if ( !profile->GetWeaponPreferenceCount() ) + continue; + + if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) ) + continue; + } + + profiles.AddToTail( profile ); + } + + if ( !profiles.Count() ) + return NULL; + + // select one at random + int which = RandomInt( 0, profiles.Count()-1 ); + return profiles[which]; +} + diff --git a/game/shared/cstrike/bot/bot_profile.h b/game/shared/cstrike/bot/bot_profile.h new file mode 100644 index 0000000..172380a --- /dev/null +++ b/game/shared/cstrike/bot/bot_profile.h @@ -0,0 +1,251 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#ifndef _BOT_PROFILE_H_ +#define _BOT_PROFILE_H_ + +#pragma warning( disable : 4786 ) // long STL names get truncated in browse info. + +#include "bot_constants.h" +#include "bot_util.h" +#include "cs_weapon_parse.h" + +enum +{ + FirstCustomSkin = 100, + NumCustomSkins = 100, + LastCustomSkin = FirstCustomSkin + NumCustomSkins - 1, +}; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * A BotProfile describes the "personality" of a given bot + */ +class BotProfile +{ +public: + BotProfile( void ) + { + m_name = NULL; + m_aggression = 0.0f; + m_skill = 0.0f; + m_teamwork = 0.0f; + m_weaponPreferenceCount = 0; + m_cost = 0; + m_skin = 0; + m_difficultyFlags = 0; + m_voicePitch = 100; + m_reactionTime = 0.3f; + m_attackDelay = 0.0f; + m_teams = TEAM_UNASSIGNED; + m_voiceBank = 0; + m_prefersSilencer = false; + } + + ~BotProfile( void ) + { + if ( m_name ) + delete [] m_name; + } + + const char *GetName( void ) const { return m_name; } ///< return bot's name + float GetAggression( void ) const { return m_aggression; } + float GetSkill( void ) const { return m_skill; } + float GetTeamwork( void ) const { return m_teamwork; } + + CSWeaponID GetWeaponPreference( int i ) const { return m_weaponPreference[ i ]; } + const char *GetWeaponPreferenceAsString( int i ) const; + int GetWeaponPreferenceCount( void ) const { return m_weaponPreferenceCount; } + bool HasPrimaryPreference( void ) const; ///< return true if this profile has a primary weapon preference + bool HasPistolPreference( void ) const; ///< return true if this profile has a pistol weapon preference + + int GetCost( void ) const { return m_cost; } + int GetSkin( void ) const { return m_skin; } + bool IsDifficulty( BotDifficultyType diff ) const; ///< return true if this profile can be used for the given difficulty level + int GetVoicePitch( void ) const { return m_voicePitch; } + float GetReactionTime( void ) const { return m_reactionTime; } + float GetAttackDelay( void ) const { return m_attackDelay; } + int GetVoiceBank() const { return m_voiceBank; } + + bool IsValidForTeam( int team ) const; + + bool PrefersSilencer() const { return m_prefersSilencer; } + + bool InheritsFrom( const char *name ) const; + +private: + friend class BotProfileManager; ///< for loading profiles + + void Inherit( const BotProfile *parent, const BotProfile *baseline ); ///< copy values from parent if they differ from baseline + + char *m_name; ///< the bot's name + float m_aggression; ///< percentage: 0 = coward, 1 = berserker + float m_skill; ///< percentage: 0 = terrible, 1 = expert + float m_teamwork; ///< percentage: 0 = rogue, 1 = complete obeyance to team, lots of comm + + enum { MAX_WEAPON_PREFS = 16 }; + CSWeaponID m_weaponPreference[ MAX_WEAPON_PREFS ]; ///< which weapons this bot likes to use, in order of priority + int m_weaponPreferenceCount; + + int m_cost; ///< reputation point cost for career mode + int m_skin; ///< "skin" index + unsigned char m_difficultyFlags; ///< bits set correspond to difficulty levels this is valid for + int m_voicePitch; ///< the pitch shift for bot chatter (100 = normal) + float m_reactionTime; //< our reaction time in seconds + float m_attackDelay; ///< time in seconds from when we notice an enemy to when we open fire + int m_teams; ///< teams for which this profile is valid + + bool m_prefersSilencer; ///< does the bot prefer to use silencers? + + int m_voiceBank; ///< Index of the BotChatter.db voice bank this profile uses (0 is the default) + + CUtlVector< const BotProfile * > m_templates; ///< List of templates we inherit from +}; +typedef CUtlLinkedList<BotProfile *> BotProfileList; + + +inline bool BotProfile::IsDifficulty( BotDifficultyType diff ) const +{ + return (m_difficultyFlags & (1 << diff)) ? true : false; +} + +/** + * Copy in data from parent if it differs from the baseline + */ +inline void BotProfile::Inherit( const BotProfile *parent, const BotProfile *baseline ) +{ + if (parent->m_aggression != baseline->m_aggression) + m_aggression = parent->m_aggression; + + if (parent->m_skill != baseline->m_skill) + m_skill = parent->m_skill; + + if (parent->m_teamwork != baseline->m_teamwork) + m_teamwork = parent->m_teamwork; + + if (parent->m_weaponPreferenceCount != baseline->m_weaponPreferenceCount) + { + m_weaponPreferenceCount = parent->m_weaponPreferenceCount; + for( int i=0; i<parent->m_weaponPreferenceCount; ++i ) + m_weaponPreference[i] = parent->m_weaponPreference[i]; + } + + if (parent->m_cost != baseline->m_cost) + m_cost = parent->m_cost; + + if (parent->m_skin != baseline->m_skin) + m_skin = parent->m_skin; + + if (parent->m_difficultyFlags != baseline->m_difficultyFlags) + m_difficultyFlags = parent->m_difficultyFlags; + + if (parent->m_voicePitch != baseline->m_voicePitch) + m_voicePitch = parent->m_voicePitch; + + if (parent->m_reactionTime != baseline->m_reactionTime) + m_reactionTime = parent->m_reactionTime; + + if (parent->m_attackDelay != baseline->m_attackDelay) + m_attackDelay = parent->m_attackDelay; + + if (parent->m_teams != baseline->m_teams) + m_teams = parent->m_teams; + + if (parent->m_voiceBank != baseline->m_voiceBank) + m_voiceBank = parent->m_voiceBank; + + m_templates.AddToTail( parent ); +} + + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * The BotProfileManager defines the interface to accessing BotProfiles + */ +class BotProfileManager +{ +public: + BotProfileManager( void ); + ~BotProfileManager( void ); + + void Init( const char *filename, unsigned int *checksum = NULL ); + void Reset( void ); + + /// given a name, return a profile + const BotProfile *GetProfile( const char *name, int team ) const + { + FOR_EACH_LL( m_profileList, it ) + { + BotProfile *profile = m_profileList[ it ]; + + if ( !stricmp( name, profile->GetName() ) && profile->IsValidForTeam( team ) ) + return profile; + } + + return NULL; + } + + /// given a template name and difficulty, return a profile + const BotProfile *GetProfileMatchingTemplate( const char *profileName, int team, BotDifficultyType difficulty ) const + { + FOR_EACH_LL( m_profileList, it ) + { + BotProfile *profile = m_profileList[ it ]; + + if ( !profile->InheritsFrom( profileName ) ) + continue; + + if ( !profile->IsValidForTeam( team ) ) + continue; + + if ( !profile->IsDifficulty( difficulty ) ) + continue; + + if ( UTIL_IsNameTaken( profile->GetName() ) ) + continue; + + return profile; + } + + return NULL; + } + + const BotProfileList *GetProfileList( void ) const { return &m_profileList; } ///< return list of all profiles + + const BotProfile *GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const; ///< return random unused profile that matches the given difficulty level + + const char * GetCustomSkin( int index ); ///< Returns custom skin name at a particular index + const char * GetCustomSkinModelname( int index ); ///< Returns custom skin modelname at a particular index + const char * GetCustomSkinFname( int index ); ///< Returns custom skin filename at a particular index + int GetCustomSkinIndex( const char *name, const char *filename = NULL ); ///< Looks up a custom skin index by name + + typedef CUtlVector<char *> VoiceBankList; + const VoiceBankList *GetVoiceBanks( void ) const { return &m_voiceBanks; } + int FindVoiceBankIndex( const char *filename ); ///< return index of the (custom) bot phrase db, inserting it if needed + +protected: + BotProfileList m_profileList; ///< the list of all bot profiles + BotProfileList m_templateList; ///< the list of all bot templates + + VoiceBankList m_voiceBanks; + + char *m_skins[ NumCustomSkins ]; ///< Custom skin names + char *m_skinModelnames[ NumCustomSkins ]; ///< Custom skin modelnames + char *m_skinFilenames[ NumCustomSkins ]; ///< Custom skin filenames + int m_nextSkin; ///< Next custom skin to allocate +}; + +/// the global singleton for accessing BotProfiles +extern BotProfileManager *TheBotProfiles; + + +#endif // _BOT_PROFILE_H_ diff --git a/game/shared/cstrike/bot/bot_util.cpp b/game/shared/cstrike/bot/bot_util.cpp new file mode 100644 index 0000000..bd10ff2 --- /dev/null +++ b/game/shared/cstrike/bot/bot_util.cpp @@ -0,0 +1,604 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 2003 + +#include "cbase.h" +#include "cs_shareddefs.h" +#include "engine/IEngineSound.h" +#include "KeyValues.h" + +#include "bot.h" +#include "bot_util.h" +#include "bot_profile.h" + +#include "cs_bot.h" +#include <ctype.h> +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static int s_iBeamSprite = 0; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if given name is already in use by another player + */ +bool UTIL_IsNameTaken( const char *name, bool ignoreHumans ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (player->IsPlayer() && player->IsBot()) + { + // bots can have prefixes so we need to check the name + // against the profile name instead. + CCSBot *bot = dynamic_cast<CCSBot *>(player); + if ( bot && bot->GetProfile()->GetName() && FStrEq(name, bot->GetProfile()->GetName())) + { + return true; + } + } + else + { + if (!ignoreHumans) + { + if (FStrEq( name, player->GetPlayerName() )) + return true; + } + } + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +int UTIL_ClientsInGame( void ) +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the number of non-bots on the given team + */ +int UTIL_HumansOnTeam( int teamID, bool isAlive ) +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *entity = UTIL_PlayerByIndex( i ); + + if ( entity == NULL ) + continue; + + CBasePlayer *player = static_cast<CBasePlayer *>( entity ); + + if (player->IsBot()) + continue; + + if (player->GetTeamNumber() != teamID) + continue; + + if (isAlive && !player->IsAlive()) + continue; + + count++; + } + + return count; +} + + +//-------------------------------------------------------------------------------------------------------------- +int UTIL_BotsInGame( void ) +{ + int count = 0; + + for (int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex( i )); + + if ( player == NULL ) + continue; + + if ( !player->IsBot() ) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Kick a bot from the given team. If no bot exists on the team, return false. + */ +bool UTIL_KickBotFromTeam( int kickTeam ) +{ + int i; + + // try to kick a dead bot first + for ( i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (!player->IsBot()) + continue; + + if (!player->IsAlive() && player->GetTeamNumber() == kickTeam) + { + // its a bot on the right team - kick it + engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) ); + + return true; + } + } + + // no dead bots, kick any bot on the given team + for ( i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (!player->IsBot()) + continue; + + if (player->GetTeamNumber() == kickTeam) + { + // its a bot on the right team - kick it + engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) ); + + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if all of the members of the given team are bots + */ +bool UTIL_IsTeamAllBots( int team ) +{ + int botCount = 0; + + for( int i=1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + // skip players on other teams + if (player->GetTeamNumber() != team) + continue; + + // if not a bot, fail the test + if (!player->IsBot()) + return false; + + // is a bot on given team + ++botCount; + } + + // if team is empty, there are no bots + return (botCount) ? true : false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest active player to the given position. + * If 'distance' is non-NULL, the distance to the closest player is returned in it. + */ +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance ) +{ + CBasePlayer *closePlayer = NULL; + float closeDistSq = 999999999999.9f; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (!IsEntityValid( player )) + continue; + + if (!player->IsAlive()) + continue; + + Vector playerOrigin = GetCentroid( player ); + float distSq = (playerOrigin - pos).LengthSqr(); + if (distSq < closeDistSq) + { + closeDistSq = distSq; + closePlayer = static_cast<CBasePlayer *>( player ); + } + } + + if (distance) + *distance = (float)sqrt( closeDistSq ); + + return closePlayer; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest active player on the given team to the given position. + * If 'distance' is non-NULL, the distance to the closest player is returned in it. + */ +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance ) +{ + CBasePlayer *closePlayer = NULL; + float closeDistSq = 999999999999.9f; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (!IsEntityValid( player )) + continue; + + if (!player->IsAlive()) + continue; + + if (player->GetTeamNumber() != team) + continue; + + Vector playerOrigin = GetCentroid( player ); + float distSq = (playerOrigin - pos).LengthSqr(); + if (distSq < closeDistSq) + { + closeDistSq = distSq; + closePlayer = static_cast<CBasePlayer *>( player ); + } + } + + if (distance) + *distance = (float)sqrt( closeDistSq ); + + return closePlayer; +} + +//-------------------------------------------------------------------------------------------------------------- +// Takes the bot pointer and constructs the net name using the current bot name prefix. +void UTIL_ConstructBotNetName( char *name, int nameLength, const BotProfile *profile ) +{ + if (profile == NULL) + { + name[0] = 0; + return; + } + + // if there is no bot prefix just use the profile name. + if ((cv_bot_prefix.GetString() == NULL) || (strlen(cv_bot_prefix.GetString()) == 0)) + { + Q_strncpy( name, profile->GetName(), nameLength ); + return; + } + + // find the highest difficulty + const char *diffStr = BotDifficultyName[0]; + for ( int i=BOT_EXPERT; i>0; --i ) + { + if ( profile->IsDifficulty( (BotDifficultyType)i ) ) + { + diffStr = BotDifficultyName[i]; + break; + } + } + + const char *weaponStr = NULL; + if ( profile->GetWeaponPreferenceCount() ) + { + weaponStr = profile->GetWeaponPreferenceAsString( 0 ); + + const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr ); + + char wpnName[128]; + Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias ); + WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName ); + if ( hWpnInfo != GetInvalidWeaponInfoHandle() ) + { + CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); + if ( pWeaponInfo ) + { + CSWeaponType weaponType = pWeaponInfo->m_WeaponType; + weaponStr = WeaponClassAsString( weaponType ); + } + } + } + if ( !weaponStr ) + { + weaponStr = ""; + } + + char skillStr[16]; + Q_snprintf( skillStr, sizeof( skillStr ), "%.0f", profile->GetSkill()*100 ); + + char temp[MAX_PLAYER_NAME_LENGTH*2]; + char prefix[MAX_PLAYER_NAME_LENGTH*2]; + Q_strncpy( temp, cv_bot_prefix.GetString(), sizeof( temp ) ); + Q_StrSubst( temp, "<difficulty>", diffStr, prefix, sizeof( prefix ) ); + Q_StrSubst( prefix, "<weaponclass>", weaponStr, temp, sizeof( temp ) ); + Q_StrSubst( temp, "<skill>", skillStr, prefix, sizeof( prefix ) ); + Q_snprintf( name, nameLength, "%s %s", prefix, profile->GetName() ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if anyone on the given team can see the given spot + */ +bool UTIL_IsVisibleToTeam( const Vector &spot, int team ) +{ + for( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (!player->IsAlive()) + continue; + + if (player->GetTeamNumber() != team) + continue; + + trace_t result; + UTIL_TraceLine( player->EyePosition(), spot, CONTENTS_SOLID, player, COLLISION_GROUP_NONE, &result ); + + if (result.fraction == 1.0f) + return true; + } + + return false; +} + + +//------------------------------------------------------------------------------------------------------------ +void UTIL_DrawBeamFromEnt( int i, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ) +{ +/* BOTPORT: What is the replacement for MESSAGE_BEGIN? + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd ); // vecEnd = origin??? + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( i ); + WRITE_COORD( vecEnd.x ); + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_SHORT( s_iBeamSprite ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( iLifetime ); // life + WRITE_BYTE( 10 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( bRed ); // r, g, b + WRITE_BYTE( bGreen ); // r, g, b + WRITE_BYTE( bBlue ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + */ +} + + +//------------------------------------------------------------------------------------------------------------ +void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ) +{ + NDebugOverlay::Line( vecStart, vecEnd, bRed, bGreen, bBlue, true, 0.1f ); + + /* + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( vecStart.x ); + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( vecEnd.x ); + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_SHORT( s_iBeamSprite ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( iLifetime ); // life + WRITE_BYTE( 10 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( bRed ); // r, g, b + WRITE_BYTE( bGreen ); // r, g, b + WRITE_BYTE( bBlue ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + */ +} + + +//------------------------------------------------------------------------------------------------------------ +void CONSOLE_ECHO( const char * pszMsg, ... ) +{ + va_list argptr; + static char szStr[1024]; + + va_start( argptr, pszMsg ); + vsprintf( szStr, pszMsg, argptr ); + va_end( argptr ); + + Msg( "%s", szStr ); +} + + +//------------------------------------------------------------------------------------------------------------ +void BotPrecache( void ) +{ + s_iBeamSprite = CBaseEntity::PrecacheModel( "sprites/smoke.spr" ); +} + +//------------------------------------------------------------------------------------------------------------ +#define COS_TABLE_SIZE 256 +static float cosTable[ COS_TABLE_SIZE ]; + +void InitBotTrig( void ) +{ + for( int i=0; i<COS_TABLE_SIZE; ++i ) + { + float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1)); + cosTable[i] = (float)cos( angle ); + } +} + +float BotCOS( float angle ) +{ + angle = AngleNormalizePositive( angle ); + int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f ); + return cosTable[i]; +} + +float BotSIN( float angle ) +{ + angle = AngleNormalizePositive( angle - 90 ); + int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f ); + return cosTable[i]; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Send a "hint" message to all players, dead or alive. + */ +void HintMessageToAllPlayers( const char *message ) +{ + hudtextparms_t textParms; + + textParms.x = -1.0f; + textParms.y = -1.0f; + textParms.fadeinTime = 1.0f; + textParms.fadeoutTime = 5.0f; + textParms.holdTime = 5.0f; + textParms.fxTime = 0.0f; + textParms.r1 = 100; + textParms.g1 = 255; + textParms.b1 = 100; + textParms.r2 = 255; + textParms.g2 = 255; + textParms.b2 = 255; + textParms.effect = 0; + textParms.channel = 0; + + UTIL_HudMessageAll( textParms, message ); +} + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Return true if moving from "start" to "finish" will cross a player's line of fire. + * The path from "start" to "finish" is assumed to be a straight line. + * "start" and "finish" are assumed to be points on the ground. + */ +bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore, int ignoreTeam ) +{ + for ( int p=1; p <= gpGlobals->maxClients; ++p ) + { + CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( p ) ); + + if (!IsEntityValid( player )) + continue; + + if (player == ignore) + continue; + + if (!player->IsAlive()) + continue; + + if (ignoreTeam && player->GetTeamNumber() == ignoreTeam) + continue; + + // compute player's unit aiming vector + Vector viewForward; + AngleVectors( player->EyeAngles() + player->GetPunchAngle(), &viewForward ); + + const float longRange = 5000.0f; + Vector playerOrigin = GetCentroid( player ); + Vector playerTarget = playerOrigin + longRange * viewForward; + + Vector result( 0, 0, 0 ); + if (IsIntersecting2D( start, finish, playerOrigin, playerTarget, &result )) + { + // simple check to see if intersection lies in the Z range of the path + float loZ, hiZ; + + if (start.z < finish.z) + { + loZ = start.z; + hiZ = finish.z; + } + else + { + loZ = finish.z; + hiZ = start.z; + } + + if (result.z >= loZ && result.z <= hiZ + HumanHeight) + return true; + } + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** +* Performs a simple case-insensitive string comparison, honoring trailing * wildcards +*/ +bool WildcardMatch( const char *query, const char *test ) +{ + if ( !query || !test ) + return false; + + while ( *test && *query ) + { + char nameChar = *test; + char queryChar = *query; + if ( tolower(nameChar) != tolower(queryChar) ) // case-insensitive + break; + ++test; + ++query; + } + + if ( *query == 0 && *test == 0 ) + return true; + + // Support trailing * + if ( *query == '*' ) + return true; + + return false; +} + + + diff --git a/game/shared/cstrike/bot/bot_util.h b/game/shared/cstrike/bot/bot_util.h new file mode 100644 index 0000000..572b1db --- /dev/null +++ b/game/shared/cstrike/bot/bot_util.h @@ -0,0 +1,167 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BOT_UTIL_H +#define BOT_UTIL_H + + +#include "convar.h" +#include "util.h" + +//-------------------------------------------------------------------------------------------------------------- +enum PriorityType +{ + PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_UNINTERRUPTABLE +}; + + +extern ConVar cv_bot_traceview; +extern ConVar cv_bot_stop; +extern ConVar cv_bot_show_nav; +extern ConVar cv_bot_walk; +extern ConVar cv_bot_difficulty; +extern ConVar cv_bot_debug; +extern ConVar cv_bot_debug_target; +extern ConVar cv_bot_quota; +extern ConVar cv_bot_quota_mode; +extern ConVar cv_bot_prefix; +extern ConVar cv_bot_allow_rogues; +extern ConVar cv_bot_allow_pistols; +extern ConVar cv_bot_allow_shotguns; +extern ConVar cv_bot_allow_sub_machine_guns; +extern ConVar cv_bot_allow_rifles; +extern ConVar cv_bot_allow_machine_guns; +extern ConVar cv_bot_allow_grenades; +extern ConVar cv_bot_allow_snipers; +extern ConVar cv_bot_allow_shield; +extern ConVar cv_bot_join_team; +extern ConVar cv_bot_join_after_player; +extern ConVar cv_bot_auto_vacate; +extern ConVar cv_bot_zombie; +extern ConVar cv_bot_defer_to_human; +extern ConVar cv_bot_chatter; +extern ConVar cv_bot_profile_db; +extern ConVar cv_bot_dont_shoot; +extern ConVar cv_bot_eco_limit; +extern ConVar cv_bot_auto_follow; +extern ConVar cv_bot_flipout; + +#define RAD_TO_DEG( deg ) ((deg) * 180.0 / M_PI) +#define DEG_TO_RAD( rad ) ((rad) * M_PI / 180.0) + +#define SIGN( num ) (((num) < 0) ? -1 : 1) +#define ABS( num ) (SIGN(num) * (num)) + + +#define CREATE_FAKE_CLIENT ( *g_engfuncs.pfnCreateFakeClient ) +#define GET_USERINFO ( *g_engfuncs.pfnGetInfoKeyBuffer ) +#define SET_KEY_VALUE ( *g_engfuncs.pfnSetKeyValue ) +#define SET_CLIENT_KEY_VALUE ( *g_engfuncs.pfnSetClientKeyValue ) + +class BotProfile; + +extern void BotPrecache( void ); +extern int UTIL_ClientsInGame( void ); + +extern bool UTIL_IsNameTaken( const char *name, bool ignoreHumans = false ); ///< return true if given name is already in use by another player + +#define IS_ALIVE true +extern int UTIL_HumansOnTeam( int teamID, bool isAlive = false ); + +extern int UTIL_BotsInGame( void ); +extern bool UTIL_IsTeamAllBots( int team ); +extern void UTIL_DrawBeamFromEnt( int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ); +extern void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ); +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance = NULL ); +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance = NULL ); +extern bool UTIL_KickBotFromTeam( int kickTeam ); ///< kick a bot from the given team. If no bot exists on the team, return false. + +extern bool UTIL_IsVisibleToTeam( const Vector &spot, int team ); ///< return true if anyone on the given team can see the given spot + +/// return true if moving from "start" to "finish" will cross a player's line of fire. +extern bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore = NULL, int ignoreTeam = 0 ); + +extern void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *bot); ///< constructs a complete name including prefix + +/** + * Echos text to the console, and prints it on the client's screen. This is NOT tied to the developer cvar. + * If you are adding debugging output in cstrike, use UTIL_DPrintf() (debug.h) instead. + */ +extern void CONSOLE_ECHO( PRINTF_FORMAT_STRING const char * pszMsg, ... ); + +extern void InitBotTrig( void ); +extern float BotCOS( float angle ); +extern float BotSIN( float angle ); + +extern void HintMessageToAllPlayers( const char *message ); + +bool WildcardMatch( const char *query, const char *test ); ///< Performs a simple case-insensitive string comparison, honoring trailing * wildcards + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if the given entity is valid + */ +inline bool IsEntityValid( CBaseEntity *entity ) +{ + if (entity == NULL) + return false; + + if (FNullEnt( entity->edict() )) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given two line segments: startA to endA, and startB to endB, return true if they intesect + * and put the intersection point in "result". + * Note that this computes the intersection of the 2D (x,y) projection of the line segments. + */ +inline bool IsIntersecting2D( const Vector &startA, const Vector &endA, + const Vector &startB, const Vector &endB, + Vector *result = NULL ) +{ + float denom = (endA.x - startA.x) * (endB.y - startB.y) - (endA.y - startA.y) * (endB.x - startB.x); + if (denom == 0.0f) + { + // parallel + return false; + } + + float numS = (startA.y - startB.y) * (endB.x - startB.x) - (startA.x - startB.x) * (endB.y - startB.y); + if (numS == 0.0f) + { + // coincident + return true; + } + + float numT = (startA.y - startB.y) * (endA.x - startA.x) - (startA.x - startB.x) * (endA.y - startA.y); + + float s = numS / denom; + if (s < 0.0f || s > 1.0f) + { + // intersection is not within line segment of startA to endA + return false; + } + + float t = numT / denom; + if (t < 0.0f || t > 1.0f) + { + // intersection is not within line segment of startB to endB + return false; + } + + // compute intesection point + if (result) + *result = startA + s * (endA - startA); + + return true; +} + + +#endif diff --git a/game/shared/cstrike/bot/improv_locomotor.h b/game/shared/cstrike/bot/improv_locomotor.h new file mode 100644 index 0000000..1705461 --- /dev/null +++ b/game/shared/cstrike/bot/improv_locomotor.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// improv_locomotor.h +// Interface for moving Improvs along computed paths +// Author: Michael Booth, July 2004 + +#ifndef _IMPROV_LOCOMOTOR_H_ +#define _IMPROV_LOCOMOTOR_H_ + +// TODO: Remove duplicate methods from CImprov, and update CImprov to use this class + +/** + * A locomotor owns the movement of an Improv + */ +class CImprovLocomotor +{ +public: + virtual const Vector &GetCentroid( void ) const = 0; + virtual const Vector &GetFeet( void ) const = 0; ///< return position of "feet" - point below centroid of improv at feet level + virtual const Vector &GetEyes( void ) const = 0; + virtual float GetMoveAngle( void ) const = 0; ///< return direction of movement + + virtual CNavArea *GetLastKnownArea( void ) const = 0; + virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ) = 0; ///< find "simple" ground height, treating current nav area as part of the floor + + virtual void Crouch( void ) = 0; + virtual void StandUp( void ) = 0; ///< "un-crouch" + virtual bool IsCrouching( void ) const = 0; + + virtual void Jump( void ) = 0; ///< initiate a jump + virtual bool IsJumping( void ) const = 0; + + virtual void Run( void ) = 0; ///< set movement speed to running + virtual void Walk( void ) = 0; ///< set movement speed to walking + virtual bool IsRunning( void ) const = 0; + + virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ) = 0; ///< invoked when a ladder is encountered while following a path + virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ) = 0; ///< traverse given ladder + virtual bool IsUsingLadder( void ) const = 0; + + enum MoveToFailureType + { + FAIL_INVALID_PATH, + FAIL_STUCK, + FAIL_FELL_OFF, + }; + virtual void TrackPath( const Vector &pathGoal, float deltaT ) = 0; ///< move along path by following "pathGoal" + virtual void OnMoveToSuccess( const Vector &goal ) { } ///< invoked when an improv reaches its MoveTo goal + virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ) { } ///< invoked when an improv fails to reach a MoveTo goal +}; + +#endif // _IMPROV_LOCOMOTOR_H_ diff --git a/game/shared/cstrike/bot/nav_path.cpp b/game/shared/cstrike/bot/nav_path.cpp new file mode 100644 index 0000000..92285bc --- /dev/null +++ b/game/shared/cstrike/bot/nav_path.cpp @@ -0,0 +1,1208 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// nav_path.cpp +// Encapsulation of a path through space +// Author: Michael S. Booth ([email protected]), November 2003 + +#include "cbase.h" +#include "cs_gamerules.h" +#include "cs_player.h" + +#include "nav_mesh.h" +#include "nav_path.h" +#include "bot_util.h" +#include "improv_locomotor.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +#pragma warning (disable:4701) // disable warning that variable *may* not be initialized +#endif + + +#define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f ) + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Determine actual path positions + */ +bool CNavPath::ComputePathPositions( void ) +{ + if (m_segmentCount == 0) + return false; + + // start in first area's center + m_path[0].pos = m_path[0].area->GetCenter(); + m_path[0].ladder = NULL; + m_path[0].how = NUM_TRAVERSE_TYPES; + + for( int i=1; i<m_segmentCount; ++i ) + { + const PathSegment *from = &m_path[ i-1 ]; + PathSegment *to = &m_path[ i ]; + + if (to->how <= GO_WEST) // walk along the floor to the next area + { + to->ladder = NULL; + + // compute next point, keeping path as straight as possible + from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos ); + + // move goal position into the goal area a bit + const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size + AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist ); + + // we need to walk out of "from" area, so keep Z where we can reach it + to->pos.z = from->area->GetZ( to->pos ); + + // if this is a "jump down" connection, we must insert an additional point on the path + if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false) + { + // this is a "jump down" link + + // compute direction of path just prior to "jump down" + Vector2D dir; + DirectionToVector2D( (NavDirType)to->how, &dir ); + + // shift top of "jump down" out a bit to "get over the ledge" + const float pushDist = 25.0f; + to->pos.x += pushDist * dir.x; + to->pos.y += pushDist * dir.y; + + // insert a duplicate node to represent the bottom of the fall + if (m_segmentCount < MAX_PATH_SEGMENTS-1) + { + // copy nodes down + for( int j=m_segmentCount; j>i; --j ) + m_path[j] = m_path[j-1]; + + // path is one node longer + ++m_segmentCount; + + // move index ahead into the new node we just duplicated + ++i; + + m_path[i].pos.x = to->pos.x + pushDist * dir.x; + m_path[i].pos.y = to->pos.y + pushDist * dir.y; + + // put this one at the bottom of the fall + m_path[i].pos.z = to->area->GetZ( m_path[i].pos ); + } + } + } + else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder + { + // find our ladder + const NavLadderConnectList *list = from->area->GetLadderList( CSNavLadder::LADDER_UP ); + int it; + for( it = list->Head(); it != list->InvalidIndex(); it = list->Next(it)) + { + CSNavLadder *ladder = (*list)[ it ].ladder; + + // can't use "behind" area when ascending... + if (ladder->m_topForwardArea == to->area || + ladder->m_topLeftArea == to->area || + ladder->m_topRightArea == to->area) + { + to->ladder = ladder; + to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth; + break; + } + } + + if (it == list->InvalidIndex()) + { + //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); + return false; + } + } + else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder + { + // find our ladder + const NavLadderConnectList *list = from->area->GetLadderList( CSNavLadder::LADDER_DOWN ); + int it; + for( it = list->Head(); it != list->InvalidIndex(); it = list->Next(it)) + { + CSNavLadder *ladder = (*list)[ it ].ladder; + + if (ladder->m_bottomArea == to->area) + { + to->ladder = ladder; + to->pos = ladder->m_top; + to->pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth; + break; + } + } + + if (it == list->InvalidIndex()) + { + //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); + return false; + } + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if position is at the end of the path + */ +bool CNavPath::IsAtEnd( const Vector &pos ) const +{ + if (!IsValid()) + return false; + + const float epsilon = 20.0f; + return (pos - GetEndpoint()).IsLengthLessThan( epsilon ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return length of path from start to finish + */ +float CNavPath::GetLength( void ) const +{ + float length = 0.0f; + for( int i=1; i<GetSegmentCount(); ++i ) + { + length += (m_path[i].pos - m_path[i-1].pos).Length(); + } + + return length; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end + * @todo Be careful of returning "positions" along one-way drops, ladders, etc. + */ +bool CNavPath::GetPointAlongPath( float distAlong, Vector *pointOnPath ) const +{ + if (!IsValid() || pointOnPath == NULL) + return false; + + if (distAlong <= 0.0f) + { + *pointOnPath = m_path[0].pos; + return true; + } + + float lengthSoFar = 0.0f; + float segmentLength; + Vector dir; + for( int i=1; i<GetSegmentCount(); ++i ) + { + dir = m_path[i].pos - m_path[i-1].pos; + segmentLength = dir.Length(); + + if (segmentLength + lengthSoFar >= distAlong) + { + // desired point is on this segment of the path + float delta = distAlong - lengthSoFar; + float t = delta / segmentLength; + + *pointOnPath = m_path[i].pos + t * dir; + + return true; + } + + lengthSoFar += segmentLength; + } + + *pointOnPath = m_path[ GetSegmentCount()-1 ].pos; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the node index closest to the given distance along the path without going over - returns (-1) if error + */ +int CNavPath::GetSegmentIndexAlongPath( float distAlong ) const +{ + if (!IsValid()) + return -1; + + if (distAlong <= 0.0f) + { + return 0; + } + + float lengthSoFar = 0.0f; + Vector dir; + for( int i=1; i<GetSegmentCount(); ++i ) + { + lengthSoFar += (m_path[i].pos - m_path[i-1].pos).Length(); + + if (lengthSoFar > distAlong) + { + return i-1; + } + } + + return GetSegmentCount()-1; +} + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute closest point on path to given point + * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc + */ +bool CNavPath::FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const +{ + if (!IsValid() || close == NULL) + return false; + + Vector along, toWorldPos; + Vector pos; + const Vector *from, *to; + float length; + float closeLength; + float closeDistSq = 9999999999.9; + float distSq; + + for( int i=startIndex; i<=endIndex; ++i ) + { + from = &m_path[i-1].pos; + to = &m_path[i].pos; + + // compute ray along this path segment + along = *to - *from; + + // make it a unit vector along the path + length = along.NormalizeInPlace(); + + // compute vector from start of segment to our point + toWorldPos = *worldPos - *from; + + // find distance of closest point on ray + closeLength = DotProduct( toWorldPos, along ); + + // constrain point to be on path segment + if (closeLength <= 0.0f) + pos = *from; + else if (closeLength >= length) + pos = *to; + else + pos = *from + closeLength * along; + + distSq = (pos - *worldPos).LengthSqr(); + + // keep the closest point so far + if (distSq < closeDistSq) + { + closeDistSq = distSq; + *close = pos; + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Build trivial path when start and goal are in the same nav area + */ +bool CNavPath::BuildTrivialPath( const Vector &start, const Vector &goal ) +{ + m_segmentCount = 0; + + CNavArea *startArea = TheNavMesh->GetNearestNavArea( start ); + if (startArea == NULL) + return false; + + CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal ); + if (goalArea == NULL) + return false; + + m_segmentCount = 2; + + m_path[0].area = startArea; + m_path[0].pos.x = start.x; + m_path[0].pos.y = start.y; + m_path[0].pos.z = startArea->GetZ( start ); + m_path[0].ladder = NULL; + m_path[0].how = NUM_TRAVERSE_TYPES; + + m_path[1].area = goalArea; + m_path[1].pos.x = goal.x; + m_path[1].pos.y = goal.y; + m_path[1].pos.z = goalArea->GetZ( goal ); + m_path[1].ladder = NULL; + m_path[1].how = NUM_TRAVERSE_TYPES; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw the path for debugging. + */ +void CNavPath::Draw( const Vector &color ) +{ + if (!IsValid()) + return; + + for( int i=1; i<m_segmentCount; ++i ) + { + DrawLine( m_path[i-1].pos + Vector( 0, 0, HalfHumanHeight ), + m_path[i].pos + Vector( 0, 0, HalfHumanHeight ), 2, 255 * color.x, 255 * color.y, 255 * color.z ); + } +} + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Check line of sight from 'anchor' node on path to subsequent nodes until + * we find a node that can't been seen from 'anchor'. + */ +int CNavPath::FindNextOccludedNode( int anchor ) +{ + for( int i=anchor+1; i<m_segmentCount; ++i ) + { + // don't remove ladder nodes + if (m_path[i].ladder) + return i; + + if (!IsWalkableTraceLineClear( m_path[ anchor ].pos, m_path[ i ].pos )) + { + // cant see this node from anchor node + return i; + } + + Vector anchorPlusHalf = m_path[ anchor ].pos + Vector( 0, 0, HalfHumanHeight ); + Vector iPlusHalf = m_path[ i ].pos +Vector( 0, 0, HalfHumanHeight ); + if (!IsWalkableTraceLineClear( anchorPlusHalf, iPlusHalf) ) + { + // cant see this node from anchor node + return i; + } + + Vector anchorPlusFull = m_path[ anchor ].pos + Vector( 0, 0, HumanHeight ); + Vector iPlusFull = m_path[ i ].pos + Vector( 0, 0, HumanHeight ); + if (!IsWalkableTraceLineClear( anchorPlusFull, iPlusFull )) + { + // cant see this node from anchor node + return i; + } + } + + return m_segmentCount; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Smooth out path, removing redundant nodes + */ +void CNavPath::Optimize( void ) +{ +// DONT USE THIS: Optimizing the path results in cutting thru obstacles +return; + + if (m_segmentCount < 3) + return; + + int anchor = 0; + + while( anchor < m_segmentCount ) + { + int occluded = FindNextOccludedNode( anchor ); + int nextAnchor = occluded-1; + + if (nextAnchor > anchor) + { + // remove redundant nodes between anchor and nextAnchor + int removeCount = nextAnchor - anchor - 1; + if (removeCount > 0) + { + for( int i=nextAnchor; i<m_segmentCount; ++i ) + { + m_path[i-removeCount] = m_path[i]; + } + m_segmentCount -= removeCount; + } + } + + ++anchor; + } +} + + +//-------------------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------------------- + +/** + * Constructor + */ +CNavPathFollower::CNavPathFollower( void ) +{ + m_improv = NULL; + m_path = NULL; + + m_segmentIndex = 0; + m_isLadderStarted = false; + + m_isDebug = false; +} + +void CNavPathFollower::Reset( void ) +{ + m_segmentIndex = 1; + m_isLadderStarted = false; + + m_stuckMonitor.Reset(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Move improv along path + */ +void CNavPathFollower::Update( float deltaT, bool avoidObstacles ) +{ + if (m_path == NULL || m_path->IsValid() == false) + return; + + const CNavPath::PathSegment *node = (*m_path)[ m_segmentIndex ]; + + if (node == NULL) + { + m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_INVALID_PATH ); + m_path->Invalidate(); + return; + } + + // handle ladders + /* + if (node->ladder) + { + const Vector *approachPos = NULL; + const Vector *departPos = NULL; + + if (m_segmentIndex) + approachPos = &(*m_path)[ m_segmentIndex-1 ]->pos; + + if (m_segmentIndex < m_path->GetSegmentCount()-1) + departPos = &(*m_path)[ m_segmentIndex+1 ]->pos; + + if (!m_isLadderStarted) + { + // set up ladder movement + m_improv->StartLadder( node->ladder, node->how, approachPos, departPos ); + m_isLadderStarted = true; + } + + // move improv along ladder + if (m_improv->TraverseLadder( node->ladder, node->how, approachPos, departPos, deltaT )) + { + // completed ladder + ++m_segmentIndex; + } + return; + } + */ + + // reset ladder init flag + m_isLadderStarted = false; + + // + // Check if we reached the end of the path + // + const float closeRange = 20.0f; + if ((m_improv->GetFeet() - node->pos).IsLengthLessThan( closeRange )) + { + ++m_segmentIndex; + + if (m_segmentIndex >= m_path->GetSegmentCount()) + { + m_improv->OnMoveToSuccess( m_path->GetEndpoint() ); + m_path->Invalidate(); + return; + } + } + + + m_goal = node->pos; + + const float aheadRange = 300.0f; + m_segmentIndex = FindPathPoint( aheadRange, &m_goal, &m_behindIndex ); + if (m_segmentIndex >= m_path->GetSegmentCount()) + m_segmentIndex = m_path->GetSegmentCount()-1; + + + bool isApproachingJumpArea = false; + + // + // Crouching + // + if (!m_improv->IsUsingLadder()) + { + // because hostage crouching is not really supported by the engine, + // if we are standing in a crouch area, we must crouch to avoid collisions + if (m_improv->GetLastKnownArea() && + m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_CROUCH && + !(m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_JUMP)) + { + m_improv->Crouch(); + } + + // if we are approaching a crouch area, crouch + // if there are no crouch areas coming up, stand + const float crouchRange = 50.0f; + bool didCrouch = false; + for( int i=m_segmentIndex; i<m_path->GetSegmentCount(); ++i ) + { + const CNavArea *to = (*m_path)[i]->area; + + // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump + if (to->GetAttributes() & NAV_MESH_JUMP) + { + isApproachingJumpArea = true; + break; + } + + Vector close; + to->GetClosestPointOnArea( m_improv->GetCentroid(), &close ); + + if ((close - m_improv->GetFeet()).AsVector2D().IsLengthGreaterThan( crouchRange )) + break; + + if (to->GetAttributes() & NAV_MESH_CROUCH) + { + m_improv->Crouch(); + didCrouch = true; + break; + } + + } + + if (!didCrouch && !m_improv->IsJumping()) + { + // no crouch areas coming up + m_improv->StandUp(); + } + + } // end crouching logic + + + if (m_isDebug) + { + m_path->Draw(); + UTIL_DrawBeamPoints( m_improv->GetCentroid(), m_goal + Vector( 0, 0, StepHeight ), 1, 255, 0, 255 ); + UTIL_DrawBeamPoints( m_goal + Vector( 0, 0, StepHeight ), m_improv->GetCentroid(), 1, 255, 0, 255 ); + } + + // check if improv becomes stuck + m_stuckMonitor.Update( m_improv ); + + + // if improv has been stuck for too long, give up + const float giveUpTime = 2.0f; + if (m_stuckMonitor.GetDuration() > giveUpTime) + { + m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_STUCK ); + m_path->Invalidate(); + return; + } + + + // if our goal is high above us, we must have fallen + if (m_goal.z - m_improv->GetFeet().z > JumpCrouchHeight) + { + const float closeRange = 75.0f; + Vector2D to( m_improv->GetFeet().x - m_goal.x, m_improv->GetFeet().y - m_goal.y ); + if (to.IsLengthLessThan( closeRange )) + { + // we can't reach the goal position + // check if we can reach the next node, in case this was a "jump down" situation + const CNavPath::PathSegment *nextNode = (*m_path)[ m_behindIndex+1 ]; + if (m_behindIndex >=0 && nextNode) + { + if (nextNode->pos.z - m_improv->GetFeet().z > JumpCrouchHeight) + { + // the next node is too high, too - we really did fall of the path + m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF ); + m_path->Invalidate(); + return; + } + } + else + { + // fell trying to get to the last node in the path + m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF ); + m_path->Invalidate(); + return; + } + } + } + + + // avoid small obstacles + if (avoidObstacles && !isApproachingJumpArea && !m_improv->IsJumping() && m_segmentIndex < m_path->GetSegmentCount()-1) + { + FeelerReflexAdjustment( &m_goal ); + + // currently, this is only used for hostages, and their collision physics stinks + // do more feeler checks to avoid short obstacles + /* + const float inc = 0.25f; + for( float t = 0.5f; t < 1.0f; t += inc ) + { + FeelerReflexAdjustment( &m_goal, t * StepHeight ); + } + */ + + } + + // move improv along path + m_improv->TrackPath( m_goal, deltaT ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest point to our current position on our current path + * If "local" is true, only check the portion of the path surrounding m_pathIndex. + */ +int CNavPathFollower::FindOurPositionOnPath( Vector *close, bool local ) const +{ + if (!m_path->IsValid()) + return -1; + + Vector along, toFeet; + Vector feet = m_improv->GetFeet(); + Vector eyes = m_improv->GetEyes(); + Vector pos; + const Vector *from, *to; + float length; + float closeLength; + float closeDistSq = 9999999999.9; + int closeIndex = -1; + float distSq; + + int start, end; + + if (local) + { + start = m_segmentIndex - 3; + if (start < 1) + start = 1; + + end = m_segmentIndex + 3; + if (end > m_path->GetSegmentCount()) + end = m_path->GetSegmentCount(); + } + else + { + start = 1; + end = m_path->GetSegmentCount(); + } + + for( int i=start; i<end; ++i ) + { + from = &(*m_path)[i-1]->pos; + to = &(*m_path)[i]->pos; + + // compute ray along this path segment + along = *to - *from; + + // make it a unit vector along the path + length = along.NormalizeInPlace(); + + // compute vector from start of segment to our point + toFeet = feet - *from; + + // find distance of closest point on ray + closeLength = DotProduct( toFeet, along ); + + // constrain point to be on path segment + if (closeLength <= 0.0f) + pos = *from; + else if (closeLength >= length) + pos = *to; + else + pos = *from + closeLength * along; + + distSq = (pos - feet).LengthSqr(); + + // keep the closest point so far + if (distSq < closeDistSq) + { + // don't use points we cant see + Vector probe = pos + Vector( 0, 0, HalfHumanHeight ); + if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES )) + continue; + + // don't use points we cant reach + //if (!IsStraightLinePathWalkable( &pos )) + // continue; + + closeDistSq = distSq; + if (close) + *close = pos; + closeIndex = i-1; + } + } + + return closeIndex; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute a point a fixed distance ahead along our path. + * Returns path index just after point. + */ +int CNavPathFollower::FindPathPoint( float aheadRange, Vector *point, int *prevIndex ) +{ + // find path index just past aheadRange + int afterIndex; + + // finds the closest point on local area of path, and returns the path index just prior to it + Vector close; + int startIndex = FindOurPositionOnPath( &close, true ); + + if (prevIndex) + *prevIndex = startIndex; + + if (startIndex <= 0) + { + // went off the end of the path + // or next point in path is unwalkable (ie: jump-down) + // keep same point + return m_segmentIndex; + } + + // if we are crouching, just follow the path exactly + if (m_improv->IsCrouching()) + { + // we want to move to the immediately next point along the path from where we are now + int index = startIndex+1; + if (index >= m_path->GetSegmentCount()) + index = m_path->GetSegmentCount()-1; + + *point = (*m_path)[ index ]->pos; + + // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling + // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc + const float closeEpsilon = 20.0f; // 10 + while ((*point - close).AsVector2D().IsLengthLessThan( closeEpsilon )) + { + ++index; + + if (index >= m_path->GetSegmentCount()) + { + index = m_path->GetSegmentCount()-1; + break; + } + + *point = (*m_path)[ index ]->pos; + } + + return index; + } + + // make sure we use a node a minimum distance ahead of us, to avoid wiggling + while (startIndex < m_path->GetSegmentCount()-1) + { + Vector pos = (*m_path)[ startIndex+1 ]->pos; + + // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc + const float closeEpsilon = 20.0f; + if ((pos - close).AsVector2D().IsLengthLessThan( closeEpsilon )) + { + ++startIndex; + } + else + { + break; + } + } + + // if we hit a ladder or jump area, must stop (dont use ladder behind us) + if (startIndex > m_segmentIndex && startIndex < m_path->GetSegmentCount() && + ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP)) + { + *point = (*m_path)[ startIndex ]->pos; + return startIndex; + } + + // we need the point just *ahead* of us + ++startIndex; + if (startIndex >= m_path->GetSegmentCount()) + startIndex = m_path->GetSegmentCount()-1; + + // if we hit a ladder or jump area, must stop + if (startIndex < m_path->GetSegmentCount() && + ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP)) + { + *point = (*m_path)[ startIndex ]->pos; + return startIndex; + } + + // note direction of path segment we are standing on + Vector initDir = (*m_path)[ startIndex ]->pos - (*m_path)[ startIndex-1 ]->pos; + initDir.NormalizeInPlace(); + + Vector feet = m_improv->GetFeet(); + Vector eyes = m_improv->GetEyes(); + float rangeSoFar = 0; + + // this flag is true if our ahead point is visible + bool visible = true; + + Vector prevDir = initDir; + + // step along the path until we pass aheadRange + bool isCorner = false; + int i; + for( i=startIndex; i<m_path->GetSegmentCount(); ++i ) + { + Vector pos = (*m_path)[i]->pos; + Vector to = pos - (*m_path)[i-1]->pos; + Vector dir = to; + dir.NormalizeInPlace(); + + // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc) + if (DotProduct( dir, initDir ) < 0.0f) // -0.25f + { + --i; + break; + } + + // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc + if (DotProduct( dir, prevDir ) < 0.5f) + { + isCorner = true; + --i; + break; + } + prevDir = dir; + + // don't use points we cant see + Vector probe = pos + Vector( 0, 0, HalfHumanHeight ); + if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES )) + { + // presumably, the previous point is visible, so we will interpolate + visible = false; + break; + } + + // if we encounter a ladder or jump area, we must stop + if (i < m_path->GetSegmentCount() && + ((*m_path)[ i ]->ladder || (*m_path)[ i ]->area->GetAttributes() & NAV_MESH_JUMP)) + break; + + // Check straight-line path from our current position to this position + // Test for un-jumpable height change, or unrecoverable fall + //if (!IsStraightLinePathWalkable( &pos )) + //{ + // --i; + // break; + //} + + Vector along = (i == startIndex) ? (pos - feet) : (pos - (*m_path)[i-1]->pos); + rangeSoFar += along.Length2D(); + + // stop if we have gone farther than aheadRange + if (rangeSoFar >= aheadRange) + break; + } + + if (i < startIndex) + afterIndex = startIndex; + else if (i < m_path->GetSegmentCount()) + afterIndex = i; + else + afterIndex = m_path->GetSegmentCount()-1; + + + // compute point on the path at aheadRange + if (afterIndex == 0) + { + *point = (*m_path)[0]->pos; + } + else + { + // interpolate point along path segment + const Vector *afterPoint = &(*m_path)[ afterIndex ]->pos; + const Vector *beforePoint = &(*m_path)[ afterIndex-1 ]->pos; + + Vector to = *afterPoint - *beforePoint; + float length = to.Length2D(); + + float t = 1.0f - ((rangeSoFar - aheadRange) / length); + + if (t < 0.0f) + t = 0.0f; + else if (t > 1.0f) + t = 1.0f; + + *point = *beforePoint + t * to; + + // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is + if (!visible) + { + const float sightStepSize = 25.0f; + float dt = sightStepSize / length; + + Vector probe = *point + Vector( 0, 0, HalfHumanHeight ); + while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) ) + { + t -= dt; + *point = *beforePoint + t * to; + } + + if (t <= 0.0f) + *point = *beforePoint; + } + } + + // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle + if (!isCorner) + { + const float epsilon = 50.0f; + Vector2D toPoint; + Vector2D centroid( m_improv->GetCentroid().x, m_improv->GetCentroid().y ); + + toPoint.x = point->x - centroid.x; + toPoint.y = point->y - centroid.y; + + if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon )) + { + int i; + for( i=startIndex; i<m_path->GetSegmentCount(); ++i ) + { + toPoint.x = (*m_path)[i]->pos.x - centroid.x; + toPoint.y = (*m_path)[i]->pos.y - centroid.y; + if ((*m_path)[i]->ladder || (*m_path)[i]->area->GetAttributes() & NAV_MESH_JUMP || toPoint.IsLengthGreaterThan( epsilon )) + { + *point = (*m_path)[i]->pos; + startIndex = i; + break; + } + } + + if (i == m_path->GetSegmentCount()) + { + *point = m_path->GetEndpoint(); + startIndex = m_path->GetSegmentCount()-1; + } + } + } + + // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it + if (startIndex < m_path->GetSegmentCount()) + return startIndex; + + return m_path->GetSegmentCount()-1; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Do reflex avoidance movements if our "feelers" are touched + * @todo Parameterize feeler spacing + */ +void CNavPathFollower::FeelerReflexAdjustment( Vector *goalPosition, float height ) +{ + // if we are in a "precise" area, do not do feeler adjustments + if (m_improv->GetLastKnownArea() && m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_PRECISE) + return; + + // use the direction towards the goal + Vector dir = *goalPosition - m_improv->GetFeet(); + dir.z = 0.0f; + dir.NormalizeInPlace(); + + Vector lat( -dir.y, dir.x, 0.0f ); + + const float feelerOffset = (m_improv->IsCrouching()) ? 15.0f : 20.0f; // 15, 20 + const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747) + const float feelerLengthWalk = 30.0f; + + const float feelerHeight = (height > 0.0f) ? height : StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it + + float feelerLength = (m_improv->IsRunning()) ? feelerLengthRun : feelerLengthWalk; + + feelerLength = (m_improv->IsCrouching()) ? 20.0f : feelerLength; + + // + // Feelers must follow floor slope + // + float ground; + Vector normal; + if (m_improv->GetSimpleGroundHeightWithFloor( m_improv->GetEyes(), &ground, &normal ) == false) + return; + + // get forward vector along floor + dir = CrossProduct( lat, normal ); + + // correct the sideways vector + lat = CrossProduct( dir, normal ); + + + Vector feet = m_improv->GetFeet(); + feet.z += feelerHeight; + + Vector from = feet + feelerOffset * lat; + Vector to = from + feelerLength * dir; + + bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); + + // draw debug beams + if (m_isDebug) + { + if (leftClear) + UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 ); + else + UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 ); + } + + from = feet - feelerOffset * lat; + to = from + feelerLength * dir; + + bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); + + // draw debug beams + if (m_isDebug) + { + if (rightClear) + UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 ); + else + UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 ); + } + + + + const float avoidRange = (m_improv->IsCrouching()) ? 150.0f : 300.0f; + + if (!rightClear) + { + if (leftClear) + { + // right hit, left clear - veer left + *goalPosition = *goalPosition + avoidRange * lat; + //*goalPosition = m_improv->GetFeet() + avoidRange * lat; + + //m_improv->StrafeLeft(); + } + } + else if (!leftClear) + { + // right clear, left hit - veer right + *goalPosition = *goalPosition - avoidRange * lat; + //*goalPosition = m_improv->GetFeet() - avoidRange * lat; + + //m_improv->StrafeRight(); + } + +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Reset the stuck-checker. + */ +CStuckMonitor::CStuckMonitor( void ) +{ + m_isStuck = false; + m_avgVelIndex = 0; + m_avgVelCount = 0; +} + +/** + * Reset the stuck-checker. + */ +void CStuckMonitor::Reset( void ) +{ + m_isStuck = false; + m_avgVelIndex = 0; + m_avgVelCount = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Test if the improv has become stuck + */ +void CStuckMonitor::Update( CImprovLocomotor *improv ) +{ + if (m_isStuck) + { + // improv is stuck - see if it has moved far enough to be considered unstuck + const float unstuckRange = 75.0f; + if ((improv->GetCentroid() - m_stuckSpot).IsLengthGreaterThan( unstuckRange )) + { + // no longer stuck + Reset(); + //PrintIfWatched( "UN-STUCK\n" ); + } + } + else + { + // check if improv has become stuck + + // compute average velocity over a short period (for stuck check) + Vector vel = improv->GetCentroid() - m_lastCentroid; + + // if we are jumping, ignore Z + //if (improv->IsJumping()) + // vel.z = 0.0f; + + // ignore Z unless we are on a ladder (which is only Z) + if (!improv->IsUsingLadder()) + vel.z = 0.0f; + + // cannot be Length2D, or will break ladder movement (they are only Z) + float moveDist = vel.Length(); + + float deltaT = gpGlobals->curtime - m_lastTime; + if (deltaT <= 0.0f) + return; + + m_lastTime = gpGlobals->curtime; + + // compute current velocity + m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT; + + if (m_avgVelIndex == MAX_VEL_SAMPLES) + m_avgVelIndex = 0; + + if (m_avgVelCount < MAX_VEL_SAMPLES) + { + ++m_avgVelCount; + } + else + { + // we have enough samples to know if we're stuck + + float avgVel = 0.0f; + for( int t=0; t<m_avgVelCount; ++t ) + avgVel += m_avgVel[t]; + + avgVel /= m_avgVelCount; + + // cannot make this velocity too high, or actors will get "stuck" when going down ladders + float stuckVel = (improv->IsUsingLadder()) ? 10.0f : 20.0f; + + if (avgVel < stuckVel) + { + // note when and where we initially become stuck + m_stuckTimer.Start(); + m_stuckSpot = improv->GetCentroid(); + m_isStuck = true; + } + } + } + + // always need to track this + m_lastCentroid = improv->GetCentroid(); +} + diff --git a/game/shared/cstrike/bot/nav_path.h b/game/shared/cstrike/bot/nav_path.h new file mode 100644 index 0000000..2542b84 --- /dev/null +++ b/game/shared/cstrike/bot/nav_path.h @@ -0,0 +1,246 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// nav_path.h +// Navigation Path encapsulation +// Author: Michael S. Booth ([email protected]), November 2003 + +#ifndef _NAV_PATH_H_ +#define _NAV_PATH_H_ + +#include "cs_nav_area.h" +#include "bot_util.h" + +class CImprovLocomotor; + +//-------------------------------------------------------------------------------------------------------- +/** + * The CNavPath class encapsulates a path through space + */ +class CNavPath +{ +public: + CNavPath( void ) + { + m_segmentCount = 0; + } + + struct PathSegment + { + CNavArea *area; ///< the area along the path + NavTraverseType how; ///< how to enter this area from the previous one + Vector pos; ///< our movement goal position at this point in the path + const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it + }; + + const PathSegment * operator[] ( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; } + const PathSegment *GetSegment( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; } + int GetSegmentCount( void ) const { return m_segmentCount; } + const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; } + bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path + + float GetLength( void ) const; ///< return length of path from start to finish + bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end + + /// return the node index closest to the given distance along the path without going over - returns (-1) if error + int GetSegmentIndexAlongPath( float distAlong ) const; + + bool IsValid( void ) const { return (m_segmentCount > 0); } + void Invalidate( void ) { m_segmentCount = 0; } + + void Draw( const Vector &color = Vector( 1.0f, 0.3f, 0 ) ); ///< draw the path for debugging + + /// compute closest point on path to given point + bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const; + + void Optimize( void ); + + /** + * Compute shortest path from 'start' to 'goal' via A* algorithm. + * If returns true, path was build to the goal position. + * If returns false, path may either be invalid (use IsValid() to check), or valid but + * doesn't reach all the way to the goal. + */ + template< typename CostFunctor > + bool Compute( const Vector &start, const Vector &goal, CostFunctor &costFunc ) + { + Invalidate(); + + CNavArea *startArea = TheNavMesh->GetNearestNavArea( start + Vector( 0.0f, 0.0f, 1.0f ) ); + if (startArea == NULL) + { + return false; + } + + CNavArea *goalArea = TheNavMesh->GetNavArea( goal ); + + // if we are already in the goal area, build trivial path + if (startArea == goalArea) + { + BuildTrivialPath( start, goal ); + return true; + } + + // make sure path end position is on the ground + Vector pathEndPosition = goal; + if (goalArea) + { + pathEndPosition.z = goalArea->GetZ( pathEndPosition ); + } + else + { + TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z ); + } + + // + // Compute shortest path to goal + // + CNavArea *closestArea; + bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea ); + + // + // Build path by following parent links + // + + // get count + int count = 0; + CNavArea *area; + for( area = closestArea; area; area = area->GetParent() ) + { + ++count; + } + + // save room for endpoint + if (count > MAX_PATH_SEGMENTS-1) + { + count = MAX_PATH_SEGMENTS-1; + } + + if (count == 0) + { + return false; + } + + if (count == 1) + { + BuildTrivialPath( start, goal ); + return true; + } + + // build path + m_segmentCount = count; + for( area = closestArea; count && area; area = area->GetParent() ) + { + --count; + m_path[ count ].area = area; + m_path[ count ].how = area->GetParentHow(); + } + + // compute path positions + if (ComputePathPositions() == false) + { + //PrintIfWatched( "CNavPath::Compute: Error building path\n" ); + Invalidate(); + return false; + } + + // append path end position + m_path[ m_segmentCount ].area = closestArea; + m_path[ m_segmentCount ].pos = pathEndPosition; + m_path[ m_segmentCount ].ladder = NULL; + m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES; + ++m_segmentCount; + + return pathResult; + } + +private: + enum { MAX_PATH_SEGMENTS = 256 }; + PathSegment m_path[ MAX_PATH_SEGMENTS ]; + int m_segmentCount; + + bool ComputePathPositions( void ); ///< determine actual path positions + bool BuildTrivialPath( const Vector &start, const Vector &goal ); ///< utility function for when start and goal are in the same area + + int FindNextOccludedNode( int anchor ); ///< used by Optimize() +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * Monitor improv movement and determine if it becomes stuck + */ +class CStuckMonitor +{ +public: + CStuckMonitor( void ); + + void Reset( void ); + void Update( CImprovLocomotor *improv ); + bool IsStuck( void ) const { return m_isStuck; } + + float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; } + +private: + bool m_isStuck; ///< if true, we are stuck + Vector m_stuckSpot; ///< the location where we became stuck + IntervalTimer m_stuckTimer; ///< how long we have been stuck + + enum { MAX_VEL_SAMPLES = 5 }; + float m_avgVel[ MAX_VEL_SAMPLES ]; + int m_avgVelIndex; + int m_avgVelCount; + Vector m_lastCentroid; + float m_lastTime; +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * The CNavPathFollower class implements path following behavior + */ +class CNavPathFollower +{ +public: + CNavPathFollower( void ); + + void SetImprov( CImprovLocomotor *improv ) { m_improv = improv; } + void SetPath( CNavPath *path ) { m_path = path; } + + void Reset( void ); + + #define DONT_AVOID_OBSTACLES false + void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path + void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off + + bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck + void ResetStuck( void ) { m_stuckMonitor.Reset(); } + float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck + + void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched + +private: + CImprovLocomotor *m_improv; ///< who is doing the path following + + CNavPath *m_path; ///< the path being followed + + int m_segmentIndex; ///< the point on the path the improv is moving towards + int m_behindIndex; ///< index of the node on the path just behind us + Vector m_goal; ///< last computed follow goal + + bool m_isLadderStarted; + + bool m_isDebug; + + int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path + int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path. + + CStuckMonitor m_stuckMonitor; +}; + + + +#endif // _NAV_PATH_H_ + diff --git a/game/shared/cstrike/bot/shared_util.cpp b/game/shared/cstrike/bot/shared_util.cpp new file mode 100644 index 0000000..4d66845 --- /dev/null +++ b/game/shared/cstrike/bot/shared_util.cpp @@ -0,0 +1,207 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: dll-agnostic routines (no dll dependencies here) +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Matthew D. Campbell ([email protected]), 2003 + +#include "cbase.h" + +#include <ctype.h> +#include "shared_util.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static char s_shared_token[ 1500 ]; +static char s_shared_quote = '\"'; + +//-------------------------------------------------------------------------------------------------------------- +char * SharedVarArgs(const char *format, ...) +{ + va_list argptr; + const int BufLen = 1024; + const int NumBuffers = 4; + static char string[NumBuffers][BufLen]; + static int curstring = 0; + + curstring = ( curstring + 1 ) % NumBuffers; + + va_start (argptr, format); + V_vsprintf_safe( string[curstring], format, argptr ); + va_end (argptr); + + return string[curstring]; +} + +//-------------------------------------------------------------------------------------------------------------- +char * BufPrintf(char *buf, int& len, const char *fmt, ...) +{ + if (len <= 0) + return NULL; + + va_list argptr; + + va_start(argptr, fmt); + _vsnprintf(buf, len, fmt, argptr); + buf[ len - 1 ] = 0; + va_end(argptr); + + len -= strlen(buf); + return buf + strlen(buf); +} + +//-------------------------------------------------------------------------------------------------------------- +wchar_t * BufWPrintf(wchar_t *buf, int& len, const wchar_t *fmt, ...) +{ + if (len <= 0) + return NULL; + + va_list argptr; + + va_start(argptr, fmt); +#ifdef WIN32 + _vsnwprintf(buf, len, fmt, argptr); +#else + vswprintf( buf, len, fmt, argptr ); +#endif + buf[ len - 1 ] = 0; + va_end(argptr); + + len -= wcslen(buf); + return buf + wcslen(buf); +} + +//-------------------------------------------------------------------------------------------------------------- +const wchar_t * NumAsWString( int val ) +{ + const int BufLen = 16; + static wchar_t buf[BufLen]; + int len = BufLen; + BufWPrintf( buf, len, L"%d", val ); + return buf; +} + +//-------------------------------------------------------------------------------------------------------------- +const char * NumAsString( int val ) +{ + const int BufLen = 16; + static char buf[BufLen]; + int len = BufLen; + BufPrintf( buf, len, "%d", val ); + return buf; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +char *SharedGetToken( void ) +{ + return s_shared_token; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +void SharedSetQuoteChar( char c ) +{ + s_shared_quote = c; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Parse a token out of a string + */ +const char *SharedParse( const char *data ) +{ + int c; + int len; + + len = 0; + s_shared_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == s_shared_quote) + { + data++; + while (1) + { + c = *data++; + if (c==s_shared_quote || !c) + { + s_shared_token[len] = 0; + return data; + } + s_shared_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + s_shared_token[len] = c; + len++; + s_shared_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + s_shared_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + s_shared_token[len] = 0; + return data; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns true if additional data is waiting to be processed on this line + */ +bool SharedTokenWaiting( const char *buffer ) +{ + const char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; +} diff --git a/game/shared/cstrike/bot/shared_util.h b/game/shared/cstrike/bot/shared_util.h new file mode 100644 index 0000000..664dcd7 --- /dev/null +++ b/game/shared/cstrike/bot/shared_util.h @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: dll-agnostic routines (no dll dependencies here) +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Matthew D. Campbell ([email protected]), 2003 + +#ifndef SHARED_UTIL_H +#define SHARED_UTIL_H + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +char *SharedGetToken( void ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Sets the character used to delimit quoted strings. Default is '\"'. Be sure to set it back when done. + */ +void SharedSetQuoteChar( char c ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Parse a token out of a string + */ +const char *SharedParse( const char *data ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns true if additional data is waiting to be processed on this line + */ +bool SharedTokenWaiting( const char *buffer ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Simple utility function to allocate memory and duplicate a string + */ +inline char *CloneString( const char *str ) +{ + char *cloneStr = new char [ strlen(str)+1 ]; + strcpy( cloneStr, str ); + return cloneStr; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * snprintf-alike that allows multiple prints into a buffer + */ +char * BufPrintf(char *buf, int& len, PRINTF_FORMAT_STRING const char *fmt, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * wide char version of BufPrintf + */ +wchar_t * BufWPrintf(wchar_t *buf, int& len, PRINTF_FORMAT_STRING const wchar_t *fmt, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that prints an int into a static wchar_t* + */ +const wchar_t * NumAsWString( int val ); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that prints an int into a static char* + */ +const char * NumAsString( int val ); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that composes a string into a static char* + */ +char * SharedVarArgs(PRINTF_FORMAT_STRING const char *format, ...); + +#include "tier0/memdbgoff.h" + +#endif // SHARED_UTIL_H diff --git a/game/shared/cstrike/cs_achievement_constants.h b/game/shared/cstrike/cs_achievement_constants.h new file mode 100644 index 0000000..35d21a3 --- /dev/null +++ b/game/shared/cstrike/cs_achievement_constants.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//------------------------------------------------------------- +// File: cs_achievement_constants.h +// Desc: Declare contants used by achievements (mostly) in one location for simpler tweaking +// Author: Peter Freese <[email protected]> +// Date: 2009/03/11 +// Copyright: � 2009 Hidden Path Entertainment +//------------------------------------------------------------- + +#ifndef CS_ACHIEVEMENT_CONSTANTS_H +#define CS_ACHIEVEMENT_CONSTANTS_H +#ifdef _WIN32 +#pragma once +#endif + +namespace AchievementConsts +{ + const int DefaultMinOpponentsForAchievement = 5; + const int KillingSpree_Kills = 5; + const float KillingSpree_WindowTime = 15.0f; + const float KillingSpreeEnder_TimeWindow = 5.0f; + const int KillEnemyTeam_MinKills = 5; + const int LastPlayerAlive_MinPlayersOnTeam = 5; + const int KillsWithMultipleGuns_MinWeapons = 5; + const float BombDefuseCloseCall_MaxTimeRemaining = 1.0f; + const int KillLowDamage_MaxHealthLeft = 5; + const int DamageNoKill_MaxHealthLeftOnKill = 5; + const float BombDefuseNeededKit_MaxTime = 5.0f; + const float FastBombPlant_Time = 25.0f; + const int KillEnemiesWhileBlind_Kills = 1; + const int KillEnemiesWhileBlindHard_Kills = 2; + const int SurviveGrenade_MinDamage = 80; + const int KillWhenAtLowHealth_MaxHealth = 1; + const int GrenadeMultiKill_MinKills = 3; + const int BombMultiKill_MinKills = 5; + const float FastRoundWin_Time = 30.0f; + const int UnstoppableForce_Kills = 10; + const int BreakPropsInRound_Props = 15; + const int HeadshotsInRound_Kills = 5; + const int BreakWindowsInOfficeRound_Windows = 14; + const float FastHostageRescue_Time = 90.0f; + const int SurviveManyAttacks_NumberDamagingPlayers = 5; + const float KillInAir_MinimumHeight = 100.0f; //100-120 is probably best. Also used for killing while in the air + const float KillBombPickup_MaxTime = 3.0f; + const int WinRoundsWithoutBuying_Rounds = 10; + const int ConcurrentDominations_MinDominations = 3; + const int ExtendedDomination_AdditionalKills = 4; + const int SameUniform_MinPlayers = 5; + const int FriendsSameUniform_MinPlayers = 4; + const float KillEnemyNearBomb_MaxDistance = 480.0f; + const int GrenadeDamage_MinDamage = 200; +} + +#endif // CS_ACHIEVEMENT_CONSTANTS_H diff --git a/game/shared/cstrike/cs_achievementdefs.h b/game/shared/cstrike/cs_achievementdefs.h new file mode 100644 index 0000000..77615de --- /dev/null +++ b/game/shared/cstrike/cs_achievementdefs.h @@ -0,0 +1,210 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Shared CS definitions. +// +//=============================================================================// + +#ifndef CS_ACHIEVEMENTDEFS_H +#define CS_ACHIEVEMENTDEFS_H +#ifdef _WIN32 +#pragma once +#endif + + + +//============================================================================= +// Achievement ID Definitions +//============================================================================= + + +typedef enum +{ + CSInvalidAchievement = -1, + + // Bomb-related Achievements + CSBombAchievementsStart = 1000, // First bomb-related achievement + + CSWinBombPlant, + CSWinBombDefuse, + CSDefuseAndNeededKit, + CSBombDefuseCloseCall, + CSKilledDefuser, + CSPlantBombWithin25Seconds, + CSKillBombPickup, + CSBombMultikill, + CSGooseChase, + CSWinBombPlantAfterRecovery, + CSDefuseDefense, + CSPlantBombsLow, + CSDefuseBombsLow, + + CSBombAchievementsEnd, // Must be after last bomb-related achievement + + + // Hostage-related Achievements + CSHostageAchievementsStart = 2000, // First hostage-related achievement + + CSRescueAllHostagesInARound, + CSKilledRescuer, + CSFastHostageRescue, + CSRescueHostagesLow, + CSRescueHostagesMid, + + CSHostageAchievmentEnd, // Must be after last hostage-related achievement + + // General Kill Achievements + CSKillAchievementsStart = 3000, // First kill-related achievement + + CSEnemyKillsLow, + CSEnemyKillsMed, + CSEnemyKillsHigh, + CSSurvivedHeadshotDueToHelmet, + CSKillEnemyReloading, + CSKillingSpree, + CSKillsWithMultipleGuns, + CSHeadshots, + CSAvengeFriend, + CSSurviveGrenade, + CSDominationsLow, + CSDominationsHigh, + CSRevengesLow, + CSRevengesHigh, + CSDominationOverkillsLow, + CSDominationOverkillsHigh, + CSDominationOverkillsMatch, + CSExtendedDomination, + CSConcurrentDominations, + CSKillEnemyBlinded, + CSKillEnemiesWhileBlind, + CSKillEnemiesWhileBlindHard, + CSKillsEnemyWeapon, + CSKillWithEveryWeapon, + CSWinKnifeFightsLow, + CSWinKnifeFightsHigh, + CSKilledDefuserWithGrenade, + CSKillSniperWithSniper, + CSKillSniperWithKnife, + CSHipShot, + CSKillSnipers, + CSKillWhenAtLowHealth, + CSPistolRoundKnifeKill, + CSWinDualDuel, + CSGrenadeMultikill, + CSKillWhileInAir, + CSKillEnemyInAir, + CSKillerAndEnemyInAir, + CSKillEnemyWithFormerGun, + CSKillTwoWithOneShot, + CSSnipeTwoFromSameSpot, + + CSKillAchievementEnd, // Must be after last kill-related achievement + + // Weapon-related Achievements + CSWeaponAchievementsStart = 4000, // First weapon-related achievement + + CSEnemyKillsDeagle, + CSEnemyKillsUSP, + CSEnemyKillsGlock, + CSEnemyKillsP228, + CSEnemyKillsElite, + CSEnemyKillsFiveSeven, + CSEnemyKillsAWP, + CSEnemyKillsAK47, + CSEnemyKillsM4A1, + CSEnemyKillsAUG, + CSEnemyKillsSG552, + CSEnemyKillsSG550, + CSEnemyKillsGALIL, + CSEnemyKillsFAMAS, + CSEnemyKillsScout, + CSEnemyKillsG3SG1, + CSEnemyKillsP90, + CSEnemyKillsMP5NAVY, + CSEnemyKillsTMP, + CSEnemyKillsMAC10, + CSEnemyKillsUMP45, + CSEnemyKillsM3, + CSEnemyKillsXM1014, + CSEnemyKillsM249, + CSEnemyKillsKnife, + CSEnemyKillsHEGrenade, + CSMetaPistol, + CSMetaRifle, + CSMetaSMG, + CSMetaShotgun, + CSMetaWeaponMaster, + + CSWeaponAchievementsEnd, // Must be after last weapon-related achievement + + // General Achievements + CSGeneralAchievementsStart = 5000, // First general achievement + + CSWinRoundsLow, + CSWinRoundsMed, + CSWinRoundsHigh, + CSMoneyEarnedLow, + CSMoneyEarnedMed, + CSMoneyEarnedHigh, + CSGiveDamageLow, + CSGiveDamageMed, + CSGiveDamageHigh, + CSPosthumousGrenadeKill, + CSKillEnemyTeam, + CSLastPlayerAlive, + CSKillEnemyLastBullet, + CSKillingSpreeEnder, + CSDamageNoKill, + CSKillLowDamage, + CSSurviveManyAttacks, + CSLosslessExtermination, + CSFlawlessVictory, + CSDecalSprays, + CSBreakWindows, + CSBreakProps, + CSUnstoppableForce, + CSImmovableObject, + CSHeadshotsInRound, + CSWinPistolRoundsLow, + CSWinPistolRoundsMed, + CSWinPistolRoundsHigh, + CSFastRoundWin, + CSNightvisionDamage, + CSSilentWin, + CSBloodlessVictory, + CSDonateWeapons, + CSWinRoundsWithoutBuying, + CSSameUniform, + CSFriendsSameUniform, + CSCauseFriendlyFireWithFlashbang, + CSWinClanMatch, + CSCollectHolidayGifts, + + CSGeneralAchievementsEnd, // Must be after last general achievement + + CSWinMapAchievementsStart = 6000, + + CSWinMapCS_ASSAULT, + CSWinMapCS_COMPOUND, + CSWinMapCS_HAVANA, + CSWinMapCS_ITALY, + CSWinMapCS_MILITIA, + CSWinMapCS_OFFICE, + CSWinMapDE_AZTEC, + CSWinMapDE_CBBLE, + CSWinMapDE_CHATEAU, + CSWinMapDE_DUST, + CSWinMapDE_DUST2, + CSWinMapDE_INFERNO, + CSWinMapDE_NUKE, + CSWinMapDE_PIRANESI, + CSWinMapDE_PORT, + CSWinMapDE_PRODIGY, + CSWinMapDE_TIDES, + CSWinMapDE_TRAIN, + + CSWinMapAchievementsEnd //Must be after last map-based achievement + +} eCSAchievementType; + + +#endif // CS_ACHIEVEMENTDEFS_H diff --git a/game/shared/cstrike/cs_ammodef.cpp b/game/shared/cstrike/cs_ammodef.cpp new file mode 100644 index 0000000..1b444d1 --- /dev/null +++ b/game/shared/cstrike/cs_ammodef.cpp @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "cs_ammodef.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +void CCSAmmoDef::AddAmmoCost( char const* name, int cost, int buySize ) +{ + int index = Index( name ); + if ( index < 1 || index >= m_nAmmoIndex ) + return; + + m_csAmmo[index].buySize = buySize; + m_csAmmo[index].cost = cost; +} + + +//----------------------------------------------------------------------------- +int CCSAmmoDef::GetBuySize( int index ) const +{ + if ( index < 1 || index >= m_nAmmoIndex ) + return 0; + + return m_csAmmo[index].buySize; +} + + +//----------------------------------------------------------------------------- +int CCSAmmoDef::GetCost( int index ) const +{ + if ( index < 1 || index >= m_nAmmoIndex ) + return 0; + + return m_csAmmo[index].cost; +} + + +//----------------------------------------------------------------------------- +CCSAmmoDef::CCSAmmoDef(void) +{ + memset( m_csAmmo, 0, sizeof( m_csAmmo ) ); +} + + +//----------------------------------------------------------------------------- +CCSAmmoDef::~CCSAmmoDef( void ) +{ +} + +//----------------------------------------------------------------------------- diff --git a/game/shared/cstrike/cs_ammodef.h b/game/shared/cstrike/cs_ammodef.h new file mode 100644 index 0000000..0175bcd --- /dev/null +++ b/game/shared/cstrike/cs_ammodef.h @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds defintion for game ammo types +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef CS_AMMODEF_H +#define CS_AMMODEF_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "ammodef.h" +#include "cs_blackmarket.h" + +class ConVar; + +struct CSAmmoCost +{ + int buySize; + int cost; +}; + +//============================================================================= +// >> CCSAmmoDef +//============================================================================= +class CCSAmmoDef : public CAmmoDef +{ + +public: + + void AddAmmoCost( char const* name, int cost, int buySize ); + + CCSAmmoDef(void); + ~CCSAmmoDef( void ); + + int GetBuySize( int nAmmoIndex ) const; + int GetCost( int nAmmoIndex ) const; + +private: + CSAmmoCost m_csAmmo[MAX_AMMO_TYPES]; +}; + + +// Get the global ammodef object. This is usually implemented in each mod's game rules file somewhere, +// so the mod can setup custom ammo types. +CCSAmmoDef* GetCSAmmoDef(); + + +#endif // CS_AMMODEF_H +
\ No newline at end of file diff --git a/game/shared/cstrike/cs_blackmarket.cpp b/game/shared/cstrike/cs_blackmarket.cpp new file mode 100644 index 0000000..f68e988 --- /dev/null +++ b/game/shared/cstrike/cs_blackmarket.cpp @@ -0,0 +1,147 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "cs_gamerules.h" +#include "cs_blackmarket.h" +#include "weapon_csbase.h" +#include "filesystem.h" +#include <KeyValues.h> +#include "cs_gamestats.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern int g_iRoundCount; + +#ifndef CLIENT_DLL +inline void CBlackMarketElement::NetworkStateChanged() +{ +} + + +inline void CBlackMarketElement::NetworkStateChanged( void *pVar ) +{ +} + +blackmarket_items_t blackmarket_items[] = +{ + { "kevlar", KEVLAR_PRICE }, + { "assaultsuit", ASSAULTSUIT_PRICE }, + { "nightvision", NVG_PRICE }, +}; + + +CUtlVector<CBlackMarketElement> g_BlackMarket_WeaponsBought; + +void TrackAutoBuyPurchases( const char *pWeaponName, CCSPlayer *pBuyer ) +{ + if ( pBuyer->IsInAutoBuy() == false ) + return; + + if ( pBuyer->IsInAutoBuy() == true ) + { + if ( Q_stristr( pWeaponName, "m4a1" ) ) + { + g_iAutoBuyM4A1Purchases++; + } + else if ( Q_stristr( pWeaponName, "ak47" ) ) + { + g_iAutoBuyAK47Purchases++; + } + else if ( Q_stristr( pWeaponName, "famas" ) ) + { + g_iAutoBuyFamasPurchases++; + } + else if ( Q_stristr( pWeaponName, "galil" ) ) + { + g_iAutoBuyGalilPurchases++; + } + else if ( Q_stristr( pWeaponName, "assault" ) ) + { + g_iAutoBuyVestHelmPurchases++; + } + else if ( Q_stristr( pWeaponName, "kevlar" ) ) + { + g_iAutoBuyVestPurchases++; + } + } +} + +void BlackMarketAddWeapon( const char *pWeaponName, CCSPlayer *pBuyer ) +{ + //Ignore bot purchases. + if ( pBuyer && pBuyer->IsBot() ) + return; + + CSWeaponID iWeaponID = AliasToWeaponID( pWeaponName ); + + TrackAutoBuyPurchases( pWeaponName, pBuyer ); + + if ( g_BlackMarket_WeaponsBought.Count() > 0 ) + { + for ( int i = 0; i < g_BlackMarket_WeaponsBought.Count(); i++ ) + { + if ( g_BlackMarket_WeaponsBought[i].m_iWeaponID == iWeaponID ) + { + g_BlackMarket_WeaponsBought[i].m_iTimesBought++; + g_iWeaponPurchases[g_BlackMarket_WeaponsBought[i].m_iWeaponID]++; + return; + } + } + } + + CBlackMarketElement newweapon; + + newweapon.m_iWeaponID = iWeaponID; + newweapon.m_iTimesBought = 1; + newweapon.m_iPrice = 0; + g_iWeaponPurchases[newweapon.m_iWeaponID] = 1; + + g_BlackMarket_WeaponsBought.AddToTail( newweapon ); +} + +int GetPistolCount( void ) +{ + int iNumPistol = 0; + + for ( int j = 1; j < WEAPON_MAX; j++ ) + { + CCSWeaponInfo *pWeaponInfo = GetWeaponInfo( (CSWeaponID)j ); + + if ( pWeaponInfo ) + { + if ( pWeaponInfo->m_WeaponType == WEAPONTYPE_PISTOL ) + { + iNumPistol++; + } + } + } + + return iNumPistol; +} + +int GetRifleCount( void ) +{ + int iNumRifle = 0; + + for ( int j = 1; j < WEAPON_MAX; j++ ) + { + CCSWeaponInfo *pWeaponInfo = GetWeaponInfo( (CSWeaponID)j ); + + if ( pWeaponInfo ) + { + if ( pWeaponInfo->m_WeaponType != WEAPONTYPE_PISTOL ) + { + iNumRifle++; + } + } + } + + return iNumRifle + ARRAYSIZE( blackmarket_items ); +} + +#endif
\ No newline at end of file diff --git a/game/shared/cstrike/cs_blackmarket.h b/game/shared/cstrike/cs_blackmarket.h new file mode 100644 index 0000000..e31b888 --- /dev/null +++ b/game/shared/cstrike/cs_blackmarket.h @@ -0,0 +1,89 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds defintion for game ammo types +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef CS_BLACKMARKET_H +#define CS_BLACKMARKET_H + +#include "cs_weapon_parse.h" + +#ifdef CLIENT_DLL +#include "c_cs_player.h" +#else +#include "cs_player.h" +#endif + + +#ifdef _WIN32 +#pragma once +#endif + +struct blackmarket_items_t +{ + const char *pClassname; + int iDefaultPrice; +}; + +extern blackmarket_items_t blackmarket_items[]; + +void BlackMarketAddWeapon( const char *pWeaponName, CCSPlayer *pBuyer ); + + +enum +{ + KEVLAR_PRICE = 650, + HELMET_PRICE = 350, + ASSAULTSUIT_PRICE = 1000, + DEFUSEKIT_PRICE = 200, + NVG_PRICE = 1250, + SHIELD_PRICE = 2200 +}; + +#ifndef CLIENT_DLL +class CBlackMarketElement +{ +public: + + DECLARE_CLASS_NOBASE( CBlackMarketElement ); + + // For CNetworkVars. + void NetworkStateChanged(); + void NetworkStateChanged( void *pVar ); + + CBlackMarketElement() + { + m_iPrice = 0; + m_iTimesBought = 0; + m_iWeaponID = 0; + } + + CNetworkVar( int, m_iPrice ); + CNetworkVar( int, m_iWeaponID ); + + int m_iTimesBought; +}; + + +#else + +class C_BlackMarketElement +{ +public: + + // This allows the datatables to access private members. + ALLOW_DATATABLES_PRIVATE_ACCESS(); + + int m_iWeaponID; + int m_iPrice; +}; + +#define CBlackMarketElement C_BlackMarketElement +#endif + +#endif // CS_BLACKMARKET_H +
\ No newline at end of file diff --git a/game/shared/cstrike/cs_gamemovement.cpp b/game/shared/cstrike/cs_gamemovement.cpp new file mode 100644 index 0000000..fa5c52a --- /dev/null +++ b/game/shared/cstrike/cs_gamemovement.cpp @@ -0,0 +1,1144 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "gamemovement.h" +#include "cs_gamerules.h" +#include "in_buttons.h" +#include "movevars_shared.h" +#include "weapon_csbase.h" + +#ifdef CLIENT_DLL + #include "c_cs_player.h" +#else + #include "cs_player.h" + #include "KeyValues.h" +#endif + +#define STAMINA_MAX 100.0 +#define STAMINA_COST_JUMP 25.0 +#define STAMINA_COST_FALL 20.0 +#define STAMINA_RECOVER_RATE 19.0 + +extern bool g_bMovementOptimizations; + +ConVar sv_timebetweenducks( "sv_timebetweenducks", "0", FCVAR_REPLICATED, "Minimum time before recognizing consecutive duck key", true, 0.0, true, 2.0 ); +ConVar sv_enableboost( "sv_enableboost", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Allow boost exploits"); + + +class CCSGameMovement : public CGameMovement +{ +public: + DECLARE_CLASS( CCSGameMovement, CGameMovement ); + + CCSGameMovement(); + + virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove ); + virtual bool CanAccelerate(); + virtual bool CheckJumpButton( void ); + virtual void PreventBunnyJumping( void ); + virtual void ReduceTimers( void ); + virtual void WalkMove( void ); + virtual void AirMove( void ); + virtual bool LadderMove( void ); + virtual void DecayPunchAngle( void ); + virtual void CheckParameters( void ); + + // allow overridden versions to respond to jumping + virtual void OnJump( float fImpulse ); + virtual void OnLand( float fVelocity ); + + // Ducking + virtual void Duck( void ); + virtual void FinishUnDuck( void ); + virtual void FinishDuck( void ); + virtual bool CanUnduck(); + virtual void HandleDuckingSpeedCrop(); + + + virtual bool OnLadder( trace_t &trace ); + virtual float LadderDistance( void ) const + { + if ( player->GetMoveType() == MOVETYPE_LADDER ) + return 10.0f; + return 2.0f; + } + + virtual unsigned int LadderMask( void ) const + { + return MASK_PLAYERSOLID & ( ~CONTENTS_PLAYERCLIP ); + } + + virtual float ClimbSpeed( void ) const; + virtual float LadderLateralMultiplier( void ) const; + + virtual void TryTouchGround( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, unsigned int fMask, int collisionGroup, trace_t& pm ); + + +protected: + virtual void PlayerMove(); + + void CheckForLadders( bool wasOnGround ); + virtual unsigned int PlayerSolidMask( bool brushOnly = false ); ///< returns the solid mask for the given player, so bots can have a more-restrictive set + + float m_fTimeLastUnducked; + +public: + CCSPlayer *m_pCSPlayer; +}; + +//----------------------------------------------------------------------------- +// Purpose: used by the TryTouchGround function to exclude non-standables from +// consideration +//----------------------------------------------------------------------------- + +bool CheckForStandable( IHandleEntity *pHandleEntity, int contentsMask ) +{ + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + + if ( !pEntity ) + return false; + + return ( pEntity->IsPlayer() && pEntity->GetGroundEntity() != NULL ) || pEntity->IsStandable(); +} + + +// Expose our interface. +static CCSGameMovement g_GameMovement; +IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement; + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement ); + + +// ---------------------------------------------------------------------------------------- // +// CCSGameMovement. +// ---------------------------------------------------------------------------------------- // + +CCSGameMovement::CCSGameMovement() +{ + m_fTimeLastUnducked = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Allow bots etc to use slightly different solid masks +//----------------------------------------------------------------------------- +unsigned int CCSGameMovement::PlayerSolidMask( bool brushOnly ) +{ + bool isBot = !player || player->IsBot(); + + if ( brushOnly ) + { + if ( isBot ) + { + return MASK_PLAYERSOLID_BRUSHONLY | CONTENTS_MONSTERCLIP; + } + else + { + return MASK_PLAYERSOLID_BRUSHONLY; + } + } + else + { + if ( isBot ) + { + return MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP; + } + else + { + return MASK_PLAYERSOLID; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSGameMovement::CheckParameters( void ) +{ + QAngle v_angle; + + // maintaining auto-duck during jumps + if ( m_pCSPlayer->m_duckUntilOnGround ) + { + if ( !player->GetGroundEntity() ) + { + if ( mv->m_nButtons & IN_DUCK ) + { + m_pCSPlayer->m_duckUntilOnGround = false; // player hit +duck, so cancel our auto duck + } + else + { + mv->m_nButtons |= IN_DUCK; + } + } + } + + // it would be nice to put this into the player->GetPlayerMaxSpeed() method, but + // this flag is only stored in the move! + if ( mv->m_nButtons & IN_SPEED ) + { + mv->m_flMaxSpeed *= CS_PLAYER_SPEED_WALK_MODIFIER; + } + + if ( player->GetMoveType() != MOVETYPE_ISOMETRIC && + player->GetMoveType() != MOVETYPE_NOCLIP && + player->GetMoveType() != MOVETYPE_OBSERVER ) + { + float spd; + + spd = ( mv->m_flForwardMove * mv->m_flForwardMove ) + + ( mv->m_flSideMove * mv->m_flSideMove ) + + ( mv->m_flUpMove * mv->m_flUpMove ); + + + // Slow down by the speed factor + float flSpeedFactor = 1.0f; + if (player->m_pSurfaceData) + { + flSpeedFactor = player->m_pSurfaceData->game.maxSpeedFactor; + } + + // If we have a constraint, slow down because of that too. + float flConstraintSpeedFactor = ComputeConstraintSpeedFactor(); + if (flConstraintSpeedFactor < flSpeedFactor) + flSpeedFactor = flConstraintSpeedFactor; + + // Take the player's velocity modifier into account + if ( FBitSet( m_pCSPlayer->GetFlags(), FL_ONGROUND ) ) + { + flSpeedFactor *= m_pCSPlayer->m_flVelocityModifier; + } + + + mv->m_flMaxSpeed *= flSpeedFactor; + + if ( g_bMovementOptimizations ) + { + // Same thing but only do the sqrt if we have to. + if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed*mv->m_flMaxSpeed ) ) + { + float fRatio = mv->m_flMaxSpeed / sqrt( spd ); + mv->m_flForwardMove *= fRatio; + mv->m_flSideMove *= fRatio; + mv->m_flUpMove *= fRatio; + } + } + else + { + spd = sqrt( spd ); + if ( ( spd != 0.0 ) && ( spd > mv->m_flMaxSpeed ) ) + { + float fRatio = mv->m_flMaxSpeed / spd; + mv->m_flForwardMove *= fRatio; + mv->m_flSideMove *= fRatio; + mv->m_flUpMove *= fRatio; + } + } + } + + + if ( player->GetFlags() & FL_FROZEN || + player->GetFlags() & FL_ONTRAIN || + IsDead() ) + { + mv->m_flForwardMove = 0; + mv->m_flSideMove = 0; + mv->m_flUpMove = 0; + } + + DecayPunchAngle(); + + // Take angles from command. + if ( !IsDead() ) + { + v_angle = mv->m_vecAngles; + v_angle = v_angle + player->m_Local.m_vecPunchAngle; + + // Now adjust roll angle + if ( player->GetMoveType() != MOVETYPE_ISOMETRIC && + player->GetMoveType() != MOVETYPE_NOCLIP ) + { + mv->m_vecAngles[ROLL] = CalcRoll( v_angle, mv->m_vecVelocity, sv_rollangle.GetFloat(), sv_rollspeed.GetFloat() ); + } + else + { + mv->m_vecAngles[ROLL] = 0.0; // v_angle[ ROLL ]; + } + mv->m_vecAngles[PITCH] = v_angle[PITCH]; + mv->m_vecAngles[YAW] = v_angle[YAW]; + } + else + { + mv->m_vecAngles = mv->m_vecOldAngles; + } + + // Set dead player view_offset + if ( IsDead() ) + { + player->SetViewOffset( VEC_DEAD_VIEWHEIGHT ); + } + + // Adjust client view angles to match values used on server. + if ( mv->m_vecAngles[YAW] > 180.0f ) + { + mv->m_vecAngles[YAW] -= 360.0f; + } + + // If we're standing on a player, then force them off. + if ( !player->IsObserver() && ( player->GetMoveType() != MOVETYPE_LADDER ) ) + { + int nLevels = 0; + CBaseEntity *pCurGround = player->GetGroundEntity(); + while ( pCurGround && pCurGround->IsPlayer() && nLevels < 1000 ) + { + pCurGround = pCurGround->GetGroundEntity(); + ++nLevels; + } + if ( nLevels == 1000 ) + Warning( "BUG: CCSGameMovement::CheckParameters - too many stacking levels.\n" ); + + // If they're stacked too many levels deep, slide them off. + if ( nLevels > 1 ) + { + mv->m_flForwardMove = mv->m_flMaxSpeed * 3; + mv->m_flSideMove = 0; + mv->m_nButtons = 0; + mv->m_nImpulseCommand = 0; + } + } +} + + +void CCSGameMovement::ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove ) +{ + m_pCSPlayer = ToCSPlayer( pBasePlayer ); + Assert( m_pCSPlayer ); + + BaseClass::ProcessMovement( pBasePlayer, pMove ); +} + + +bool CCSGameMovement::CanAccelerate() +{ + // Only allow the player to accelerate when in certain states. + CSPlayerState curState = m_pCSPlayer->State_Get(); + if ( curState == STATE_ACTIVE ) + { + return player->GetWaterJumpTime() == 0; + } + else if ( player->IsObserver() ) + { + return true; + } + else + { + return false; + } +} + + +void CCSGameMovement::PlayerMove() +{ + if ( !m_pCSPlayer->CanMove() ) + { + mv->m_flForwardMove = 0; + mv->m_flSideMove = 0; + mv->m_flUpMove = 0; + mv->m_nButtons &= ~(IN_JUMP | IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT); + } + + BaseClass::PlayerMove(); + + if ( FBitSet( m_pCSPlayer->GetFlags(), FL_ONGROUND ) ) + { + if ( m_pCSPlayer->m_flVelocityModifier < 1.0 ) + { + m_pCSPlayer->m_flVelocityModifier += gpGlobals->frametime / 3.0f; + } + + if ( m_pCSPlayer->m_flVelocityModifier > 1.0 ) + m_pCSPlayer->m_flVelocityModifier = 1.0; + } + +#if !defined(CLIENT_DLL) + if ( m_pCSPlayer->IsAlive() ) + { + // Check if our eye height is too close to the ceiling and lower it. + // This is needed because we have taller models with the old collision bounds. + + const float eyeClearance = 12.0f; // eye pos must be this far below the ceiling + + Vector offset = player->GetViewOffset(); + + Vector vHullMin = GetPlayerMins( player->m_Local.m_bDucked ); + vHullMin.z = 0.0f; + Vector vHullMax = GetPlayerMaxs( player->m_Local.m_bDucked ); + + Vector start = player->GetAbsOrigin(); + start.z += vHullMax.z; + Vector end = start; + end.z += eyeClearance - vHullMax.z; + end.z += player->m_Local.m_bDucked ? VEC_DUCK_VIEW_SCALED( player ).z : VEC_VIEW_SCALED( player ).z; + + vHullMax.z = 0.0f; + + Vector fudge( 1, 1, 0 ); + vHullMin += fudge; + vHullMax -= fudge; + + trace_t trace; + Ray_t ray; + ray.Init( start, end, vHullMin, vHullMax ); + UTIL_TraceRay( ray, PlayerSolidMask(), mv->m_nPlayerHandle.Get(), COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + if ( trace.fraction < 1.0f ) + { + float est = start.z + trace.fraction * (end.z - start.z) - player->GetAbsOrigin().z - eyeClearance; + if ( ( player->GetFlags() & FL_DUCKING ) == 0 && !player->m_Local.m_bDucking && !player->m_Local.m_bDucked ) + { + offset.z = est; + } + else + { + offset.z = MIN( est, offset.z ); + } + player->SetViewOffset( offset ); + } + else + { + if ( ( player->GetFlags() & FL_DUCKING ) == 0 && !player->m_Local.m_bDucking && !player->m_Local.m_bDucked ) + { + + player->SetViewOffset( VEC_VIEW_SCALED( player ) ); + } + else if ( m_pCSPlayer->m_duckUntilOnGround ) + { + // Duck Hull, but we're in the air. Calculate where the view would be. + Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); + + // We've got the duck hull, pulled up to the top of where the player should be + Vector lowerClearance = hullSizeNormal - hullSizeCrouch; + Vector duckEyeHeight = GetPlayerViewOffset( false ) - lowerClearance; + player->SetViewOffset( duckEyeHeight ); + } + else if( player->m_Local.m_bDucked && !player->m_Local.m_bDucking ) + { + player->SetViewOffset( VEC_DUCK_VIEW ); + } + } + } +#endif +} + + +void CCSGameMovement::WalkMove( void ) +{ + if ( m_pCSPlayer->m_flStamina > 0 ) + { + float flRatio; + + flRatio = ( STAMINA_MAX - ( ( m_pCSPlayer->m_flStamina / 1000.0 ) * STAMINA_RECOVER_RATE ) ) / STAMINA_MAX; + + // This Goldsrc code was run with variable timesteps and it had framerate dependencies. + // People looking at Goldsrc for reference are usually + // (these days) measuring the stoppage at 60fps or greater, so we need + // to account for the fact that Goldsrc was applying more stopping power + // since it applied the slowdown across more frames. + float flReferenceFrametime = 1.0f / 70.0f; + float flFrametimeRatio = gpGlobals->frametime / flReferenceFrametime; + + flRatio = pow( flRatio, flFrametimeRatio ); + + mv->m_vecVelocity.x *= flRatio; + mv->m_vecVelocity.y *= flRatio; + } + + BaseClass::WalkMove(); + + CheckForLadders( player->GetGroundEntity() != NULL ); +} + + +//------------------------------------------------------------------------------------------------------------------------------- +void CCSGameMovement::AirMove( void ) +{ + BaseClass::AirMove(); + + CheckForLadders( false ); +} + + +//------------------------------------------------------------------------------------------------------------------------------- +bool CCSGameMovement::OnLadder( trace_t &trace ) +{ + if ( trace.plane.normal.z == 1.0f ) + return false; + + return BaseClass::OnLadder( trace ); +} + + +//------------------------------------------------------------------------------------------------------------------------------- +bool CCSGameMovement::LadderMove( void ) +{ + bool isOnLadder = BaseClass::LadderMove(); + if ( isOnLadder && m_pCSPlayer ) + { + m_pCSPlayer->SurpressLadderChecks( mv->GetAbsOrigin(), m_pCSPlayer->m_vecLadderNormal ); + } + + return isOnLadder; +} + + +//------------------------------------------------------------------------------------------------------------------------------- +/** + * In CS, crouching up ladders goes slowly and doesn't make a sound. + */ +float CCSGameMovement::ClimbSpeed( void ) const +{ + if ( mv->m_nButtons & IN_DUCK ) + { + return BaseClass::ClimbSpeed() * CS_PLAYER_SPEED_CLIMB_MODIFIER; + } + else + { + return BaseClass::ClimbSpeed(); + } +} + + +//------------------------------------------------------------------------------------------------------------------------------- +/** +* In CS, strafing on ladders goes slowly. +*/ +float CCSGameMovement::LadderLateralMultiplier( void ) const +{ + if ( mv->m_nButtons & IN_DUCK ) + { + return 1.0f; + } + else + { + return 0.5f; + } +} + + +//------------------------------------------------------------------------------------------------------------------------------- +/** + * Looks behind and beneath the player in the air, in case he skips out over the top of a ladder. If the + * trace hits a ladder, the player is snapped to the ladder. + */ +void CCSGameMovement::CheckForLadders( bool wasOnGround ) +{ + if ( !wasOnGround ) + { + // If we're higher than the last place we were on the ground, bail - obviously we're not dropping + // past a ladder we might want to grab. + if ( mv->GetAbsOrigin().z > m_pCSPlayer->m_lastStandingPos.z ) + return; + + Vector dir = -m_pCSPlayer->m_lastStandingPos + mv->GetAbsOrigin(); + if ( !dir.x && !dir.y ) + { + // If we're dropping straight down, we don't know which way to look for a ladder. Oh well. + return; + } + + dir.z = 0.0f; + float dist = dir.NormalizeInPlace(); + if ( dist > 64.0f ) + { + // Don't grab ladders too far behind us. + return; + } + + trace_t trace; + + TracePlayerBBox( + mv->GetAbsOrigin(), + m_pCSPlayer->m_lastStandingPos - dir*(5+dist), + (PlayerSolidMask() & (~CONTENTS_PLAYERCLIP)), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); + + if ( trace.fraction != 1.0f && OnLadder( trace ) && trace.plane.normal.z != 1.0f ) + { + if ( m_pCSPlayer->CanGrabLadder( trace.endpos, trace.plane.normal ) ) + { + player->SetMoveType( MOVETYPE_LADDER ); + player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); + + player->SetLadderNormal( trace.plane.normal ); + mv->m_vecVelocity.Init(); + + // The ladder check ignored playerclips, to fix a bug exposed by de_train, where a clipbrush is + // flush with a ladder. This causes the above tracehull to fail unless we ignore playerclips. + // However, we have to check for playerclips before we snap to that pos, so we don't warp a + // player into a clipbrush. + TracePlayerBBox( + mv->GetAbsOrigin(), + m_pCSPlayer->m_lastStandingPos - dir*(5+dist), + PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); + + mv->SetAbsOrigin( trace.endpos ); + } + } + } + else + { + m_pCSPlayer->m_lastStandingPos = mv->GetAbsOrigin(); + } +} + + +void CCSGameMovement::ReduceTimers( void ) +{ + float frame_msec = 1000.0f * gpGlobals->frametime; + + if ( m_pCSPlayer->m_flStamina > 0 ) + { + m_pCSPlayer->m_flStamina -= frame_msec; + + if ( m_pCSPlayer->m_flStamina < 0 ) + { + m_pCSPlayer->m_flStamina = 0; + } + } + + BaseClass::ReduceTimers(); +} + +ConVar sv_enablebunnyhopping( "sv_enablebunnyhopping", "0", FCVAR_REPLICATED | FCVAR_NOTIFY ); + +// Only allow bunny jumping up to 1.1x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.1f + +// taken from TF2 but changed BUNNYJUMP_MAX_SPEED_FACTOR from 1.1 to 1.0 +void CCSGameMovement::PreventBunnyJumping() +{ + // Speed at which bunny jumping is limited + float maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * player->m_flMaxspeed; + if ( maxscaledspeed <= 0.0f ) + return; + + // Current player speed + float spd = mv->m_vecVelocity.Length(); + + if ( spd <= maxscaledspeed ) + return; + + // Apply this cropping fraction to velocity + float fraction = ( maxscaledspeed / spd ); + + mv->m_vecVelocity *= fraction; + + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCSGameMovement::CheckJumpButton( void ) +{ + if (m_pCSPlayer->pl.deadflag) + { + mv->m_nOldButtons |= IN_JUMP ; // don't jump again until released + return false; + } + + // See if we are waterjumping. If so, decrement count and return. + if (m_pCSPlayer->m_flWaterJumpTime) + { + m_pCSPlayer->m_flWaterJumpTime -= gpGlobals->frametime; + if (m_pCSPlayer->m_flWaterJumpTime < 0) + m_pCSPlayer->m_flWaterJumpTime = 0; + + return false; + } + + // If we are in the water most of the way... + if ( m_pCSPlayer->GetWaterLevel() >= 2 ) + { + // swimming, not jumping + SetGroundEntity( NULL ); + + if(m_pCSPlayer->GetWaterType() == CONTENTS_WATER) // We move up a certain amount + mv->m_vecVelocity[2] = 100; + else if (m_pCSPlayer->GetWaterType() == CONTENTS_SLIME) + mv->m_vecVelocity[2] = 80; + + // play swiming sound + if ( m_pCSPlayer->m_flSwimSoundTime <= 0 ) + { + // Don't play sound again for 1 second + m_pCSPlayer->m_flSwimSoundTime = 1000; + PlaySwimSound(); + } + + return false; + } + + // No more effect + if (m_pCSPlayer->GetGroundEntity() == NULL) + { + mv->m_nOldButtons |= IN_JUMP; + return false; // in air, so no effect + } + + if ( mv->m_nOldButtons & IN_JUMP ) + return false; // don't pogo stick + + if ( !sv_enablebunnyhopping.GetBool() ) + { + PreventBunnyJumping(); + } + + // In the air now. + SetGroundEntity( NULL ); + + m_pCSPlayer->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true ); + + //MoveHelper()->PlayerSetAnimation( PLAYER_JUMP ); + m_pCSPlayer->DoAnimationEvent( PLAYERANIMEVENT_JUMP ); + + float flGroundFactor = 1.0f; + if (player->m_pSurfaceData) + { + flGroundFactor = player->m_pSurfaceData->game.jumpFactor; + } + + // if we weren't ducking, bots and hostages do a crouchjump programatically + if ( (!player || player->IsBot()) && !(mv->m_nButtons & IN_DUCK) ) + { + m_pCSPlayer->m_duckUntilOnGround = true; + FinishDuck(); + } + + // Acclerate upward + // If we are ducking... + float startz = mv->m_vecVelocity[2]; + if ( m_pCSPlayer->m_duckUntilOnGround || ( m_pCSPlayer->m_Local.m_bDucking ) || ( m_pCSPlayer->GetFlags() & FL_DUCKING ) ) + { + // d = 0.5 * g * t^2 - distance traveled with linear accel + // t = sqrt(2.0 * 45 / g) - how long to fall 45 units + // v = g * t - velocity at the end (just invert it to jump up that high) + // v = g * sqrt(2.0 * 45 / g ) + // v^2 = g * g * 2.0 * 45 / g + // v = sqrt( g * 2.0 * 45 ) + + mv->m_vecVelocity[2] = flGroundFactor * sqrt(2 * 800 * 57.0); // 2 * gravity * height + } + else + { + mv->m_vecVelocity[2] += flGroundFactor * sqrt(2 * 800 * 57.0); // 2 * gravity * height + } + + if ( m_pCSPlayer->m_flStamina > 0 ) + { + float flRatio; + + flRatio = ( STAMINA_MAX - ( ( m_pCSPlayer->m_flStamina / 1000.0 ) * STAMINA_RECOVER_RATE ) ) / STAMINA_MAX; + + mv->m_vecVelocity[2] *= flRatio; + } + + m_pCSPlayer->m_flStamina = ( STAMINA_COST_JUMP / STAMINA_RECOVER_RATE ) * 1000.0; + + FinishGravity(); + + mv->m_outWishVel.z += mv->m_vecVelocity[2] - startz; + mv->m_outStepHeight += 0.1f; + + OnJump(mv->m_outWishVel.z); + +#ifndef CLIENT_DLL + // allow bots to react + IGameEvent * event = gameeventmanager->CreateEvent( "player_jump" ); + if ( event ) + { + event->SetInt( "userid", m_pCSPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif + + // Flag that we jumped. + mv->m_nOldButtons |= IN_JUMP; // don't jump again until released + return true; +} + + +void CCSGameMovement::DecayPunchAngle( void ) +{ + float len; + + Vector vPunchAngle; + + vPunchAngle.x = m_pCSPlayer->m_Local.m_vecPunchAngle->x; + vPunchAngle.y = m_pCSPlayer->m_Local.m_vecPunchAngle->y; + vPunchAngle.z = m_pCSPlayer->m_Local.m_vecPunchAngle->z; + + len = VectorNormalize ( vPunchAngle ); + len -= (10.0 + len * 0.5) * gpGlobals->frametime; + len = MAX( len, 0.0 ); + VectorScale ( vPunchAngle, len, vPunchAngle ); + + m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 0, vPunchAngle.x ); + m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 1, vPunchAngle.y ); + m_pCSPlayer->m_Local.m_vecPunchAngle.Set( 2, vPunchAngle.z ); +} + + +void CCSGameMovement::HandleDuckingSpeedCrop() +{ + //============================================================================= + // HPE_BEGIN: + // [Forrest] + //============================================================================= + // Movement speed in free look camera mode is unaffected by ducking state. + if ( player->GetObserverMode() == OBS_MODE_ROAMING ) + return; + //============================================================================= + // HPE_END + //============================================================================= + + if ( !( m_iSpeedCropped & SPEED_CROPPED_DUCK ) ) + { + if ( ( mv->m_nButtons & IN_DUCK ) || ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) ) + { + mv->m_flForwardMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; + mv->m_flSideMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; + mv->m_flUpMove *= CS_PLAYER_SPEED_DUCK_MODIFIER; + m_iSpeedCropped |= SPEED_CROPPED_DUCK; + } + } +} + +bool CCSGameMovement::CanUnduck() +{ + trace_t trace; + Vector newOrigin; + + VectorCopy( mv->GetAbsOrigin(), newOrigin ); + + if ( player->GetGroundEntity() != NULL ) + { + newOrigin += VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + } + else + { + // If in air an letting go of croush, make sure we can offset origin to make + // up for uncrouching + Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); + + newOrigin += -0.5f * ( hullSizeNormal - hullSizeCrouch ); + } + + UTIL_TraceHull( mv->GetAbsOrigin(), newOrigin, VEC_HULL_MIN_SCALED( player ), VEC_HULL_MAX_SCALED( player ), PlayerSolidMask(), player, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + if ( trace.startsolid || ( trace.fraction != 1.0f ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Stop ducking +//----------------------------------------------------------------------------- +void CCSGameMovement::FinishUnDuck( void ) +{ + trace_t trace; + Vector newOrigin; + + VectorCopy( mv->GetAbsOrigin(), newOrigin ); + + if ( player->GetGroundEntity() != NULL ) + { + newOrigin += VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + } + else + { + // If in air an letting go of croush, make sure we can offset origin to make + // up for uncrouching + Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); + + Vector viewDelta = -0.5f * ( hullSizeNormal - hullSizeCrouch ); + + VectorAdd( newOrigin, viewDelta, newOrigin ); + } + + player->m_Local.m_bDucked = false; + player->RemoveFlag( FL_DUCKING ); + player->m_Local.m_bDucking = false; + player->SetViewOffset( GetPlayerViewOffset( false ) ); + player->m_Local.m_flDucktime = 0; + + mv->SetAbsOrigin( newOrigin ); + + // Recategorize position since ducking can change origin + CategorizePosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: Finish ducking +//----------------------------------------------------------------------------- +void CCSGameMovement::FinishDuck( void ) +{ + + Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); + + Vector viewDelta = 0.5f * ( hullSizeNormal - hullSizeCrouch ); + + player->SetViewOffset( GetPlayerViewOffset( true ) ); + player->AddFlag( FL_DUCKING ); + player->m_Local.m_bDucking = false; + + if ( !player->m_Local.m_bDucked ) + { + + Vector org = mv->GetAbsOrigin(); + + if ( player->GetGroundEntity() != NULL ) + { + org -= VEC_DUCK_HULL_MIN_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + } + else + { + org += viewDelta; + } + mv->SetAbsOrigin( org ); + + player->m_Local.m_bDucked = true; + } + + // See if we are stuck? + FixPlayerCrouchStuck( true ); + + // Recategorize position since ducking can change origin + CategorizePosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: See if duck button is pressed and do the appropriate things +//----------------------------------------------------------------------------- +void CCSGameMovement::Duck( void ) +{ + + // Fix taken from zblock for rapid crouch/stand not showing stand on other clients + + if ( player->GetFlags() & FL_ONGROUND ) + { + // if prevent crouch + if ( !( mv->m_nButtons & IN_DUCK ) && ( mv->m_nOldButtons & IN_DUCK ) ) + { + // Player has released crouch and moving to standing + m_fTimeLastUnducked = gpGlobals->curtime; + } + else if ( ( mv->m_nButtons & IN_DUCK ) && !( mv->m_nOldButtons & IN_DUCK ) ) + { + // Crouch from standing + if ( ( player->GetFlags() & FL_DUCKING ) + && ( m_fTimeLastUnducked > (gpGlobals->curtime - sv_timebetweenducks.GetFloat() ) ) ) + { + // if the server thinks the player is still crouched + // AND the time the player started to stand (from being ducked) was less than 300ms ago + // prevent the player from ducking again + mv->m_nButtons &= ~IN_DUCK; + } + } + } + + int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame + int buttonsPressed = buttonsChanged & mv->m_nButtons; // The changed ones still down are "pressed" + int buttonsReleased = buttonsChanged & mv->m_nOldButtons; // The changed ones which were previously down are "released" + + // Check to see if we are in the air. + bool bInAir = player->GetGroundEntity() == NULL && player->GetMoveType() != MOVETYPE_LADDER; + + if ( mv->m_nButtons & IN_DUCK ) + { + mv->m_nOldButtons |= IN_DUCK; + } + else + { + mv->m_nOldButtons &= ~IN_DUCK; + } + + if ( IsDead() ) + { + // Unduck + if ( player->GetFlags() & FL_DUCKING ) + { + FinishUnDuck(); + } + return; + } + + HandleDuckingSpeedCrop(); + + if ( m_pCSPlayer->m_duckUntilOnGround ) + { + if ( !bInAir ) + { + m_pCSPlayer->m_duckUntilOnGround = false; + if ( CanUnduck() ) + { + FinishUnDuck(); + } + return; + } + else + { + if ( mv->m_vecVelocity.z > 0.0f ) + return; + + // Check if we can un-duck. We want to unduck if we have space for the standing hull, and + // if it is less than 2 inches off the ground. + trace_t trace; + Vector newOrigin; + Vector groundCheck; + + VectorCopy( mv->GetAbsOrigin(), newOrigin ); + Vector hullSizeNormal = VEC_HULL_MAX_SCALED( player ) - VEC_HULL_MIN_SCALED( player ); + Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( player ) - VEC_DUCK_HULL_MIN_SCALED( player ); + newOrigin -= ( hullSizeNormal - hullSizeCrouch ); + groundCheck = newOrigin; + groundCheck.z -= player->GetStepSize(); + + UTIL_TraceHull( newOrigin, groundCheck, VEC_HULL_MIN_SCALED( player ), VEC_HULL_MAX_SCALED( player ), PlayerSolidMask(), player, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + if ( trace.startsolid || trace.fraction == 1.0f ) + return; // Can't even stand up, or there's no ground underneath us + + m_pCSPlayer->m_duckUntilOnGround = false; + if ( CanUnduck() ) + { + FinishUnDuck(); + } + return; + } + } + + // Holding duck, in process of ducking or fully ducked? + if ( ( mv->m_nButtons & IN_DUCK ) || ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) ) + { + if ( mv->m_nButtons & IN_DUCK ) + { + bool alreadyDucked = ( player->GetFlags() & FL_DUCKING ) ? true : false; + + if ( (buttonsPressed & IN_DUCK ) && !( player->GetFlags() & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + player->m_Local.m_flDucktime = 1000; + player->m_Local.m_bDucking = true; + } + + float duckmilliseconds = MAX( 0.0f, 1000.0f - (float)player->m_Local.m_flDucktime ); + float duckseconds = duckmilliseconds / 1000.0f; + + //time = MAX( 0.0, ( 1.0 - (float)player->m_Local.m_flDucktime / 1000.0 ) ); + + if ( player->m_Local.m_bDucking ) + { + if ( !( player->GetFlags() & FL_ANIMDUCKING ) ) + player->AddFlag( FL_ANIMDUCKING ); + + // Finish ducking immediately if duck time is over or not on ground + if ( ( duckseconds > TIME_TO_DUCK ) || + ( player->GetGroundEntity() == NULL ) || + alreadyDucked) + { + FinishDuck(); + } + else + { + // Calc parametric time + float duckFraction = SimpleSpline( duckseconds / TIME_TO_DUCK ); + SetDuckedEyeOffset( duckFraction ); + } + } + } + else + { + // Try to unduck unless automovement is not allowed + // NOTE: When not onground, you can always unduck + if ( player->m_Local.m_bAllowAutoMovement || player->GetGroundEntity() == NULL ) + { + if ( (buttonsReleased & IN_DUCK ) && ( player->GetFlags() & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + player->m_Local.m_flDucktime = 1000; + player->m_Local.m_bDucking = true; // or unducking + } + + float duckmilliseconds = MAX( 0.0f, 1000.0f - (float)player->m_Local.m_flDucktime ); + float duckseconds = duckmilliseconds / 1000.0f; + + if ( CanUnduck() ) + { + if ( player->m_Local.m_bDucking || + player->m_Local.m_bDucked ) // or unducking + { + if ( player->GetFlags() & FL_ANIMDUCKING ) + player->RemoveFlag( FL_ANIMDUCKING ); + + // Finish ducking immediately if duck time is over or not on ground + if ( ( duckseconds > TIME_TO_UNDUCK ) || + ( player->GetGroundEntity() == NULL ) ) + { + FinishUnDuck(); + } + else + { + // Calc parametric time + float duckFraction = SimpleSpline( 1.0f - ( duckseconds / TIME_TO_UNDUCK ) ); + SetDuckedEyeOffset( duckFraction ); + } + } + } + else + { + // Still under something where we can't unduck, so make sure we reset this timer so + // that we'll unduck once we exit the tunnel, etc. + player->m_Local.m_flDucktime = 1000; + } + } + } + } +} + + +void CCSGameMovement::OnJump( float fImpulse ) +{ + m_pCSPlayer->OnJump( fImpulse ); +} + +void CCSGameMovement::OnLand( float fVelocity ) +{ + m_pCSPlayer->OnLand( fVelocity ); +} + +//----------------------------------------------------------------------------- +// Purpose: Essentially the same as TracePlayerBBox, but adds a callback to +// exclude entities that are not standable (except for other players) +//----------------------------------------------------------------------------- +void CCSGameMovement::TryTouchGround( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, unsigned int fMask, int collisionGroup, trace_t& pm ) +{ + VPROF( "CCSGameMovement::TryTouchGround" ); + + Ray_t ray; + ray.Init( start, end, mins, maxs ); + + ShouldHitFunc_t pStandingTestCallback = sv_enableboost.GetBool() ? NULL : CheckForStandable; + + UTIL_TraceRay( ray, fMask, mv->m_nPlayerHandle.Get(), collisionGroup, &pm, pStandingTestCallback ); + +} diff --git a/game/shared/cstrike/cs_gamerules.cpp b/game/shared/cstrike/cs_gamerules.cpp new file mode 100644 index 0000000..c688140 --- /dev/null +++ b/game/shared/cstrike/cs_gamerules.cpp @@ -0,0 +1,5859 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The TF Game rules +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "cs_gamerules.h" +#include "cs_ammodef.h" +#include "weapon_csbase.h" +#include "cs_shareddefs.h" +#include "KeyValues.h" +#include "cs_achievement_constants.h" +#include "fmtstr.h" + +#ifdef CLIENT_DLL + + #include "networkstringtable_clientdll.h" + #include "utlvector.h" + +#else + + #include "bot.h" + #include "utldict.h" + #include "cs_player.h" + #include "cs_team.h" + #include "cs_gamerules.h" + #include "voice_gamemgr.h" + #include "igamesystem.h" + #include "weapon_c4.h" + #include "mapinfo.h" + #include "shake.h" + #include "mapentities.h" + #include "game.h" + #include "cs_simple_hostage.h" + #include "cs_gameinterface.h" + #include "player_resource.h" + #include "info_view_parameters.h" + #include "cs_bot_manager.h" + #include "cs_bot.h" + #include "eventqueue.h" + #include "fmtstr.h" + #include "teamplayroundbased_gamerules.h" + #include "gameweaponmanager.h" + + #include "cs_gamestats.h" + #include "cs_urlretrieveprices.h" + #include "networkstringtable_gamedll.h" + #include "player_resource.h" + #include "cs_player_resource.h" + +#if defined( REPLAY_ENABLED ) + #include "replay/ireplaysystem.h" + #include "replay/iserverreplaycontext.h" + #include "replay/ireplaysessionrecorder.h" +#endif // REPLAY_ENABLED + +#endif + + +#include "cs_blackmarket.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#ifndef CLIENT_DLL + + +#define CS_GAME_STATS_UPDATE 79200 //22 hours +#define CS_GAME_STATS_UPDATE_PERIOD 7200 // 2 hours + +extern IUploadGameStats *gamestatsuploader; + +#if defined( REPLAY_ENABLED ) +extern IReplaySystem *g_pReplay; +#endif // REPLAY_ENABLED + +#endif + + +/** + * Player hull & eye position for standing, ducking, etc. This version has a taller + * player height, but goldsrc-compatible collision bounds. + */ +static CViewVectors g_CSViewVectors( + Vector( 0, 0, 64 ), // eye position + + Vector(-16, -16, 0 ), // hull min + Vector( 16, 16, 62 ), // hull max + + Vector(-16, -16, 0 ), // duck hull min + Vector( 16, 16, 45 ), // duck hull max + Vector( 0, 0, 47 ), // duck view + + Vector(-10, -10, -10 ), // observer hull min + Vector( 10, 10, 10 ), // observer hull max + + Vector( 0, 0, 14 ) // dead view height +); + + +#ifndef CLIENT_DLL +LINK_ENTITY_TO_CLASS(info_player_terrorist, CPointEntity); +LINK_ENTITY_TO_CLASS(info_player_counterterrorist,CPointEntity); +LINK_ENTITY_TO_CLASS(info_player_logo,CPointEntity); +#endif + +REGISTER_GAMERULES_CLASS( CCSGameRules ); + + +BEGIN_NETWORK_TABLE_NOBASE( CCSGameRules, DT_CSGameRules ) + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bFreezePeriod ) ), + RecvPropInt( RECVINFO( m_iRoundTime ) ), + RecvPropFloat( RECVINFO( m_fRoundStartTime ) ), + RecvPropFloat( RECVINFO( m_flGameStartTime ) ), + RecvPropInt( RECVINFO( m_iHostagesRemaining ) ), + RecvPropBool( RECVINFO( m_bMapHasBombTarget ) ), + RecvPropBool( RECVINFO( m_bMapHasRescueZone ) ), + RecvPropBool( RECVINFO( m_bLogoMap ) ), + RecvPropBool( RECVINFO( m_bBlackMarket ) ) + #else + SendPropBool( SENDINFO( m_bFreezePeriod ) ), + SendPropInt( SENDINFO( m_iRoundTime ), 16 ), + SendPropFloat( SENDINFO( m_fRoundStartTime ), 32, SPROP_NOSCALE ), + SendPropFloat( SENDINFO( m_flGameStartTime ), 32, SPROP_NOSCALE ), + SendPropInt( SENDINFO( m_iHostagesRemaining ), 4 ), + SendPropBool( SENDINFO( m_bMapHasBombTarget ) ), + SendPropBool( SENDINFO( m_bMapHasRescueZone ) ), + SendPropBool( SENDINFO( m_bLogoMap ) ), + SendPropBool( SENDINFO( m_bBlackMarket ) ) + #endif +END_NETWORK_TABLE() + + +LINK_ENTITY_TO_CLASS( cs_gamerules, CCSGameRulesProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( CSGameRulesProxy, DT_CSGameRulesProxy ) + + +#ifdef CLIENT_DLL + void RecvProxy_CSGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) + { + CCSGameRules *pRules = CSGameRules(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy ) + RecvPropDataTable( "cs_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_CSGameRules ), RecvProxy_CSGameRules ) + END_RECV_TABLE() +#else + void* SendProxy_CSGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) + { + CCSGameRules *pRules = CSGameRules(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE( CCSGameRulesProxy, DT_CSGameRulesProxy ) + SendPropDataTable( "cs_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_CSGameRules ), SendProxy_CSGameRules ) + END_SEND_TABLE() +#endif + + + +ConVar ammo_50AE_max( "ammo_50AE_max", "35", FCVAR_REPLICATED ); +ConVar ammo_762mm_max( "ammo_762mm_max", "90", FCVAR_REPLICATED ); +ConVar ammo_556mm_max( "ammo_556mm_max", "90", FCVAR_REPLICATED ); +ConVar ammo_556mm_box_max( "ammo_556mm_box_max", "200", FCVAR_REPLICATED ); +ConVar ammo_338mag_max( "ammo_338mag_max", "30", FCVAR_REPLICATED ); +ConVar ammo_9mm_max( "ammo_9mm_max", "120", FCVAR_REPLICATED ); +ConVar ammo_buckshot_max( "ammo_buckshot_max", "32", FCVAR_REPLICATED ); +ConVar ammo_45acp_max( "ammo_45acp_max", "100", FCVAR_REPLICATED ); +ConVar ammo_357sig_max( "ammo_357sig_max", "52", FCVAR_REPLICATED ); +ConVar ammo_57mm_max( "ammo_57mm_max", "100", FCVAR_REPLICATED ); +ConVar ammo_hegrenade_max( "ammo_hegrenade_max", "1", FCVAR_REPLICATED ); +ConVar ammo_flashbang_max( "ammo_flashbang_max", "2", FCVAR_REPLICATED ); +ConVar ammo_smokegrenade_max( "ammo_smokegrenade_max", "1", FCVAR_REPLICATED ); + +//ConVar mp_dynamicpricing( "mp_dynamicpricing", "0", FCVAR_REPLICATED, "Enables or Disables the dynamic weapon prices" ); + + +extern ConVar sv_stopspeed; + +ConVar mp_buytime( + "mp_buytime", + "1.5", + FCVAR_REPLICATED, + "How many minutes after round start players can buy items for.", + true, 0.25, + false, 0 ); + +ConVar mp_playerid( + "mp_playerid", + "0", + FCVAR_REPLICATED, + "Controls what information player see in the status bar: 0 all names; 1 team names; 2 no names", + true, 0, + true, 2 ); + +ConVar mp_playerid_delay( + "mp_playerid_delay", + "0.5", + FCVAR_REPLICATED, + "Number of seconds to delay showing information in the status bar", + true, 0, + true, 1 ); + +ConVar mp_playerid_hold( + "mp_playerid_hold", + "0.25", + FCVAR_REPLICATED, + "Number of seconds to keep showing old information in the status bar", + true, 0, + true, 1 ); + +ConVar mp_round_restart_delay( + "mp_round_restart_delay", + "5.0", + FCVAR_REPLICATED, + "Number of seconds to delay before restarting a round after a win", + true, 0.0f, + true, 10.0f ); + +ConVar sv_allowminmodels( + "sv_allowminmodels", + "1", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "Allow or disallow the use of cl_minmodels on this server." ); + +#ifdef CLIENT_DLL + +ConVar cl_autowepswitch( + "cl_autowepswitch", + "1", + FCVAR_ARCHIVE | FCVAR_USERINFO, + "Automatically switch to picked up weapons (if more powerful)" ); + +ConVar cl_autohelp( + "cl_autohelp", + "1", + FCVAR_ARCHIVE | FCVAR_USERINFO, + "Auto-help" ); + +#else + + // longest the intermission can last, in seconds + #define MAX_INTERMISSION_TIME 120 + + // Falling damage stuff. + #define CS_PLAYER_FATAL_FALL_SPEED 1100 // approx 60 feet + #define CS_PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet + #define CS_DAMAGE_FOR_FALL_SPEED ((float)100 / ( CS_PLAYER_FATAL_FALL_SPEED - CS_PLAYER_MAX_SAFE_FALL_SPEED )) // damage per unit per second. + + // These entities are preserved each round restart. The rest are removed and recreated. + static const char *s_PreserveEnts[] = + { + "ai_network", + "ai_hint", + "cs_gamerules", + "cs_team_manager", + "cs_player_manager", + "env_soundscape", + "env_soundscape_proxy", + "env_soundscape_triggerable", + "env_sun", + "env_wind", + "env_fog_controller", + "func_brush", + "func_wall", + "func_buyzone", + "func_illusionary", + "func_hostage_rescue", + "func_bomb_target", + "infodecal", + "info_projecteddecal", + "info_node", + "info_target", + "info_node_hint", + "info_player_counterterrorist", + "info_player_terrorist", + "info_map_parameters", + "keyframe_rope", + "move_rope", + "info_ladder", + "player", + "point_viewcontrol", + "scene_manager", + "shadow_control", + "sky_camera", + "soundent", + "trigger_soundscape", + "viewmodel", + "predicted_viewmodel", + "worldspawn", + "point_devshot_camera", + "", // END Marker + }; + + + // --------------------------------------------------------------------------------------------------- // + // Voice helper + // --------------------------------------------------------------------------------------------------- // + + class CVoiceGameMgrHelper : public IVoiceGameMgrHelper + { + public: + virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity ) + { + // Dead players can only be heard by other dead team mates + if ( pTalker->IsAlive() == false ) + { + if ( pListener->IsAlive() == false ) + return ( pListener->InSameTeam( pTalker ) ); + + return false; + } + + return ( pListener->InSameTeam( pTalker ) ); + } + }; + CVoiceGameMgrHelper g_VoiceGameMgrHelper; + IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; + + + + // --------------------------------------------------------------------------------------------------- // + // Globals. + // --------------------------------------------------------------------------------------------------- // + + // NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. + const char *sTeamNames[] = + { + "Unassigned", + "Spectator", + "TERRORIST", + "CT" + }; + + extern ConVar mp_maxrounds; + + ConVar mp_startmoney( + "mp_startmoney", + "800", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "amount of money each player gets when they reset", + true, 800, + true, 16000 ); + + ConVar mp_roundtime( + "mp_roundtime", + "2.5", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "How many minutes each round takes.", + true, 1, // min value + true, 9 // max value + ); + + ConVar mp_freezetime( + "mp_freezetime", + "6", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "how many seconds to keep players frozen when the round starts", + true, 0, // min value + true, 60 // max value + ); + + ConVar mp_c4timer( + "mp_c4timer", + "45", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "how long from when the C4 is armed until it blows", + true, 10, // min value + true, 90 // max value + ); + + ConVar mp_limitteams( + "mp_limitteams", + "2", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "Max # of players 1 team can have over another (0 disables check)", + true, 0, // min value + true, 30 // max value + ); + + ConVar mp_tkpunish( + "mp_tkpunish", + "0", + FCVAR_REPLICATED, + "Will a TK'er be punished in the next round? {0=no, 1=yes}" ); + + ConVar mp_autokick( + "mp_autokick", + "1", + FCVAR_REPLICATED, + "Kick idle/team-killing players" ); + + ConVar mp_spawnprotectiontime( + "mp_spawnprotectiontime", + "5", + FCVAR_REPLICATED, + "Kick players who team-kill within this many seconds of a round restart." ); + + ConVar mp_humanteam( + "mp_humanteam", + "any", + FCVAR_REPLICATED, + "Restricts human players to a single team {any, CT, T}" ); + + ConVar mp_ignore_round_win_conditions( + "mp_ignore_round_win_conditions", + "0", + FCVAR_REPLICATED, + "Ignore conditions which would end the current round"); + + ConCommand EndRound( "endround", &CCSGameRules::EndRound, "End the current round.", FCVAR_CHEAT ); + + + // --------------------------------------------------------------------------------------------------- // + // Global helper functions. + // --------------------------------------------------------------------------------------------------- // + + void InitBodyQue(void) + { + // FIXME: Make this work + } + + + Vector DropToGround( + CBaseEntity *pMainEnt, + const Vector &vPos, + const Vector &vMins, + const Vector &vMaxs ) + { + trace_t trace; + UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace ); + return trace.endpos; + } + + + //----------------------------------------------------------------------------- + // Purpose: This function can be used to find a valid placement location for an entity. + // Given an origin to start looking from and a minimum radius to place the entity at, + // it will sweep out a circle around vOrigin and try to find a valid spot (on the ground) + // where mins and maxs will fit. + // Input : *pMainEnt - Entity to place + // &vOrigin - Point to search around + // fRadius - Radius to search within + // nTries - Number of tries to attempt + // &mins - mins of the Entity + // &maxs - maxs of the Entity + // &outPos - Return point + // Output : Returns true and fills in outPos if it found a spot. + //----------------------------------------------------------------------------- + bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround ) + { + // This function moves the box out in each dimension in each step trying to find empty space like this: + // + // X + // X X + // Step 1: X Step 2: XXX Step 3: XXXXX + // X X + // X + // + + Vector mins, maxs; + pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs ); + mins -= pMainEnt->GetAbsOrigin(); + maxs -= pMainEnt->GetAbsOrigin(); + + // Put some padding on their bbox. + float flPadSize = 5; + Vector vTestMins = mins - Vector( flPadSize, flPadSize, flPadSize ); + Vector vTestMaxs = maxs + Vector( flPadSize, flPadSize, flPadSize ); + + // First test the starting origin. + if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) ) + { + if ( bDropToGround ) + { + outPos = DropToGround( pMainEnt, vOrigin, vTestMins, vTestMaxs ); + } + else + { + outPos = vOrigin; + } + return true; + } + + Vector vDims = vTestMaxs - vTestMins; + + // Keep branching out until we get too far. + int iCurIteration = 0; + int nMaxIterations = 15; + + int offset = 0; + do + { + for ( int iDim=0; iDim < 3; iDim++ ) + { + float flCurOffset = offset * vDims[iDim]; + + for ( int iSign=0; iSign < 2; iSign++ ) + { + Vector vBase = vOrigin; + vBase[iDim] += (iSign*2-1) * flCurOffset; + + if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) ) + { + // Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point. + // (Useful for keeping things from spawning behind walls near a spawn point) + trace_t tr; + UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction != 1.0 ) + { + continue; + } + + if ( bDropToGround ) + outPos = DropToGround( pMainEnt, vBase, vTestMins, vTestMaxs ); + else + outPos = vBase; + + return true; + } + } + } + + ++offset; + } while ( iCurIteration++ < nMaxIterations ); + + // Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() ); + return false; + } + + int UTIL_HumansInGame( bool ignoreSpectators ) + { + int iCount = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *entity = CCSPlayer::Instance( i ); + + if ( entity && !FNullEnt( entity->edict() ) ) + { + if ( FStrEq( entity->GetPlayerName(), "" ) ) + continue; + + if ( FBitSet( entity->GetFlags(), FL_FAKECLIENT ) ) + continue; + + if ( ignoreSpectators && entity->GetTeamNumber() != TEAM_TERRORIST && entity->GetTeamNumber() != TEAM_CT ) + continue; + + if ( ignoreSpectators && entity->State_Get() == STATE_PICKINGCLASS ) + continue; + + iCount++; + } + } + + return iCount; + } + + // --------------------------------------------------------------------------------------------------- // + // CCSGameRules implementation. + // --------------------------------------------------------------------------------------------------- // + + CCSGameRules::CCSGameRules() + { + m_iRoundTime = 0; + m_iRoundWinStatus = WINNER_NONE; + m_iFreezeTime = 0; + + m_fRoundStartTime = 0; + m_bAllowWeaponSwitch = true; + m_bFreezePeriod = true; + m_iNumTerrorist = m_iNumCT = 0; // number of players per team + m_flRestartRoundTime = 0.1f; // restart first round as soon as possible + m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0; + m_bFirstConnected = false; + m_bCompleteReset = false; + m_iAccountTerrorist = m_iAccountCT = 0; + m_iNumCTWins = 0; + m_iNumTerroristWins = 0; + m_iNumConsecutiveCTLoses = 0; + m_iNumConsecutiveTerroristLoses = 0; + m_bTargetBombed = false; + m_bBombDefused = false; + m_iTotalRoundsPlayed = -1; + m_iUnBalancedRounds = 0; + m_flGameStartTime = 0; + m_iHostagesRemaining = 0; + m_bLevelInitialized = false; + m_bLogoMap = false; + m_tmNextPeriodicThink = 0; + + m_bMapHasBombTarget = false; + m_bMapHasRescueZone = false; + + m_iSpawnPointCount_Terrorist = 0; + m_iSpawnPointCount_CT = 0; + + m_bTCantBuy = false; + m_bCTCantBuy = false; + m_bMapHasBuyZone = false; + + m_iLoserBonus = 0; + + m_iHostagesRescued = 0; + m_iHostagesTouched = 0; + m_flNextHostageAnnouncement = 0.0f; + + //============================================================================= + // HPE_BEGIN + // [dwenger] Reset rescue-related achievement values + //============================================================================= + + // [tj] reset flawless and lossless round related flags + m_bNoTerroristsKilled = true; + m_bNoCTsKilled = true; + m_bNoTerroristsDamaged = true; + m_bNoCTsDamaged = true; + m_pFirstKill = NULL; + m_firstKillTime = 0; + + // [menglish] Reset fun fact values + m_pFirstBlood = NULL; + m_firstBloodTime = 0; + + m_bCanDonateWeapons = true; + + // [dwenger] Reset rescue-related achievement values + m_pLastRescuer = NULL; + m_iNumRescuers = 0; + + m_hostageWasInjured = false; + m_hostageWasKilled = false; + + m_pFunFactManager = new CCSFunFactMgr(); + m_pFunFactManager->Init(); + + //============================================================================= + // HPE_END + //============================================================================= + + m_iHaveEscaped = 0; + m_bMapHasEscapeZone = false; + m_iNumEscapers = 0; + m_iNumEscapeRounds = 0; + + m_iMapHasVIPSafetyZone = 0; + m_pVIP = NULL; + m_iConsecutiveVIP = 0; + + m_bMapHasBombZone = false; + m_bBombDropped = false; + m_bBombPlanted = false; + m_pLastBombGuy = NULL; + + m_bAllowWeaponSwitch = true; + + m_flNextHostageAnnouncement = gpGlobals->curtime; // asap. + + ReadMultiplayCvars(); + + m_pPrices = NULL; + m_bBlackMarket = false; + m_bDontUploadStats = false; + + // Create the team managers + for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) + { + CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "cs_team_manager" )); + pTeam->Init( sTeamNames[i], i ); + + g_Teams.AddToTail( pTeam ); + } + + if ( filesystem->FileExists( UTIL_VarArgs( "maps/cfg/%s.cfg", STRING(gpGlobals->mapname) ) ) ) + { + // Execute a map specific cfg file - as in Day of Defeat + // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it. + engine->ServerCommand( UTIL_VarArgs( "exec \"%s.cfg\" */maps\n", STRING(gpGlobals->mapname) ) ); + engine->ServerExecute(); + } + +#ifndef CLIENT_DLL + // stats + + if ( g_flGameStatsUpdateTime == 0.0f ) + { + memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) ); + memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) ); + memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) ); + g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours. + } +#endif + } + + void CCSGameRules::AddPricesToTable( weeklyprice_t prices ) + { + int iIndex = m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" ); + + if ( iIndex == INVALID_STRING_INDEX ) + { + m_StringTableBlackMarket->AddString( CBaseEntity::IsServer(), "blackmarket_prices", sizeof( weeklyprice_t), &prices ); + } + else + { + m_StringTableBlackMarket->SetStringUserData( iIndex, sizeof( weeklyprice_t), &prices ); + } + + SetBlackMarketPrices( false ); + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + CCSGameRules::~CCSGameRules() + { + // Note, don't delete each team since they are in the gEntList and will + // automatically be deleted from there, instead. + g_Teams.Purge(); + if( m_pFunFactManager ) + { + delete m_pFunFactManager; + } + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void CCSGameRules::UpdateClientData( CBasePlayer *player ) + { + } + + //----------------------------------------------------------------------------- + // Purpose: TF2 Specific Client Commands + // Input : + // Output : + //----------------------------------------------------------------------------- + bool CCSGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) + { + CCSPlayer *pPlayer = ToCSPlayer( pEdict ); + + if ( FStrEq( args[0], "changeteam" ) ) + { + return true; + } + else if ( FStrEq( args[0], "nextmap" ) ) + { + if ( pPlayer->m_iNextTimeCheck < gpGlobals->curtime ) + { + char szNextMap[32]; + + if ( nextlevel.GetString() && *nextlevel.GetString() ) + { + Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) ); + } + else + { + GetNextLevelName( szNextMap, sizeof( szNextMap ) ); + } + + ClientPrint( pPlayer, HUD_PRINTTALK, "#game_nextmap", szNextMap); + + pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1; + } + return true; + } + else if( pPlayer->ClientCommand( args ) ) + { + return true; + } + else if( BaseClass::ClientCommand( pEdict, args ) ) + { + return true; + } + else if ( TheBots->ServerCommand( args.GetCommandString() ) ) + { + return true; + } + else + { + return TheBots->ClientCommand( pPlayer, args ); + } + } + + //----------------------------------------------------------------------------- + // Purpose: Player has just spawned. Equip them. + //----------------------------------------------------------------------------- + void CCSGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) + { + CCSPlayer *pPlayer = dynamic_cast< CCSPlayer * >( CBaseEntity::Instance( pEntity ) ); + if ( pPlayer ) + { + char const *pszCommand = pKeyValues->GetName(); + if ( pszCommand && pszCommand[0] ) + { + if ( FStrEq( pszCommand, "ClanTagChanged" ) ) + { + pPlayer->SetClanTag( pKeyValues->GetString( "tag", "" ) ); + + const char *teamName = "UNKNOWN"; + if ( pPlayer->GetTeam() ) + { + teamName = pPlayer->GetTeam()->GetName(); + } + UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"clantag\" (value \"%s\")\n", + pPlayer->GetPlayerName(), + pPlayer->GetUserID(), + pPlayer->GetNetworkIDString(), + teamName, + pKeyValues->GetString( "tag", "unknown" ) ); + } + } + } + + BaseClass::ClientCommandKeyValues( pEntity, pKeyValues ); + } + + //----------------------------------------------------------------------------- + // Purpose: Player has just spawned. Equip them. + //----------------------------------------------------------------------------- + void CCSGameRules::PlayerSpawn( CBasePlayer *pBasePlayer ) + { + CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer ); + if ( !pPlayer ) + Error( "PlayerSpawn" ); + + if ( pPlayer->State_Get() != STATE_ACTIVE ) + return; + + pPlayer->EquipSuit(); + + bool addDefault = true; + + CBaseEntity *pWeaponEntity = NULL; + while ( ( pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL ) + { + if ( addDefault ) + { + // remove all our weapons and armor before touching the first game_player_equip + pPlayer->RemoveAllItems( true ); + } + pWeaponEntity->Touch( pPlayer ); + addDefault = false; + } + + + if ( addDefault || pPlayer->m_bIsVIP ) + pPlayer->GiveDefaultItems(); + } + + void CCSGameRules::BroadcastSound( const char *sound, int team ) + { + CBroadcastRecipientFilter filter; + filter.MakeReliable(); + + if( team != -1 ) + { + filter.RemoveAllRecipients(); + filter.AddRecipientsByTeam( GetGlobalTeam(team) ); + } + + UserMessageBegin ( filter, "SendAudio" ); + WRITE_STRING( sound ); + MessageEnd(); + } + + //----------------------------------------------------------------------------- + // Purpose: Player has just spawned. Equip them. + //----------------------------------------------------------------------------- + + // return a multiplier that should adjust the damage done by a blast at position vecSrc to something at the position + // vecEnd. This will take into account the density of an entity that blocks the line of sight from one position to + // the other. + // + // this algorithm was taken from the HL2 version of RadiusDamage. + float CCSGameRules::GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pEntityToIgnore) + { + float retval = 0.0; + trace_t tr; + + UTIL_TraceLine(vecSrc, vecEnd, MASK_SHOT, pEntityToIgnore, COLLISION_GROUP_NONE, &tr); + if (tr.fraction == 1.0) + { + retval = 1.0; + } + else if (!(tr.DidHitWorld()) && (tr.m_pEnt != NULL) && (tr.m_pEnt != pEntityToIgnore) && (tr.m_pEnt->GetOwnerEntity() != pEntityToIgnore)) + { + // if we didn't hit world geometry perhaps there's still damage to be done here. + + CBaseEntity *blockingEntity = tr.m_pEnt; + + // check to see if this part of the player is visible if entities are ignored. + UTIL_TraceLine(vecSrc, vecEnd, CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &tr); + + if (tr.fraction == 1.0) + { + if ((blockingEntity != NULL) && (blockingEntity->VPhysicsGetObject() != NULL)) + { + int nMaterialIndex = blockingEntity->VPhysicsGetObject()->GetMaterialIndex(); + + float flDensity; + float flThickness; + float flFriction; + float flElasticity; + + physprops->GetPhysicsProperties( nMaterialIndex, &flDensity, + &flThickness, &flFriction, &flElasticity ); + + const float DENSITY_ABSORB_ALL_DAMAGE = 3000.0; + float scale = flDensity / DENSITY_ABSORB_ALL_DAMAGE; + if ((scale >= 0.0) && (scale < 1.0)) + { + retval = 1.0 - scale; + } + else if (scale < 0.0) + { + // should never happen, but just in case. + retval = 1.0; + } + } + else + { + retval = 0.75; // we're blocked by something that isn't an entity with a physics module or world geometry, just cut damage in half for now. + } + } + } + + return retval; + } + + // returns the percentage of the player that is visible from the given point in the world. + // return value is between 0 and 1. + float CCSGameRules::GetAmountOfEntityVisible(Vector & vecSrc, CBaseEntity *entity) + { + float retval = 0.0; + + const float damagePercentageChest = 0.40; + const float damagePercentageHead = 0.20; + const float damagePercentageFeet = 0.20; + const float damagePercentageRightSide = 0.10; + const float damagePercentageLeftSide = 0.10; + + if (!(entity->IsPlayer())) + { + // the entity is not a player, so the damage is all or nothing. + Vector vecTarget; + vecTarget = entity->BodyTarget(vecSrc, false); + + return GetExplosionDamageAdjustment(vecSrc, vecTarget, entity); + } + + CCSPlayer *player = (CCSPlayer *)entity; + + // check what parts of the player we can see from this point and modify the return value accordingly. + float chestHeightFromFeet; + + float armDistanceFromChest = HalfHumanWidth; + + // calculate positions of various points on the target player's body + Vector vecFeet = player->GetAbsOrigin(); + + Vector vecChest = player->BodyTarget(vecSrc, false); + chestHeightFromFeet = vecChest.z - vecFeet.z; // compute the distance from the chest to the feet. (this accounts for ducking and the like) + + Vector vecHead = player->GetAbsOrigin(); + vecHead.z += HumanHeight; + + Vector vecRightFacing; + AngleVectors(player->GetAbsAngles(), NULL, &vecRightFacing, NULL); + + vecRightFacing.NormalizeInPlace(); + vecRightFacing = vecRightFacing * armDistanceFromChest; + + Vector vecLeftSide = player->GetAbsOrigin(); + vecLeftSide.x -= vecRightFacing.x; + vecLeftSide.y -= vecRightFacing.y; + vecLeftSide.z += chestHeightFromFeet; + + Vector vecRightSide = player->GetAbsOrigin(); + vecRightSide.x += vecRightFacing.x; + vecRightSide.y += vecRightFacing.y; + vecRightSide.z += chestHeightFromFeet; + + // check chest + float damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecChest, entity); + retval += (damagePercentageChest * damageAdjustment); + + // check top of head + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecHead, entity); + retval += (damagePercentageHead * damageAdjustment); + + // check feet + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecFeet, entity); + retval += (damagePercentageFeet * damageAdjustment); + + // check left "edge" + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecLeftSide, entity); + retval += (damagePercentageLeftSide * damageAdjustment); + + // check right "edge" + damageAdjustment = GetExplosionDamageAdjustment(vecSrc, vecRightSide, entity); + retval += (damagePercentageRightSide * damageAdjustment); + + return retval; + } + + void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity * pEntityIgnore ) + { + RadiusDamage( info, vecSrcIn, flRadius, iClassIgnore, false ); + } + + // Add the ability to ignore the world trace + void CCSGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, bool bIgnoreWorld ) + { + CBaseEntity *pEntity = NULL; + trace_t tr; + float falloff, damagePercentage; + Vector vecSpot; + Vector vecToTarget; + Vector vecEndPos; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] The number of enemy players this explosion killed + int numberOfEnemyPlayersKilledByThisExplosion = 0; + + // [tj] who we award the achievement to if enough players are killed + CCSPlayer* pCSExplosionAttacker = ToCSPlayer(info.GetAttacker()); + + // [tj] used to determine which achievement to award for sufficient kills + CBaseEntity* pInflictor = info.GetInflictor(); + bool isGrenade = pInflictor && V_strcmp(pInflictor->GetClassname(), "hegrenade_projectile") == 0; + bool isBomb = pInflictor && V_strcmp(pInflictor->GetClassname(), "planted_c4") == 0; + + //============================================================================= + // HPE_END + //============================================================================= + + + vecEndPos.Init(); + + Vector vecSrc = vecSrcIn; + + damagePercentage = 1.0; + + if ( flRadius ) + falloff = info.GetDamage() / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) & MASK_WATER) ? true : false; + + vecSrc.z += 1;// in case grenade is lying on the ground + + // iterate on all entities in the vicinity. + for ( CEntitySphereQuery sphere( vecSrc, flRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + //============================================================================= + // HPE_BEGIN: + // [tj] We have to save whether or not the player is killed so we don't give credit + // for pre-dead players. + //============================================================================= + bool wasAliveBeforeExplosion = false; + CCSPlayer* pCSExplosionVictim = ToCSPlayer(pEntity); + if (pCSExplosionVictim) + { + wasAliveBeforeExplosion = pCSExplosionVictim->IsAlive(); + } + //============================================================================= + // HPE_END + //============================================================================= + if ( pEntity->m_takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blasts don't travel into or out of water + if ( !bIgnoreWorld ) + { + if (bInWater && pEntity->GetWaterLevel() == 0) + continue; + if (!bInWater && pEntity->GetWaterLevel() == 3) + continue; + } + + // radius damage can only be blocked by the world + vecSpot = pEntity->BodyTarget( vecSrc ); + + bool bHit = false; + + if( bIgnoreWorld ) + { + vecEndPos = vecSpot; + bHit = true; + } + else + { + // get the percentage of the target entity that is visible from the + // explosion position. + damagePercentage = GetAmountOfEntityVisible(vecSrc, pEntity); + if (damagePercentage > 0.0) + { + vecEndPos = vecSpot; + + bHit = true; + } + } + + if ( bHit ) + { + // the explosion can 'see' this entity, so hurt them! + //vecToTarget = ( vecSrc - vecEndPos ); + vecToTarget = ( vecEndPos - vecSrc ); + + // use a Gaussian function to describe the damage falloff over distance, with flRadius equal to 3 * sigma + // this results in the following values: + // + // Range Fraction Damage + // 0.0 100% + // 0.1 96% + // 0.2 84% + // 0.3 67% + // 0.4 49% + // 0.5 32% + // 0.6 20% + // 0.7 11% + // 0.8 6% + // 0.9 3% + // 1.0 1% + + float fDist = vecToTarget.Length(); + float fSigma = flRadius / 3.0f; // flRadius specifies 3rd standard deviation (0.0111 damage at this range) + float fGaussianFalloff = exp(-fDist * fDist / (2.0f * fSigma * fSigma)); + float flAdjustedDamage = info.GetDamage() * fGaussianFalloff * damagePercentage; + + if ( flAdjustedDamage > 0 ) + { + CTakeDamageInfo adjustedInfo = info; + adjustedInfo.SetDamage( flAdjustedDamage ); + + Vector dir = vecToTarget; + VectorNormalize( dir ); + + // If we don't have a damage force, manufacture one + if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin ) + { + CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc, 1.5 /* explosion scale! */ ); + } + else + { + // Assume the force passed in is the maximum force. Decay it based on falloff. + float flForce = adjustedInfo.GetDamageForce().Length() * falloff; + adjustedInfo.SetDamageForce( dir * flForce ); + adjustedInfo.SetDamagePosition( vecSrc ); + } + + Vector vecTarget; + vecTarget = pEntity->BodyTarget(vecSrc, false); + + UTIL_TraceLine(vecSrc, vecTarget, MASK_SHOT, NULL, COLLISION_GROUP_NONE, &tr); + + // blasts always hit chest + tr.hitgroup = HITGROUP_GENERIC; + + if (tr.fraction != 1.0) + { + // this has to be done to make breakable glass work. + ClearMultiDamage( ); + pEntity->DispatchTraceAttack( adjustedInfo, dir, &tr ); + ApplyMultiDamage(); + } + else + { + pEntity->TakeDamage( adjustedInfo ); + } + + // Now hit all triggers along the way that respond to damage... + pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, vecEndPos, dir ); + //============================================================================= + // HPE_BEGIN: + // [sbodenbender] Increment grenade damage stat + //============================================================================= + if (pCSExplosionVictim && pCSExplosionAttacker && isGrenade) + { + CCS_GameStats.IncrementStat(pCSExplosionAttacker, CSSTAT_GRENADE_DAMAGE, static_cast<int>(adjustedInfo.GetDamage())); + } + //============================================================================= + // HPE_END + //============================================================================= + } + } + } + + //============================================================================= + // HPE_BEGIN: + // [tj] Count up victims of area of effect damage for achievement purposes + //============================================================================= + + if (pCSExplosionVictim) + { + //If the bomb is exploding, set the attacker to the planter (we can't put this in the CTakeDamageInfo, since + //players aren't supposed to get credit for bomb kills) + if (isBomb) + { + CPlantedC4* bomb = static_cast<CPlantedC4*> (pInflictor); + if (bomb) + { + pCSExplosionAttacker = bomb->GetPlanter(); + } + } + + //Count check to make sure we killed an enemy player + if( pCSExplosionAttacker && + !pCSExplosionVictim->IsAlive() && + wasAliveBeforeExplosion && + pCSExplosionVictim->GetTeamNumber() != pCSExplosionAttacker->GetTeamNumber()) + { + numberOfEnemyPlayersKilledByThisExplosion++; + } + } + //============================================================================= + // HPE_END + //============================================================================= + + } + + //============================================================================= + // HPE_BEGIN: + // [tj] //Depending on which type of explosion it was, award the appropriate achievement. + //============================================================================= + + if (pCSExplosionAttacker && isGrenade && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::GrenadeMultiKill_MinKills) + { + pCSExplosionAttacker->AwardAchievement(CSGrenadeMultikill); + pCSExplosionAttacker->CheckMaxGrenadeKills(numberOfEnemyPlayersKilledByThisExplosion); + + } + if (pCSExplosionAttacker && isBomb && numberOfEnemyPlayersKilledByThisExplosion >= AchievementConsts::BombMultiKill_MinKills) + { + pCSExplosionAttacker->AwardAchievement(CSBombMultikill); + } + + //============================================================================= + // HPE_END + //============================================================================= + } + + //----------------------------------------------------------------------------- + // Purpose: + // Input : *pVictim - + // *pKiller - + // *pInflictor - + //----------------------------------------------------------------------------- + void CCSGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) + { + // Work out what killed the player, and send a message to all clients about it + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_ID = 0; + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); + CCSPlayer *pCSVictim = (CCSPlayer*)(pVictim); + + bool bHeadshot = false; + + if ( pScorer ) // Is the killer a client? + { + killer_ID = pScorer->GetUserID(); + + if( info.GetDamageType() & DMG_HEADSHOT ) + { + //to enable drawing the headshot icon as well as the weapon icon, + bHeadshot = true; + } + + if ( pInflictor ) + { + if ( pInflictor == pScorer ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + if ( pScorer->GetActiveWeapon() ) + { + killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); //GetDeathNoticeName(); + } + } + else + { + killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pInflictor->m_iClassname ); + } + + // strip the NPC_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + { + killer_weapon_name += 7; + } + else if ( strncmp( killer_weapon_name, "NPC_", 8 ) == 0 ) + { + killer_weapon_name += 8; + } + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + { + killer_weapon_name += 5; + } + else if( strncmp( killer_weapon_name, "hegrenade", 9 ) == 0 ) //"hegrenade_projectile" + { + killer_weapon_name = "hegrenade"; + } + else if( strncmp( killer_weapon_name, "flashbang", 9 ) == 0 ) //"flashbang_projectile" + { + killer_weapon_name = "flashbang"; + } + + IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); + + if ( event ) + { + event->SetInt("userid", pVictim->GetUserID() ); + event->SetInt("attacker", killer_ID ); + event->SetString("weapon", killer_weapon_name ); + event->SetInt("headshot", bHeadshot ? 1 : 0 ); + event->SetInt("priority", bHeadshot ? 8 : 7 ); // HLTV event priority, not transmitted + if ( pCSVictim->GetDeathFlags() & CS_DEATH_DOMINATION ) + { + event->SetInt( "dominated", 1 ); + } + else if ( pCSVictim->GetDeathFlags() & CS_DEATH_REVENGE ) + { + event->SetInt( "revenge", 1 ); + } + + gameeventmanager->FireEvent( event ); + } + } + + //========================================================= + //========================================================= + void CCSGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) + { + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); + CCSPlayer *pCSVictim = (CCSPlayer *)pVictim; + CCSPlayer *pCSScorer = (CCSPlayer *)pScorer; + + CCS_GameStats.PlayerKilled( pVictim, info ); + //============================================================================= + // HPE_BEGIN: + // [tj] Flag the round as non-lossless for the appropriate team. + // [menglish] Set the death flags depending on a nemesis system + //============================================================================= + + if (pVictim->GetTeamNumber() == TEAM_TERRORIST) + { + m_bNoTerroristsKilled = false; + m_bNoTerroristsDamaged = false; + } + if (pVictim->GetTeamNumber() == TEAM_CT) + { + m_bNoCTsKilled = false; + m_bNoCTsDamaged = false; + } + + m_bCanDonateWeapons = false; + + if ( m_pFirstKill == NULL && pCSScorer != pVictim ) + { + m_pFirstKill = pCSScorer; + m_firstKillTime = gpGlobals->curtime - m_fRoundStartTime; + } + + // determine if this kill affected a nemesis relationship + int iDeathFlags = 0; + if ( pScorer ) + { + CCS_GameStats.CalculateOverkill( pCSScorer, pCSVictim); + CCS_GameStats.CalcDominationAndRevenge( pCSScorer, pCSVictim, &iDeathFlags ); + } + pCSVictim->SetDeathFlags( iDeathFlags ); + //============================================================================= + // HPE_END + //============================================================================= + + // If we're killed by the C4, we do a subset of BaseClass::PlayerKilled() + // Specifically, we shouldn't lose any points or show death notices, to match goldsrc + if ( Q_strcmp(pKiller->GetClassname(), "planted_c4" ) == 0 ) + { + // dvsents2: uncomment when removing all FireTargets + // variant_t value; + // g_EventQueue.AddEvent( "game_playerdie", "Use", value, 0, pVictim, pVictim ); + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + } + else + { + BaseClass::PlayerKilled( pVictim, info ); + } + + // check for team-killing, and give monetary rewards/penalties + // Find the killer & the scorer + if ( !pScorer ) + return; + + if ( IPointsForKill( pScorer, pVictim ) < 0 ) + { + // team-killer! + pCSScorer->AddAccount( -3300 ); + ++pCSScorer->m_iTeamKills; + pCSScorer->m_bJustKilledTeammate = true; + + ClientPrint( pCSScorer, HUD_PRINTCENTER, "#Killed_Teammate" ); + if ( mp_autokick.GetBool() ) + { + char strTeamKills[64]; + Q_snprintf( strTeamKills, sizeof( strTeamKills ), "%d", pCSScorer->m_iTeamKills ); + ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Game_teammate_kills", strTeamKills ); // this includes a " of 3" in it + + if ( pCSScorer->m_iTeamKills >= 3 ) + { + ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" ); + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) ); + } + else if ( mp_spawnprotectiontime.GetInt() > 0 && GetRoundElapsedTime() < mp_spawnprotectiontime.GetInt() ) + { + ClientPrint( pCSScorer, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates" ); + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pCSScorer->GetUserID() ) ); + } + } + + if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) ) + { + pCSScorer->m_iDisplayHistoryBits |= DHF_FRIEND_KILLED; + pCSScorer->HintMessage( "#Hint_careful_around_teammates", false ); + } + } + else + { + //============================================================================= + // HPE_BEGIN: + // [tj] Added a check to make sure we don't get money for suicides. + //============================================================================= + if (pCSScorer != pCSVictim) + { + //============================================================================= + // HPE_END + //============================================================================= + if ( pCSVictim->IsVIP() ) + { + pCSScorer->HintMessage( "#Hint_reward_for_killing_vip", true ); + pCSScorer->AddAccount( 2500 ); + } + else + { + pCSScorer->AddAccount( 300 ); + } + } + + if ( !(pCSScorer->m_iDisplayHistoryBits & DHF_ENEMY_KILLED) ) + { + pCSScorer->m_iDisplayHistoryBits |= DHF_ENEMY_KILLED; + pCSScorer->HintMessage( "#Hint_win_round_by_killing_enemy", false ); + } + } + } + + + void CCSGameRules::InitDefaultAIRelationships() + { + // Allocate memory for default relationships + CBaseCombatCharacter::AllocateDefaultRelationships(); + + // -------------------------------------------------------------- + // First initialize table so we can report missing relationships + // -------------------------------------------------------------- + int i, j; + for (i=0;i<NUM_AI_CLASSES;i++) + { + for (j=0;j<NUM_AI_CLASSES;j++) + { + // By default all relationships are neutral of priority zero + CBaseCombatCharacter::SetDefaultRelationship( (Class_T)i, (Class_T)j, D_NU, 0 ); + } + } + } + + //------------------------------------------------------------------------------ + // Purpose : Return classify text for classify type + //------------------------------------------------------------------------------ + const char *CCSGameRules::AIClassText(int classType) + { + switch (classType) + { + case CLASS_NONE: return "CLASS_NONE"; + case CLASS_PLAYER: return "CLASS_PLAYER"; + default: return "MISSING CLASS in ClassifyText()"; + } + } + + //----------------------------------------------------------------------------- + // Purpose: When gaining new technologies in TF, prevent auto switching if we + // receive a weapon during the switch + // Input : *pPlayer - + // *pWeapon - + // Output : Returns true on success, false on failure. + //----------------------------------------------------------------------------- + bool CCSGameRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) + { + bool bIsBeingGivenItem = false; + CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer ); + if ( pCSPlayer && pCSPlayer->IsBeingGivenItem() ) + bIsBeingGivenItem = true; + + if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() && !bIsBeingGivenItem ) + { + // Player has an active item, so let's check cl_autowepswitch. + const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" ); + if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) + { + return false; + } + } + + if ( pPlayer->IsBot() && !bIsBeingGivenItem ) + { + return false; + } + + if ( !GetAllowWeaponSwitch() ) + { + return false; + } + + return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); + } + + //----------------------------------------------------------------------------- + // Purpose: + // Input : allow - + //----------------------------------------------------------------------------- + void CCSGameRules::SetAllowWeaponSwitch( bool allow ) + { + m_bAllowWeaponSwitch = allow; + } + + //----------------------------------------------------------------------------- + // Purpose: + // Output : Returns true on success, false on failure. + //----------------------------------------------------------------------------- + bool CCSGameRules::GetAllowWeaponSwitch() + { + return m_bAllowWeaponSwitch; + } + + //----------------------------------------------------------------------------- + // Purpose: + // Input : *pPlayer - + // Output : const char + //----------------------------------------------------------------------------- + const char *CCSGameRules::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) + { + Assert( pPlayer ); + return BaseClass::SetDefaultPlayerTeam( pPlayer ); + } + + + void CCSGameRules::LevelInitPreEntity() + { + BaseClass::LevelInitPreEntity(); + + // TODO for CZ-style hostages: TheHostageChatter->Precache(); + } + + + void CCSGameRules::LevelInitPostEntity() + { + BaseClass::LevelInitPostEntity(); + + m_bLevelInitialized = false; // re-count CT and T start spots now that they exist + + // Figure out from the entities in the map what kind of map this is (bomb run, prison escape, etc). + CheckMapConditions(); + } + + INetworkStringTable *g_StringTableBlackMarket = NULL; + + void CCSGameRules::CreateCustomNetworkStringTables( void ) + { + m_StringTableBlackMarket = g_StringTableBlackMarket; + + if ( 0 )//mp_dynamicpricing.GetBool() ) + { + m_bBlackMarket = BlackMarket_DownloadPrices(); + + if ( m_bBlackMarket == false ) + { + Msg( "ERROR: mp_dynamicpricing set to 1 but couldn't download the price list!\n" ); + } + } + else + { + m_bBlackMarket = false; + SetBlackMarketPrices( true ); + } + } + + float CCSGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) + { + float fFallVelocity = pPlayer->m_Local.m_flFallVelocity - CS_PLAYER_MAX_SAFE_FALL_SPEED; + float fallDamage = fFallVelocity * CS_DAMAGE_FOR_FALL_SPEED * 1.25; + + if ( fallDamage > 0.0f ) + { + // let the bots know + IGameEvent * event = gameeventmanager->CreateEvent( "player_falldamage" ); + if ( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetFloat( "damage", fallDamage ); + event->SetInt( "priority", 4 ); // HLTV event priority, not transmitted + + gameeventmanager->FireEvent( event ); + } + } + + return fallDamage; + } + + + void CCSGameRules::ClientDisconnected( edict_t *pClient ) + { + BaseClass::ClientDisconnected( pClient ); + + //============================================================================= + // HPE_BEGIN: + // [tj] Clear domination data when a player disconnects + //============================================================================= + + CCSPlayer *pPlayer = ToCSPlayer( GetContainingEntity( pClient ) ); + if ( pPlayer ) + { + pPlayer->RemoveNemesisRelationships(); + } + + //============================================================================= + // HPE_END + //============================================================================= + + + CheckWinConditions(); + } + + + // Called when game rules are destroyed by CWorld + void CCSGameRules::LevelShutdown() + { + int iLevelIndex = GetCSLevelIndex( STRING( gpGlobals->mapname ) ); + + if ( iLevelIndex != -1 ) + { + g_iTerroristVictories[iLevelIndex] += m_iNumTerroristWins; + g_iCounterTVictories[iLevelIndex] += m_iNumCTWins; + } + + BaseClass::LevelShutdown(); + } + + + //--------------------------------------------------------------------------------------------------- + /** + * Check if the scenario has been won/lost. + * Return true if the scenario is over, false if the scenario is still in progress + */ + bool CCSGameRules::CheckWinConditions( void ) + { + if ( mp_ignore_round_win_conditions.GetBool() ) + { + return false; + } + + // If a winner has already been determined.. then get the heck out of here + if (m_iRoundWinStatus != WINNER_NONE) + { + // still check if we lost players to where we need to do a full reset next round... + int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; + InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); + + bool bNeededPlayers = false; + NeededPlayersCheck( bNeededPlayers ); + + return true; + } + + // Initialize the player counts.. + int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; + InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); + + + /***************************** OTHER PLAYER's CHECK *********************************************************/ + bool bNeededPlayers = false; + if ( NeededPlayersCheck( bNeededPlayers ) ) + return false; + + /****************************** ASSASINATION/VIP SCENARIO CHECK *******************************************************/ + if ( VIPRoundEndCheck( bNeededPlayers ) ) + return true; + + /****************************** PRISON ESCAPE CHECK *******************************************************/ + if ( PrisonRoundEndCheck() ) + return true; + + + /****************************** BOMB CHECK ********************************************************/ + if ( BombRoundEndCheck( bNeededPlayers ) ) + return true; + + + /***************************** TEAM EXTERMINATION CHECK!! *********************************************************/ + // CounterTerrorists won by virture of elimination + if ( TeamExterminationCheck( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT, bNeededPlayers ) ) + return true; + + + /******************************** HOSTAGE RESCUE CHECK ******************************************************/ + if ( HostageRescueRoundEndCheck( bNeededPlayers ) ) + return true; + + // scenario not won - still in progress + return false; + } + + + bool CCSGameRules::NeededPlayersCheck( bool &bNeededPlayers ) + { + // We needed players to start scoring + // Do we have them now? + if( !m_iNumSpawnableTerrorist || !m_iNumSpawnableCT ) + { + Msg( "Game will not start until both teams have players.\n" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_scoring" ); + bNeededPlayers = true; + + m_bFirstConnected = false; + } + + if ( !m_bFirstConnected && m_iNumSpawnableTerrorist && m_iNumSpawnableCT ) + { + // Start the round immediately when the first person joins + // UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" ); + + m_bFreezePeriod = false; //Make sure we are not on the FreezePeriod. + m_bCompleteReset = true; + + TerminateRound( 3.0f, Game_Commencing ); + m_bFirstConnected = true; + return true; + } + + return false; + } + + + void CCSGameRules::InitializePlayerCounts( + int &NumAliveTerrorist, + int &NumAliveCT, + int &NumDeadTerrorist, + int &NumDeadCT + ) + { + NumAliveTerrorist = NumAliveCT = NumDeadCT = NumDeadTerrorist = 0; + m_iNumTerrorist = m_iNumCT = m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0; + m_iHaveEscaped = 0; + + // Count how many dead players there are on each team. + for ( int iTeam=0; iTeam < GetNumberOfTeams(); iTeam++ ) + { + CTeam *pTeam = GetGlobalTeam( iTeam ); + + for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); + Assert( pPlayer ); + if ( !pPlayer ) + continue; + + Assert( pPlayer->GetTeamNumber() == pTeam->GetTeamNumber() ); + + switch ( pTeam->GetTeamNumber() ) + { + case TEAM_CT: + m_iNumCT++; + + if ( pPlayer->State_Get() != STATE_PICKINGCLASS ) + m_iNumSpawnableCT++; + + if ( pPlayer->m_lifeState != LIFE_ALIVE ) + NumDeadCT++; + else + NumAliveCT++; + + break; + + case TEAM_TERRORIST: + m_iNumTerrorist++; + + if ( pPlayer->State_Get() != STATE_PICKINGCLASS ) + m_iNumSpawnableTerrorist++; + + if ( pPlayer->m_lifeState != LIFE_ALIVE ) + NumDeadTerrorist++; + else + NumAliveTerrorist++; + + // Check to see if this guy escaped. + if ( pPlayer->m_bEscaped == true ) + m_iHaveEscaped++; + + break; + } + } + } + } + + bool CCSGameRules::HostageRescueRoundEndCheck( bool bNeededPlayers ) + { + // Check to see if 50% of the hostages have been rescued. + CHostage* hostage = NULL; + + int iNumHostages = g_Hostages.Count(); + int iNumLeftToRescue = 0; + int i; + + for ( i=0; i<iNumHostages; i++ ) + { + hostage = g_Hostages[i]; + + if ( hostage->m_iHealth > 0 && !hostage->IsRescued() ) // We've found a live hostage. don't end the round + iNumLeftToRescue++; + } + + m_iHostagesRemaining = iNumLeftToRescue; + + if ( (iNumLeftToRescue == 0) && (iNumHostages > 0) ) + { + if ( m_iHostagesRescued >= (iNumHostages * 0.5) ) + { + m_iAccountCT += 2500; + + if ( !bNeededPlayers ) + { + m_iNumCTWins ++; + // Update the clients team score + UpdateTeamScores(); + } + CCS_GameStats.Event_AllHostagesRescued(); + // tell the bots all the hostages have been rescued + IGameEvent * event = gameeventmanager->CreateEvent( "hostage_rescued_all" ); + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), All_Hostages_Rescued ); + return true; + } + } + + return false; + } + + + bool CCSGameRules::PrisonRoundEndCheck() + { + //MIKETODO: get this working when working on prison escape + /* + if (m_bMapHasEscapeZone == true) + { + float flEscapeRatio; + + flEscapeRatio = (float) m_iHaveEscaped / (float) m_iNumEscapers; + + if (flEscapeRatio >= m_flRequiredEscapeRatio) + { + BroadcastSound( "Event.TERWin" ); + m_iAccountTerrorist += 3150; + + if ( !bNeededPlayers ) + { + m_iNumTerroristWins ++; + // Update the clients team score + UpdateTeamScores(); + } + EndRoundMessage( "#Terrorists_Escaped", Terrorists_Escaped ); + TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_TER ); + return; + } + else if ( NumAliveTerrorist == 0 && flEscapeRatio < m_flRequiredEscapeRatio) + { + BroadcastSound( "Event.CTWin" ); + m_iAccountCT += (1 - flEscapeRatio) * 3500; // CTs are rewarded based on how many terrorists have escaped... + + if ( !bNeededPlayers ) + { + m_iNumCTWins++; + // Update the clients team score + UpdateTeamScores(); + } + EndRoundMessage( "#CTs_PreventEscape", CTs_PreventEscape ); + TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_CT ); + return; + } + + else if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 ) + { + BroadcastSound( "Event.CTWin" ); + m_iAccountCT += (1 - flEscapeRatio) * 3250; // CTs are rewarded based on how many terrorists have escaped... + + if ( !bNeededPlayers ) + { + m_iNumCTWins++; + // Update the clients team score + UpdateTeamScores(); + } + EndRoundMessage( "#Escaping_Terrorists_Neutralized", Escaping_Terrorists_Neutralized ); + TerminateRound( mp_round_restart_delay.GetFloat(), WINNER_CT ); + return; + } + // else return; + } + */ + + return false; + } + + + bool CCSGameRules::VIPRoundEndCheck( bool bNeededPlayers ) + { + if (m_iMapHasVIPSafetyZone != 1) + return false; + + if (m_pVIP == NULL) + return false; + + if (m_pVIP->m_bEscaped == true) + { + m_iAccountCT += 3500; + + if ( !bNeededPlayers ) + { + m_iNumCTWins ++; + // Update the clients team score + UpdateTeamScores(); + } + + //MIKETODO: get this working when working on VIP scenarios + /* + MessageBegin( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // VIP rescued + WRITE_SHORT( ENTINDEX(m_pVIP->edict()) ); // index number of primary entity + WRITE_SHORT( 0 ); // index number of secondary entity + WRITE_LONG( 15 | DRC_FLAG_FINAL); // eventflags (priority and flags) + MessageEnd(); + */ + + // tell the bots the VIP got out + IGameEvent * event = gameeventmanager->CreateEvent( "vip_escaped" ); + if ( event ) + { + event->SetInt( "userid", m_pVIP->GetUserID() ); + event->SetInt( "priority", 9 ); + gameeventmanager->FireEvent( event ); + } + + //============================================================================= + // HPE_BEGIN: + // [menglish] If the VIP has escaped award him an MVP + //============================================================================= + + m_pVIP->IncrementNumMVPs( CSMVP_UNDEFINED ); + + //============================================================================= + // HPE_END + //============================================================================= + + TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Escaped ); + return true; + } + else if ( m_pVIP->m_lifeState == LIFE_DEAD ) // The VIP is dead + { + m_iAccountTerrorist += 3250; + + if ( !bNeededPlayers ) + { + m_iNumTerroristWins ++; + // Update the clients team score + UpdateTeamScores(); + } + + // tell the bots the VIP was killed + IGameEvent * event = gameeventmanager->CreateEvent( "vip_killed" ); + if ( event ) + { + event->SetInt( "userid", m_pVIP->GetUserID() ); + event->SetInt( "priority", 9 ); + gameeventmanager->FireEvent( event ); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Assassinated ); + return true; + } + + return false; + } + + + bool CCSGameRules::BombRoundEndCheck( bool bNeededPlayers ) + { + // Check to see if the bomb target was hit or the bomb defused.. if so, then let's end the round! + if ( ( m_bTargetBombed == true ) && ( m_bMapHasBombTarget == true ) ) + { + m_iAccountTerrorist += 3500; + + if ( !bNeededPlayers ) + { + m_iNumTerroristWins ++; + // Update the clients team score + UpdateTeamScores(); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), Target_Bombed ); + return true; + } + else + if ( ( m_bBombDefused == true ) && ( m_bMapHasBombTarget == true ) ) + { + m_iAccountCT += 3250; + + m_iAccountTerrorist += 800; // give the T's a little bonus for planting the bomb even though it was defused. + + if ( !bNeededPlayers ) + { + m_iNumCTWins++; + // Update the clients team score + UpdateTeamScores(); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), Bomb_Defused ); + return true; + } + + return false; + } + + + bool CCSGameRules::TeamExterminationCheck( + int NumAliveTerrorist, + int NumAliveCT, + int NumDeadTerrorist, + int NumDeadCT, + bool bNeededPlayers + ) + { + if ( ( m_iNumCT > 0 && m_iNumSpawnableCT > 0 ) && ( m_iNumTerrorist > 0 && m_iNumSpawnableTerrorist > 0 ) ) + { + if ( NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0 ) + { + bool nowin = false; + + for ( int iGrenade=0; iGrenade < g_PlantedC4s.Count(); iGrenade++ ) + { + CPlantedC4 *pC4 = g_PlantedC4s[iGrenade]; + + if ( pC4->IsBombActive() ) + nowin = true; + } + + if ( !nowin ) + { + if ( m_bMapHasBombTarget ) + m_iAccountCT += 3250; + else + m_iAccountCT += 3000; + + if ( !bNeededPlayers ) + { + m_iNumCTWins++; + // Update the clients team score + UpdateTeamScores(); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), CTs_Win ); + return true; + } + } + + // Terrorists WON + if ( NumAliveCT == 0 && NumDeadCT != 0 && m_iNumSpawnableTerrorist > 0 ) + { + if ( m_bMapHasBombTarget ) + m_iAccountTerrorist += 3250; + else + m_iAccountTerrorist += 3000; + + if ( !bNeededPlayers ) + { + m_iNumTerroristWins++; + // Update the clients team score + UpdateTeamScores(); + } + + TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Win ); + return true; + } + } + else if ( NumAliveCT == 0 && NumAliveTerrorist == 0 ) + { + TerminateRound( mp_round_restart_delay.GetFloat(), Round_Draw ); + return true; + } + + return false; + } + + + void CCSGameRules::PickNextVIP() + { + // MIKETODO: work on this when getting VIP maps running. + /* + if (IsVIPQueueEmpty() != true) + { + // Remove the current VIP from his VIP status and make him a regular CT. + if (m_pVIP != NULL) + ResetCurrentVIP(); + + for (int i = 0; i <= 4; i++) + { + if (VIPQueue[i] != NULL) + { + m_pVIP = VIPQueue[i]; + m_pVIP->MakeVIP(); + + VIPQueue[i] = NULL; // remove this player from the VIP queue + StackVIPQueue(); // and re-organize the queue + m_iConsecutiveVIP = 0; + return; + } + } + } + else if (m_iConsecutiveVIP >= 3) // If it's been the same VIP for 3 rounds already.. then randomly pick a new one + { + m_iLastPick++; + + if (m_iLastPick > m_iNumCT) + m_iLastPick = 1; + + int iCount = 1; + + CBaseEntity* pPlayer = NULL; + CBasePlayer* player = NULL; + CBasePlayer* pLastPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) ) + { + if ( !(pPlayer->pev->flags & FL_DORMANT) ) + { + player = GetClassPtr((CBasePlayer *)pPlayer->pev); + + if ( (player->m_iTeam == CT) && (iCount == m_iLastPick) ) + { + if ( (player == m_pVIP) && (pLastPlayer != NULL) ) + player = pLastPlayer; + + // Remove the current VIP from his VIP status and make him a regular CT. + if (m_pVIP != NULL) + ResetCurrentVIP(); + + player->MakeVIP(); + m_iConsecutiveVIP = 0; + + return; + } + else if ( player->m_iTeam == CT ) + iCount++; + + if ( player->m_iTeam != SPECTATOR ) + pLastPlayer = player; + } + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + } + } + else if (m_pVIP == NULL) // There is no VIP and there is no one waiting to be the VIP.. therefore just pick the first CT player we can find. + { + CBaseEntity* pPlayer = NULL; + CBasePlayer* player = NULL; + + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) ) + { + if ( pPlayer->pev->flags != FL_DORMANT ) + { + player = GetClassPtr((CBasePlayer *)pPlayer->pev); + + if ( player->m_iTeam == CT ) + { + player->MakeVIP(); + m_iConsecutiveVIP = 0; + return; + } + } + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + } + } + */ + } + + + void CCSGameRules::ReadMultiplayCvars() + { + m_iRoundTime = (int)(mp_roundtime.GetFloat() * 60); + m_iFreezeTime = mp_freezetime.GetInt(); + } + + + void CCSGameRules::RestartRound() + { +#if defined( REPLAY_ENABLED ) + if ( g_pReplay ) + { + // Write replay and stop recording if appropriate + if ( g_pReplay->IsRecording() ) + { + g_pReplay->SV_EndRecordingSession(); + } + + int nActivePlayerCount = m_iNumTerrorist + m_iNumCT; + if ( nActivePlayerCount && g_pReplay->SV_ShouldBeginRecording( false ) ) + { + // Tell the replay manager that it should begin recording the new round as soon as possible + g_pReplay->SV_GetContext()->GetSessionRecorder()->StartRecording(); + } + } +#endif + //============================================================================= + // HPE_BEGIN: + // [tj] Notify players that the round is about to be reset + //============================================================================= + for ( int clientIndex = 1; clientIndex <= gpGlobals->maxClients; clientIndex++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( clientIndex ); + if(pPlayer) + { + pPlayer->OnPreResetRound(); + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + if ( !IsFinite( gpGlobals->curtime ) ) + { + Warning( "NaN curtime in RestartRound\n" ); + gpGlobals->curtime = 0.0f; + } + + int i; + + m_iTotalRoundsPlayed++; + + //ClearBodyQue(); + + // Hardlock the player accelaration to 5.0 + //CVAR_SET_FLOAT( "sv_accelerate", 5.0 ); + //CVAR_SET_FLOAT( "sv_friction", 4.0 ); + //CVAR_SET_FLOAT( "sv_stopspeed", 75 ); + + sv_stopspeed.SetValue( 75.0f ); + + // Tabulate the number of players on each team. + int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; + InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); + + m_bBombDropped = false; + m_bBombPlanted = false; + + if ( GetHumanTeam() != TEAM_UNASSIGNED ) + { + MoveHumansToHumanTeam(); + } + + /*************** AUTO-BALANCE CODE *************/ + if ( mp_autoteambalance.GetInt() != 0 && + (m_iUnBalancedRounds >= 1) ) + { + if ( GetHumanTeam() == TEAM_UNASSIGNED ) + { + BalanceTeams(); + } + } + + if ( ((m_iNumSpawnableCT - m_iNumSpawnableTerrorist) >= 2) || + ((m_iNumSpawnableTerrorist - m_iNumSpawnableCT) >= 2) ) + { + m_iUnBalancedRounds++; + } + else + { + m_iUnBalancedRounds = 0; + } + + // Warn the players of an impending auto-balance next round... + if ( mp_autoteambalance.GetInt() != 0 && + (m_iUnBalancedRounds == 1) ) + { + if ( GetHumanTeam() == TEAM_UNASSIGNED ) + { + UTIL_ClientPrintAll( HUD_PRINTCENTER,"#Auto_Team_Balance_Next_Round"); + } + } + + /*************** AUTO-BALANCE CODE *************/ + + if ( m_bCompleteReset ) + { + // bounds check + if ( mp_timelimit.GetInt() < 0 ) + { + mp_timelimit.SetValue( 0 ); + } + m_flGameStartTime = gpGlobals->curtime; + if ( !IsFinite( m_flGameStartTime.Get() ) ) + { + Warning( "Trying to set a NaN game start time\n" ); + m_flGameStartTime.GetForModify() = 0.0f; + } + + // Reset total # of rounds played + m_iTotalRoundsPlayed = 0; + + // Reset score info + m_iNumTerroristWins = 0; + m_iNumCTWins = 0; + m_iNumConsecutiveTerroristLoses = 0; + m_iNumConsecutiveCTLoses = 0; + + + // Reset team scores + UpdateTeamScores(); + + + // Reset the player stats + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = CCSPlayer::Instance( i ); + + if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) + pPlayer->Reset(); + } + } + + m_bFreezePeriod = true; + + ReadMultiplayCvars(); + + // Check to see if there's a mapping info paramater entity + if ( g_pMapInfo ) + { + switch ( g_pMapInfo->m_iBuyingStatus ) + { + case 0: + m_bCTCantBuy = false; + m_bTCantBuy = false; + Msg( "EVERYONE CAN BUY!\n" ); + break; + + case 1: + m_bCTCantBuy = false; + m_bTCantBuy = true; + Msg( "Only CT's can buy!!\n" ); + break; + + case 2: + m_bCTCantBuy = true; + m_bTCantBuy = false; + Msg( "Only T's can buy!!\n" ); + break; + + case 3: + m_bCTCantBuy = true; + m_bTCantBuy = true; + Msg( "No one can buy!!\n" ); + break; + + default: + m_bCTCantBuy = false; + m_bTCantBuy = false; + break; + } + } + else + { + // by default everyone can buy + m_bCTCantBuy = false; + m_bTCantBuy = false; + } + + + // Check to see if this map has a bomb target in it + + if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) ) + { + m_bMapHasBombTarget = true; + m_bMapHasBombZone = true; + } + else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) ) + { + m_bMapHasBombTarget = true; + m_bMapHasBombZone = false; + } + else + { + m_bMapHasBombTarget = false; + m_bMapHasBombZone = false; + } + + // Check to see if this map has hostage rescue zones + + if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) ) + m_bMapHasRescueZone = true; + else + m_bMapHasRescueZone = false; + + + // See if the map has func_buyzone entities + // Used by CBasePlayer::HandleSignals() to support maps without these entities + + if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) ) + m_bMapHasBuyZone = true; + else + m_bMapHasBuyZone = false; + + + // GOOSEMAN : See if this map has func_escapezone entities + if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) ) + { + m_bMapHasEscapeZone = true; + m_iHaveEscaped = 0; + m_iNumEscapers = 0; // Will increase this later when we count how many Ts are starting + if (m_iNumEscapeRounds >= 3) + { + SwapAllPlayers(); + m_iNumEscapeRounds = 0; + } + + m_iNumEscapeRounds++; // Increment the number of rounds played... After 8 rounds, the players will do a whole sale switch.. + } + else + m_bMapHasEscapeZone = false; + + // Check to see if this map has VIP safety zones + if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) ) + { + PickNextVIP(); + m_iConsecutiveVIP++; + m_iMapHasVIPSafetyZone = 1; + } + else + m_iMapHasVIPSafetyZone = 2; + + // Update accounts based on number of hostages remaining.. + int iRescuedHostageBonus = 0; + + for ( int iHostage=0; iHostage < g_Hostages.Count(); iHostage++ ) + { + CHostage *pHostage = g_Hostages[iHostage]; + + if( pHostage->IsRescuable() ) //Alive and not rescued + { + iRescuedHostageBonus += 150; + } + + if ( iRescuedHostageBonus >= 2000 ) + break; + } + + //*******Catch up code by SupraFiend. Scale up the loser bonus when teams fall into losing streaks + if (m_iRoundWinStatus == WINNER_TER) // terrorists won + { + //check to see if they just broke a losing streak + if(m_iNumConsecutiveTerroristLoses > 1) + m_iLoserBonus = 1500;//this is the default losing bonus + + m_iNumConsecutiveTerroristLoses = 0;//starting fresh + m_iNumConsecutiveCTLoses++;//increment the number of wins the CTs have had + } + else if (m_iRoundWinStatus == WINNER_CT) // CT Won + { + //check to see if they just broke a losing streak + if(m_iNumConsecutiveCTLoses > 1) + m_iLoserBonus = 1500;//this is the default losing bonus + + m_iNumConsecutiveCTLoses = 0;//starting fresh + m_iNumConsecutiveTerroristLoses++;//increment the number of wins the Terrorists have had + } + + //check if the losing team is in a losing streak & that the loser bonus hasen't maxed out. + if((m_iNumConsecutiveTerroristLoses > 1) && (m_iLoserBonus < 3000)) + m_iLoserBonus += 500;//help out the team in the losing streak + else + if((m_iNumConsecutiveCTLoses > 1) && (m_iLoserBonus < 3000)) + m_iLoserBonus += 500;//help out the team in the losing streak + + // assign the wining and losing bonuses + if (m_iRoundWinStatus == WINNER_TER) // terrorists won + { + m_iAccountTerrorist += iRescuedHostageBonus; + m_iAccountCT += m_iLoserBonus; + } + else if (m_iRoundWinStatus == WINNER_CT) // CT Won + { + m_iAccountCT += iRescuedHostageBonus; + if (m_bMapHasEscapeZone == false) // only give them the bonus if this isn't an escape map + m_iAccountTerrorist += m_iLoserBonus; + } + + + //Update CT account based on number of hostages rescued + m_iAccountCT += m_iHostagesRescued * 750; + + + // Update individual players accounts and respawn players + + //**********new code by SupraFiend + //##########code changed by MartinO + //the round time stamp must be set before players are spawned + m_fRoundStartTime = gpGlobals->curtime + m_iFreezeTime; + + if ( !IsFinite( m_fRoundStartTime.Get() ) ) + { + Warning( "Trying to set a NaN round start time\n" ); + m_fRoundStartTime.GetForModify() = 0.0f; + } + + //Adrian - No cash for anyone at first rounds! ( well, only the default. ) + if ( m_bCompleteReset ) + { + m_iAccountTerrorist = m_iAccountCT = 0; //No extra cash!. + + //We are starting fresh. So it's like no one has ever won or lost. + m_iNumTerroristWins = 0; + m_iNumCTWins = 0; + m_iNumConsecutiveTerroristLoses = 0; + m_iNumConsecutiveCTLoses = 0; + m_iLoserBonus = 1400; + } + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + pPlayer->m_iNumSpawns = 0; + pPlayer->m_bTeamChanged = false; + + if ( pPlayer->GetTeamNumber() == TEAM_CT ) + { + if (pPlayer->DoesPlayerGetRoundStartMoney()) + { + pPlayer->AddAccount( m_iAccountCT ); + } + } + else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) + { + m_iNumEscapers++; // Add another potential escaper to the mix! + if (pPlayer->DoesPlayerGetRoundStartMoney()) + { + pPlayer->AddAccount( m_iAccountTerrorist ); + } + } + + // tricky, make players non solid while moving to their spawn points + if ( (pPlayer->GetTeamNumber() == TEAM_CT) || (pPlayer->GetTeamNumber() == TEAM_TERRORIST) ) + { + pPlayer->AddSolidFlags( FSOLID_NOT_SOLID ); + } + } + + //============================================================================= + // HPE_BEGIN: + // [tj] Keep track of number of players per side and if they have the same uniform + //============================================================================= + + int terroristUniform = -1; + bool allTerroristsWearingSameUniform = true; + int numberOfTerrorists = 0; + int ctUniform = -1; + bool allCtsWearingSameUniform = true; + int numberOfCts = 0; + + //============================================================================= + // HPE_END + //============================================================================= + + // know respawn all players + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS ) + { + //============================================================================= + // HPE_BEGIN: + // [tj] Increment CT count and check CT uniforms. + //============================================================================= + + numberOfCts++; + if (ctUniform == -1) + { + ctUniform = pPlayer->PlayerClass(); + } + else if (pPlayer->PlayerClass() != ctUniform) + { + allCtsWearingSameUniform = false; + } + + //============================================================================= + // HPE_END + //============================================================================= + + pPlayer->RoundRespawn(); + } + + if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS ) + { + //============================================================================= + // HPE_BEGIN: + // [tj] Increment terrorist count and check terrorist uniforms + //============================================================================= + + numberOfTerrorists++; + if (terroristUniform == -1) + { + terroristUniform = pPlayer->PlayerClass(); + } + else if (pPlayer->PlayerClass() != terroristUniform) + { + allTerroristsWearingSameUniform = false; + } + + //============================================================================= + // HPE_END + //============================================================================= + + pPlayer->RoundRespawn(); + } + else + { + pPlayer->ObserverRoundRespawn(); + } + + if ( pPlayer->m_iAccount > pPlayer->m_iShouldHaveCash ) + { + m_bDontUploadStats = true; + } + } + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] Award same uniform achievement for qualifying teams + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetTeamNumber() == TEAM_CT && allCtsWearingSameUniform && numberOfCts >= AchievementConsts::SameUniform_MinPlayers) + { + pPlayer->AwardAchievement(CSSameUniform); + } + + if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && allTerroristsWearingSameUniform && numberOfTerrorists >= AchievementConsts::SameUniform_MinPlayers) + { + pPlayer->AwardAchievement(CSSameUniform); + } + } + + // [menglish] reset per-round achievement variables for each player + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + if( pPlayer ) + { + pPlayer->ResetRoundBasedAchievementVariables(); + } + } + + // [pfreese] Reset all round or match stats, depending on type of restart + if ( m_bCompleteReset ) + { + CCS_GameStats.ResetAllStats(); + CCS_GameStats.ResetPlayerClassMatchStats(); + } + else + { + CCS_GameStats.ResetRoundStats(); + } + + //============================================================================= + // HPE_END + //============================================================================= + + // Respawn entities (glass, doors, etc..) + CleanUpMap(); + + // now run a tkpunish check, after the map has been cleaned up + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetTeamNumber() == TEAM_CT && pPlayer->PlayerClass() >= FIRST_CT_CLASS && pPlayer->PlayerClass() <= LAST_CT_CLASS ) + { + pPlayer->CheckTKPunishment(); + } + if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->PlayerClass() >= FIRST_T_CLASS && pPlayer->PlayerClass() <= LAST_T_CLASS ) + { + pPlayer->CheckTKPunishment(); + } + } + + // Give C4 to the terrorists + if (m_bMapHasBombTarget == true ) + GiveC4(); + + // Reset game variables + m_flIntermissionEndTime = 0; + m_flRestartRoundTime = 0.0; + m_iAccountTerrorist = m_iAccountCT = 0; + m_iHostagesRescued = 0; + m_iHostagesTouched = 0; + + //============================================================================= + // HPE_BEGIN + // [dwenger] Reset rescue-related achievement values + //============================================================================= + + // [tj] reset flawless and lossless round related flags + m_bNoTerroristsKilled = true; + m_bNoCTsKilled = true; + m_bNoTerroristsDamaged = true; + m_bNoCTsDamaged = true; + m_pFirstKill = NULL; + m_pFirstBlood = NULL; + + m_bCanDonateWeapons = true; + + // [dwenger] Reset rescue-related achievement values + m_iHostagesRemaining = 0; + m_pLastRescuer = NULL; + + m_hostageWasInjured = false; + m_hostageWasKilled = false; + + //============================================================================= + // HPE_END + //============================================================================= + + m_iNumRescuers = 0; + m_iRoundWinStatus = WINNER_NONE; + m_bTargetBombed = m_bBombDefused = false; + m_bCompleteReset = false; + m_flNextHostageAnnouncement = gpGlobals->curtime; + + m_iHostagesRemaining = g_Hostages.Count(); + + // fire global game event + IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); + if ( event ) + { + event->SetInt("timelimit", m_iRoundTime ); + event->SetInt("fraglimit", 0 ); + event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted + + if ( m_bMapHasRescueZone ) + { + event->SetString("objective","HOSTAGE RESCUE"); + } + else if ( m_bMapHasEscapeZone ) + { + event->SetString("objective","PRISON ESCAPE"); + } + else if ( m_iMapHasVIPSafetyZone == 1 ) + { + event->SetString("objective","VIP RESCUE"); + } + else if ( m_bMapHasBombTarget || m_bMapHasBombZone ) + { + event->SetString("objective","BOMB TARGET"); + } + else + { + event->SetString("objective","DEATHMATCH"); + } + + gameeventmanager->FireEvent( event ); + } + + UploadGameStats(); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] I commented out this call to CreateWeaponManager, as the + // CGameWeaponManager object doesn't appear to be actually used by the CSS + // code, and in any case, the weapon manager does not support wildcards in + // entity names (as seemingly indicated) below. When the manager fails to + // create its factory, it removes itself in any case. + //============================================================================= + + // CreateWeaponManager( "weapon_*", gpGlobals->maxClients * 2 ); + + //============================================================================= + // HPE_END + //============================================================================= + } + + void CCSGameRules::GiveC4() + { + enum { + ALL_TERRORISTS = 0, + HUMAN_TERRORISTS, + }; + int iTerrorists[2][ABSOLUTE_PLAYER_LIMIT]; + int numAliveTs[2] = { 0, 0 }; + int lastBombGuyIndex[2] = { -1, -1 }; + + //Create an array of the indeces of bomb carrier candidates + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); + + if( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == TEAM_TERRORIST && numAliveTs[ALL_TERRORISTS] < ABSOLUTE_PLAYER_LIMIT ) + { + if ( pPlayer == m_pLastBombGuy ) + { + lastBombGuyIndex[ALL_TERRORISTS] = numAliveTs[ALL_TERRORISTS]; + lastBombGuyIndex[HUMAN_TERRORISTS] = numAliveTs[HUMAN_TERRORISTS]; + } + + iTerrorists[ALL_TERRORISTS][numAliveTs[ALL_TERRORISTS]] = i; + numAliveTs[ALL_TERRORISTS]++; + if ( !pPlayer->IsBot() ) + { + iTerrorists[HUMAN_TERRORISTS][numAliveTs[HUMAN_TERRORISTS]] = i; + numAliveTs[HUMAN_TERRORISTS]++; + } + } + } + + int which = cv_bot_defer_to_human.GetBool(); + if ( numAliveTs[HUMAN_TERRORISTS] == 0 ) + { + which = ALL_TERRORISTS; + } + + //pick one of the candidates randomly + if( numAliveTs[which] > 0 ) + { + int index = random->RandomInt(0,numAliveTs[which]-1); + if ( lastBombGuyIndex[which] >= 0 ) + { + // give the C4 sequentially + index = (lastBombGuyIndex[which] + 1) % numAliveTs[which]; + } + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iTerrorists[which][index] ) ); + + Assert( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST && pPlayer->IsAlive() ); + + pPlayer->GiveNamedItem( WEAPON_C4_CLASSNAME ); + m_pLastBombGuy = pPlayer; + + //pPlayer->SetBombIcon(); + //pPlayer->pev->body = 1; + + pPlayer->m_iDisplayHistoryBits |= DHF_BOMB_RETRIEVED; + pPlayer->HintMessage( "#Hint_you_have_the_bomb", false, true ); + + // Log this information + //UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Spawned_With_The_Bomb\"\n", + // STRING( pPlayer->GetPlayerName() ), + // GETPLAYERUSERID( pPlayer->edict() ), + // GETPLAYERAUTHID( pPlayer->edict() ) ); + } + + m_bBombDropped = false; + } + + void CCSGameRules::Think() + { + CGameRules::Think(); + + for ( int i = 0; i < GetNumberOfTeams(); i++ ) + { + GetGlobalTeam( i )->Think(); + } + + ///// Check game rules ///// + if ( CheckGameOver() ) + { + return; + } + + // have we hit the max rounds? + if ( CheckMaxRounds() ) + { + return; + } + + // did somebaody hit the fraglimit ? + if ( CheckFragLimit() ) + { + return; + } + + if ( CheckWinLimit() ) + { + return; + } + + + // Check for the end of the round. + if ( IsFreezePeriod() ) + { + CheckFreezePeriodExpired(); + } + else + { + CheckRoundTimeExpired(); + } + + CheckLevelInitialized(); + + if ( m_flRestartRoundTime > 0.0f && m_flRestartRoundTime <= gpGlobals->curtime ) + { + bool botSpeaking = false; + for ( int i=1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = UTIL_PlayerByIndex( i ); + if (player == NULL) + continue; + + if (!player->IsBot()) + continue; + + CCSBot *bot = dynamic_cast< CCSBot * >(player); + if ( !bot ) + continue; + + if ( bot->IsUsingVoice() ) + { + if ( gpGlobals->curtime > m_flRestartRoundTime + 10.0f ) + { + Msg( "Ignoring speaking bot %s at round end\n", bot->GetPlayerName() ); + } + else + { + botSpeaking = true; + break; + } + } + } + + if ( !botSpeaking ) + { + RestartRound(); + } + } + + if ( gpGlobals->curtime > m_tmNextPeriodicThink ) + { + CheckRestartRound(); + m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; + } + } + + + // The bots do their processing after physics simulation etc so their visibility checks don't recompute + // bone positions multiple times a frame. + void CCSGameRules::EndGameFrame( void ) + { + TheBots->StartFrame(); + + BaseClass::EndGameFrame(); + } + + bool CCSGameRules::CheckGameOver() + { + if ( g_fGameOver ) // someone else quit the game already + { + //============================================================================= + // HPE_BEGIN: + // [Forrest] Calling ChangeLevel multiple times was causing IncrementMapCycleIndex + // to skip over maps in the list. Avoid this using a technique from CTeamplayRoundBasedRules::Think. + //============================================================================= + // check to see if we should change levels now + if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) ) + { + ChangeLevel(); // intermission is over + + // Don't run this code again + m_flIntermissionEndTime = 0.f; + } + //============================================================================= + // HPE_END + //============================================================================= + + return true; + } + + return false; + } + + bool CCSGameRules::CheckFragLimit() + { + if ( fraglimit.GetInt() <= 0 ) + return false; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->FragCount() >= fraglimit.GetInt() ) + { + const char *teamName = "UNKNOWN"; + if ( pPlayer->GetTeam() ) + { + teamName = pPlayer->GetTeam()->GetName(); + } + UTIL_LogPrintf("\"%s<%i><%s><%s>\" triggered \"Intermission_Kill_Limit\"\n", + pPlayer->GetPlayerName(), + pPlayer->GetUserID(), + pPlayer->GetNetworkIDString(), + teamName + ); + GoToIntermission(); + return true; + } + } + + return false; + } + + bool CCSGameRules::CheckMaxRounds() + { + if ( mp_maxrounds.GetInt() != 0 ) + { + if ( m_iTotalRoundsPlayed >= mp_maxrounds.GetInt() ) + { + UTIL_LogPrintf("World triggered \"Intermission_Round_Limit\"\n"); + GoToIntermission(); + return true; + } + } + + return false; + } + + + bool CCSGameRules::CheckWinLimit() + { + // has one team won the specified number of rounds? + if ( mp_winlimit.GetInt() != 0 ) + { + if ( m_iNumCTWins >= mp_winlimit.GetInt() ) + { + UTIL_LogPrintf("Team \"CT\" triggered \"Intermission_Win_Limit\"\n"); + GoToIntermission(); + return true; + } + if ( m_iNumTerroristWins >= mp_winlimit.GetInt() ) + { + UTIL_LogPrintf("Team \"TERRORIST\" triggered \"Intermission_Win_Limit\"\n"); + GoToIntermission(); + return true; + } + } + + return false; + } + + + void CCSGameRules::CheckFreezePeriodExpired() + { + float startTime = m_fRoundStartTime; + if ( !IsFinite( startTime ) ) + { + Warning( "Infinite round start time!\n" ); + m_fRoundStartTime.GetForModify() = gpGlobals->curtime; + } + + if ( IsFinite( startTime ) && gpGlobals->curtime < startTime ) + { + return; // not time yet to start round + } + + // Log this information + UTIL_LogPrintf("World triggered \"Round_Start\"\n"); + + char CT_sentence[40]; + char T_sentence[40]; + + switch ( random->RandomInt( 0, 3 ) ) + { + case 0: + Q_strncpy(CT_sentence,"radio.moveout", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence ,"radio.moveout", sizeof( T_sentence ) ); + break; + + case 1: + Q_strncpy(CT_sentence, "radio.letsgo", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence , "radio.letsgo", sizeof( T_sentence ) ); + break; + + case 2: + Q_strncpy(CT_sentence , "radio.locknload", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) ); + break; + + default: + Q_strncpy(CT_sentence , "radio.go", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence , "radio.go", sizeof( T_sentence ) ); + break; + } + + // More specific radio commands for the new scenarios : Prison & Assasination + if (m_bMapHasEscapeZone == TRUE) + { + Q_strncpy(CT_sentence , "radio.elim", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence , "radio.getout", sizeof( T_sentence ) ); + } + else if (m_iMapHasVIPSafetyZone == 1) + { + Q_strncpy(CT_sentence , "radio.vip", sizeof( CT_sentence ) ); + Q_strncpy(T_sentence , "radio.locknload", sizeof( T_sentence ) ); + } + + // Freeze period expired: kill the flag + m_bFreezePeriod = false; + + IGameEvent * event = gameeventmanager->CreateEvent( "round_freeze_end" ); + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + // Update the timers for all clients and play a sound + bool bCTPlayed = false; + bool bTPlayed = false; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = CCSPlayer::Instance( i ); + if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) + { + if ( pPlayer->State_Get() == STATE_ACTIVE ) + { + if ( (pPlayer->GetTeamNumber() == TEAM_CT) && !bCTPlayed ) + { + pPlayer->Radio( CT_sentence ); + bCTPlayed = true; + } + else if ( (pPlayer->GetTeamNumber() == TEAM_TERRORIST) && !bTPlayed ) + { + pPlayer->Radio( T_sentence ); + bTPlayed = true; + } + + } + + //pPlayer->SyncRoundTimer(); + } + } + } + + + void CCSGameRules::CheckRoundTimeExpired() + { + if ( mp_ignore_round_win_conditions.GetBool() ) + return; + + if ( GetRoundRemainingTime() > 0 || m_iRoundWinStatus != WINNER_NONE ) + return; //We haven't completed other objectives, so go for this!. + + if( !m_bFirstConnected ) + return; + + // New code to get rid of round draws!! + + if ( m_bMapHasBombTarget ) + { + //If the bomb is planted, don't let the round timer end the round. + //keep going until the bomb explodes or is defused + if( !m_bBombPlanted ) + { + m_iAccountCT += 3250; + + m_iNumCTWins++; + TerminateRound( mp_round_restart_delay.GetFloat(), Target_Saved ); + UpdateTeamScores(); + MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_TERRORIST); + } + } + else if ( m_bMapHasRescueZone ) + { + m_iAccountTerrorist += 3250; + + m_iNumTerroristWins++; + TerminateRound( mp_round_restart_delay.GetFloat(), Hostages_Not_Rescued ); + UpdateTeamScores(); + MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TEAM_CT); + } + else if ( m_bMapHasEscapeZone ) + { + m_iNumCTWins++; + TerminateRound( mp_round_restart_delay.GetFloat(), Terrorists_Not_Escaped ); + UpdateTeamScores(); + } + else if ( m_iMapHasVIPSafetyZone == 1 ) + { + m_iAccountTerrorist += 3250; + m_iNumTerroristWins++; + + TerminateRound( mp_round_restart_delay.GetFloat(), VIP_Not_Escaped ); + UpdateTeamScores(); + } + +#if defined( REPLAY_ENABLED ) + if ( g_pReplay ) + { + // Write replay and stop recording if appropriate + g_pReplay->SV_EndRecordingSession(); + } +#endif + } + + void CCSGameRules::GoToIntermission( void ) + { + Msg( "Going to intermission...\n" ); + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" ); + + if( winEvent ) + { + for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; teamIndex++ ) + { + CTeam *team = GetGlobalTeam( teamIndex ); + if ( team ) + { + float kills = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_KILLS]; + float deaths = CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_DEATHS]; + // choose dialog variables to set depending on team + switch ( teamIndex ) + { + case TEAM_TERRORIST: + winEvent->SetInt( "t_score", team->GetScore() ); + if(deaths == 0) + { + winEvent->SetFloat( "t_kd", kills ); + } + else + { + winEvent->SetFloat( "t_kd", kills / deaths ); + } + winEvent->SetInt( "t_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] ); + winEvent->SetInt( "t_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] ); + break; + case TEAM_CT: + winEvent->SetInt( "ct_score", team->GetScore() ); + if(deaths == 0) + { + winEvent->SetFloat( "ct_kd", kills ); + } + else + { + winEvent->SetFloat( "ct_kd", kills / deaths ); + } + winEvent->SetInt( "ct_objectives_done", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_OBJECTIVES_COMPLETED] ); + winEvent->SetInt( "ct_money_earned", CCS_GameStats.GetTeamStats(teamIndex)[CSSTAT_MONEY_EARNED] ); + break; + default: + Assert( false ); + break; + } + } + } + + gameeventmanager->FireEvent( winEvent ); + } + + BaseClass::GoToIntermission(); + + // set all players to FL_FROZEN + for ( int i = 1; i <= MAX_PLAYERS; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer ) + { + pPlayer->AddFlag( FL_FROZEN ); + } + } + + // freeze players while in intermission + m_bFreezePeriod = true; + } + + int PlayerScoreInfoSort( const playerscore_t *p1, const playerscore_t *p2 ) + { + // check frags + if ( p1->iScore > p2->iScore ) + return -1; + if ( p2->iScore > p1->iScore ) + return 1; + + // check index + if ( p1->iPlayerIndex < p2->iPlayerIndex ) + return -1; + + return 1; + } + +#if defined (_DEBUG) + void TestRoundWinpanel( void ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "round_end" ); + event->SetInt( "winner", TEAM_TERRORIST ); + + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + + IGameEvent *event2 = gameeventmanager->CreateEvent( "player_death" ); + if ( event2 ) + { + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex(1) ); + + // pCappingPlayers is a null terminated list of player indeces + event2->SetInt("userid", pPlayer->GetUserID() ); + event2->SetInt("attacker", pPlayer->GetUserID() ); + event2->SetString("weapon", "Bare Hands" ); + event2->SetInt("headshot", 1 ); + event2->SetInt( "revenge", 1 ); + + gameeventmanager->FireEvent( event2 ); + } + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" ); + + if ( winEvent ) + { + if ( 1 ) + { + if ( 0 /*team == m_iTimerWinTeam */) + { + // timer expired, defenders win + // show total time that was defended + winEvent->SetBool( "show_timer_defend", true ); + winEvent->SetInt( "timer_time", 0 /*m_pRoundTimer->GetTimerMaxLength() */); + } + else + { + // attackers win + // show time it took for them to win + winEvent->SetBool( "show_timer_attack", true ); + + int iTimeElapsed = 90; //m_pRoundTimer->GetTimerMaxLength() - (int)m_pRoundTimer->GetTimeRemaining(); + winEvent->SetInt( "timer_time", iTimeElapsed ); + } + } + else + { + winEvent->SetBool( "show_timer_attack", false ); + winEvent->SetBool( "show_timer_defend", false ); + } + + int iLastEvent = Terrorists_Win; + + winEvent->SetInt( "final_event", iLastEvent ); + + // Set the fun fact data in the event + winEvent->SetString( "funfact_token", "#funfact_first_blood" ); + winEvent->SetInt( "funfact_player", 1 ); + winEvent->SetInt( "funfact_data1", 20 ); + winEvent->SetInt( "funfact_data2", 31 ); + winEvent->SetInt( "funfact_data3", 45 ); + + gameeventmanager->FireEvent( winEvent ); + } + } + ConCommand test_round_winpanel( "test_round_winpanel", TestRoundWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); + + void TestMatchWinpanel( void ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "round_end" ); + event->SetInt( "winner", TEAM_TERRORIST ); + + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_match" ); + + if ( winEvent ) + { + winEvent->SetInt( "t_score", 4 ); + winEvent->SetInt( "ct_score", 1 ); + + winEvent->SetFloat( "t_kd", 1.8f ); + winEvent->SetFloat( "ct_kd", 0.4f ); + + winEvent->SetInt( "t_objectives_done", 5 ); + winEvent->SetInt( "ct_objectives_done", 2 ); + + winEvent->SetInt( "t_money_earned", 30000 ); + winEvent->SetInt( "ct_money_earned", 19999 ); + + gameeventmanager->FireEvent( winEvent ); + } + } + ConCommand test_match_winpanel( "test_match_winpanel", TestMatchWinpanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); + + void TestFreezePanel( void ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "freezecam_started" ); + if ( event ) + { + gameeventmanager->FireEvent( event ); + } + + IGameEvent *winEvent = gameeventmanager->CreateEvent( "show_freezepanel" ); + + if ( winEvent ) + { + winEvent->SetInt( "killer", 1 ); + gameeventmanager->FireEvent( winEvent ); + } + } + ConCommand test_freezepanel( "test_freezepanel", TestFreezePanel, "", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); +#endif // _DEBUG + + static void PrintToConsole( CBasePlayer *player, const char *text ) + { + if ( player ) + { + ClientPrint( player, HUD_PRINTCONSOLE, text ); + } + else + { + Msg( "%s", text ); + } + } + + void CCSGameRules::DumpTimers( void ) const + { + extern ConVar bot_join_delay; + CBasePlayer *player = UTIL_GetCommandClient(); + CFmtStr str; + + PrintToConsole( player, str.sprintf( "Timers and related info at %f:\n", gpGlobals->curtime ) ); + PrintToConsole( player, str.sprintf( "m_bCompleteReset: %d\n", m_bCompleteReset ) ); + PrintToConsole( player, str.sprintf( "m_iTotalRoundsPlayed: %d\n", m_iTotalRoundsPlayed ) ); + PrintToConsole( player, str.sprintf( "m_iRoundTime: %d\n", m_iRoundTime.Get() ) ); + PrintToConsole( player, str.sprintf( "m_iRoundWinStatus: %d\n", m_iRoundWinStatus ) ); + + PrintToConsole( player, str.sprintf( "first connected: %d\n", m_bFirstConnected ) ); + PrintToConsole( player, str.sprintf( "intermission end time: %f\n", m_flIntermissionEndTime ) ); + PrintToConsole( player, str.sprintf( "freeze period: %d\n", m_bFreezePeriod.Get() ) ); + PrintToConsole( player, str.sprintf( "round restart time: %f\n", m_flRestartRoundTime ) ); + PrintToConsole( player, str.sprintf( "game start time: %f\n", m_flGameStartTime.Get() ) ); + PrintToConsole( player, str.sprintf( "m_fRoundStartTime: %f\n", m_fRoundStartTime.Get() ) ); + PrintToConsole( player, str.sprintf( "freeze time: %d\n", m_iFreezeTime ) ); + PrintToConsole( player, str.sprintf( "next think: %f\n", m_tmNextPeriodicThink ) ); + + PrintToConsole( player, str.sprintf( "fraglimit: %d\n", fraglimit.GetInt() ) ); + PrintToConsole( player, str.sprintf( "mp_maxrounds: %d\n", mp_maxrounds.GetInt() ) ); + PrintToConsole( player, str.sprintf( "mp_winlimit: %d\n", mp_winlimit.GetInt() ) ); + PrintToConsole( player, str.sprintf( "bot_quota: %d\n", cv_bot_quota.GetInt() ) ); + PrintToConsole( player, str.sprintf( "bot_quota_mode: %s\n", cv_bot_quota_mode.GetString() ) ); + PrintToConsole( player, str.sprintf( "bot_join_after_player: %d\n", cv_bot_join_after_player.GetInt() ) ); + PrintToConsole( player, str.sprintf( "bot_join_delay: %d\n", bot_join_delay.GetInt() ) ); + PrintToConsole( player, str.sprintf( "nextlevel: %s\n", nextlevel.GetString() ) ); + + int humansInGame = UTIL_HumansInGame( true ); + int botsInGame = UTIL_BotsInGame(); + PrintToConsole( player, str.sprintf( "%d humans and %d bots in game\n", humansInGame, botsInGame ) ); + + PrintToConsole( player, str.sprintf( "num CTs (spawnable): %d (%d)\n", m_iNumCT, m_iNumSpawnableCT ) ); + PrintToConsole( player, str.sprintf( "num Ts (spawnable): %d (%d)\n", m_iNumTerrorist, m_iNumSpawnableTerrorist ) ); + + if ( g_fGameOver ) + { + PrintToConsole( player, str.sprintf( "Game is over!\n" ) ); + } + PrintToConsole( player, str.sprintf( "\n" ) ); + } + + CON_COMMAND( mp_dump_timers, "Prints round timers to the console for debugging" ) + { + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( CSGameRules() ) + { + CSGameRules()->DumpTimers(); + } + } + + + // living players on the given team need to be marked as not receiving any money + // next round. + void CCSGameRules::MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int team) + { + int playerNum; + for (playerNum = 1; playerNum <= gpGlobals->maxClients; ++playerNum) + { + CCSPlayer *player = (CCSPlayer *)UTIL_PlayerByIndex(playerNum); + if (player == NULL) + { + continue; + } + + if ((player->GetTeamNumber() == team) && (player->IsAlive())) + { + player->MarkAsNotReceivingMoneyNextRound(); + } + } + } + + + void CCSGameRules::CheckLevelInitialized( void ) + { + if ( !m_bLevelInitialized ) + { + // Count the number of spawn points for each team + // This determines the maximum number of players allowed on each + + CBaseEntity* ent = NULL; + + m_iSpawnPointCount_Terrorist = 0; + m_iSpawnPointCount_CT = 0; + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + m_iSpawnPointCount_Terrorist++; + } + else + { + Warning("Invalid terrorist spawnpoint at (%.1f,%.1f,%.1f)\n", + ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); + } + } + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + m_iSpawnPointCount_CT++; + } + else + { + Warning("Invalid counterterrorist spawnpoint at (%.1f,%.1f,%.1f)\n", + ent->GetAbsOrigin()[0],ent->GetAbsOrigin()[2],ent->GetAbsOrigin()[2] ); + } + } + + // Is this a logo map? + if ( gEntList.FindEntityByClassname( NULL, "info_player_logo" ) ) + m_bLogoMap = true; + + m_bLevelInitialized = true; + } + } + + void CCSGameRules::ShowSpawnPoints( void ) + { + CBaseEntity* ent = NULL; + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_terrorist" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 ); + } + else + { + NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600); + } + } + + while ( ( ent = gEntList.FindEntityByClassname( ent, "info_player_counterterrorist" ) ) != NULL ) + { + if ( IsSpawnPointValid( ent, NULL ) ) + { + NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 200, 600 ); + } + else + { + NDebugOverlay::Box( ent->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 200, 600 ); + } + } + } + + void CCSGameRules::CheckRestartRound( void ) + { + // Restart the game if specified by the server + int iRestartDelay = mp_restartgame.GetInt(); + + if ( iRestartDelay > 0 ) + { + if ( iRestartDelay > 60 ) + iRestartDelay = 60; + + // log the restart + UTIL_LogPrintf( "World triggered \"Restart_Round_(%i_%s)\"\n", iRestartDelay, iRestartDelay == 1 ? "second" : "seconds" ); + + UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", m_iNumCTWins, m_iNumCT ); + UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_iNumTerroristWins, m_iNumTerrorist ); + + // let the players know + char strRestartDelay[64]; + Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); + UTIL_ClientPrintAll( HUD_PRINTCENTER, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_will_restart_in", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + + m_flRestartRoundTime = gpGlobals->curtime + iRestartDelay; + m_bCompleteReset = true; + mp_restartgame.SetValue( 0 ); + } + } + + + class SetHumanTeamFunctor + { + public: + SetHumanTeamFunctor( int targetTeam ) + { + m_targetTeam = targetTeam; + m_sourceTeam = ( m_targetTeam == TEAM_CT ) ? TEAM_TERRORIST : TEAM_CT; + + m_traitors.MakeReliable(); + m_loyalists.MakeReliable(); + m_loyalists.AddAllPlayers(); + } + + bool operator()( CBasePlayer *basePlayer ) + { + CCSPlayer *player = ToCSPlayer( basePlayer ); + if ( !player ) + return true; + + if ( player->IsBot() ) + return true; + + if ( player->GetTeamNumber() != m_sourceTeam ) + return true; + + if ( player->State_Get() == STATE_PICKINGCLASS ) + return true; + + if ( CSGameRules()->TeamFull( m_targetTeam ) ) + return false; + + if ( CSGameRules()->TeamStacked( m_targetTeam, m_sourceTeam ) ) + return false; + + player->SwitchTeam( m_targetTeam ); + m_traitors.AddRecipient( player ); + m_loyalists.RemoveRecipient( player ); + + return true; + } + + void SendNotice( void ) + { + if ( m_traitors.GetRecipientCount() > 0 ) + { + UTIL_ClientPrintFilter( m_traitors, HUD_PRINTCENTER, "#Player_Balanced" ); + UTIL_ClientPrintFilter( m_loyalists, HUD_PRINTCENTER, "#Teams_Balanced" ); + } + } + + private: + int m_targetTeam; + int m_sourceTeam; + + CRecipientFilter m_traitors; + CRecipientFilter m_loyalists; + }; + + + void CCSGameRules::MoveHumansToHumanTeam( void ) + { + int targetTeam = GetHumanTeam(); + if ( targetTeam != TEAM_TERRORIST && targetTeam != TEAM_CT ) + return; + + SetHumanTeamFunctor setTeam( targetTeam ); + ForEachPlayer( setTeam ); + + setTeam.SendNotice(); + } + + + void CCSGameRules::BalanceTeams( void ) + { + int iTeamToSwap = TEAM_UNASSIGNED; + int iNumToSwap; + + if (m_iMapHasVIPSafetyZone == 1) // The ratio for teams is different for Assasination maps + { + int iDesiredNumCT, iDesiredNumTerrorist; + + if ( (m_iNumCT + m_iNumTerrorist)%2 != 0) // uneven number of players + iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist) * 0.55) + 1; + else + iDesiredNumCT = (int)((m_iNumCT + m_iNumTerrorist)/2); + iDesiredNumTerrorist = (m_iNumCT + m_iNumTerrorist) - iDesiredNumCT; + + if ( m_iNumCT < iDesiredNumCT ) + { + iTeamToSwap = TEAM_TERRORIST; + iNumToSwap = iDesiredNumCT - m_iNumCT; + } + else if ( m_iNumTerrorist < iDesiredNumTerrorist ) + { + iTeamToSwap = TEAM_CT; + iNumToSwap = iDesiredNumTerrorist - m_iNumTerrorist; + } + else + return; + } + else + { + if (m_iNumCT > m_iNumTerrorist) + { + iTeamToSwap = TEAM_CT; + iNumToSwap = (m_iNumCT - m_iNumTerrorist)/2; + + } + else if (m_iNumTerrorist > m_iNumCT) + { + iTeamToSwap = TEAM_TERRORIST; + iNumToSwap = (m_iNumTerrorist - m_iNumCT)/2; + } + else + { + return; // Teams are even.. Get out of here. + } + } + + if (iNumToSwap > 3) // Don't swap more than 3 players at a time.. This is a naive method of avoiding infinite loops. + iNumToSwap = 3; + + int iTragetTeam = TEAM_UNASSIGNED; + + if ( iTeamToSwap == TEAM_CT ) + { + iTragetTeam = TEAM_TERRORIST; + } + else if ( iTeamToSwap == TEAM_TERRORIST ) + { + iTragetTeam = TEAM_CT; + } + else + { + // no valid team to swap + return; + } + + CRecipientFilter traitors; + CRecipientFilter loyalists; + + traitors.MakeReliable(); + loyalists.MakeReliable(); + loyalists.AddAllPlayers(); + + for (int i = 0; i < iNumToSwap; i++) + { + // last person to join the server + int iHighestUserID = -1; + CCSPlayer *pPlayerToSwap = NULL; + + // check if target team is full, exit if so + if ( TeamFull(iTragetTeam) ) + break; + + // search for player with highest UserID = most recently joined to switch over + for ( int j = 1; j <= gpGlobals->maxClients; j++ ) + { + CCSPlayer *pPlayer = (CCSPlayer *)UTIL_PlayerByIndex( j ); + + if ( !pPlayer ) + continue; + + CCSBot *bot = dynamic_cast< CCSBot * >(pPlayer); + if ( bot ) + continue; // don't swap bots - the bot system will handle that + + if ( pPlayer && + ( m_pVIP != pPlayer ) && + ( pPlayer->GetTeamNumber() == iTeamToSwap ) && + ( engine->GetPlayerUserId( pPlayer->edict() ) > iHighestUserID ) && + ( pPlayer->State_Get() != STATE_PICKINGCLASS ) ) + { + iHighestUserID = engine->GetPlayerUserId( pPlayer->edict() ); + pPlayerToSwap = pPlayer; + } + } + + if ( pPlayerToSwap != NULL ) + { + traitors.AddRecipient( pPlayerToSwap ); + loyalists.RemoveRecipient( pPlayerToSwap ); + pPlayerToSwap->SwitchTeam( iTragetTeam ); + } + } + + if ( traitors.GetRecipientCount() > 0 ) + { + UTIL_ClientPrintFilter( traitors, HUD_PRINTCENTER, "#Player_Balanced" ); + UTIL_ClientPrintFilter( loyalists, HUD_PRINTCENTER, "#Teams_Balanced" ); + } + } + + + bool CCSGameRules::TeamFull( int team_id ) + { + CheckLevelInitialized(); + + switch ( team_id ) + { + case TEAM_TERRORIST: + return m_iNumTerrorist >= m_iSpawnPointCount_Terrorist; + + case TEAM_CT: + return m_iNumCT >= m_iSpawnPointCount_CT; + } + + return false; + } + + int CCSGameRules::GetHumanTeam() + { + if ( FStrEq( "CT", mp_humanteam.GetString() ) ) + { + return TEAM_CT; + } + else if ( FStrEq( "T", mp_humanteam.GetString() ) ) + { + return TEAM_TERRORIST; + } + + return TEAM_UNASSIGNED; + } + + int CCSGameRules::SelectDefaultTeam( bool ignoreBots /*= false*/ ) + { + if ( ignoreBots && ( FStrEq( cv_bot_join_team.GetString(), "T" ) || FStrEq( cv_bot_join_team.GetString(), "CT" ) ) ) + { + ignoreBots = false; // don't ignore bots when they can't switch teams + } + + if ( ignoreBots && !mp_autoteambalance.GetBool() ) + { + ignoreBots = false; // don't ignore bots when they can't switch teams + } + + int team = TEAM_UNASSIGNED; + int numTerrorists = m_iNumTerrorist; + int numCTs = m_iNumCT; + if ( ignoreBots ) + { + numTerrorists = UTIL_HumansOnTeam( TEAM_TERRORIST ); + numCTs = UTIL_HumansOnTeam( TEAM_CT ); + } + + // Choose the team that's lacking players + if ( numTerrorists < numCTs ) + { + team = TEAM_TERRORIST; + } + else if ( numTerrorists > numCTs ) + { + team = TEAM_CT; + } + // Choose the team that's losing + else if ( m_iNumTerroristWins < m_iNumCTWins ) + { + team = TEAM_TERRORIST; + } + else if ( m_iNumCTWins < m_iNumTerroristWins ) + { + team = TEAM_CT; + } + else + { + // Teams and scores are equal, pick a random team + if ( random->RandomInt( 0, 1 ) == 0 ) + { + team = TEAM_CT; + } + else + { + team = TEAM_TERRORIST; + } + } + + if ( TeamFull( team ) ) + { + // Pick the opposite team + if ( team == TEAM_TERRORIST ) + { + team = TEAM_CT; + } + else + { + team = TEAM_TERRORIST; + } + + // No choices left + if ( TeamFull( team ) ) + return TEAM_UNASSIGNED; + } + + return team; + } + + //checks to see if the desired team is stacked, returns true if it is + bool CCSGameRules::TeamStacked( int newTeam_id, int curTeam_id ) + { + //players are allowed to change to their own team + if(newTeam_id == curTeam_id) + return false; + + // if mp_limitteams is 0, don't check + if ( mp_limitteams.GetInt() == 0 ) + return false; + + switch ( newTeam_id ) + { + case TEAM_TERRORIST: + if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR) + { + if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt() - 1)) + return true; + else + return false; + } + else + { + if((m_iNumTerrorist + 1) > (m_iNumCT + mp_limitteams.GetInt())) + return true; + else + return false; + } + break; + case TEAM_CT: + if(curTeam_id != TEAM_UNASSIGNED && curTeam_id != TEAM_SPECTATOR) + { + if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt() - 1)) + return true; + else + return false; + } + else + { + if((m_iNumCT + 1) > (m_iNumTerrorist + mp_limitteams.GetInt())) + return true; + else + return false; + } + break; + } + + return false; + } + + + //========================================================= + //========================================================= + bool CCSGameRules::FPlayerCanRespawn( CBasePlayer *pBasePlayer ) + { + CCSPlayer *pPlayer = ToCSPlayer( pBasePlayer ); + if ( !pPlayer ) + Error( "FPlayerCanRespawn: pPlayer=0" ); + + // Player cannot respawn twice in a round + if ( pPlayer->m_iNumSpawns > 0 && m_bFirstConnected ) + return false; + + // If they're dead after the map has ended, and it's about to start the next round, + // wait for the round restart to respawn them. + if ( gpGlobals->curtime < m_flRestartRoundTime ) + return false; + + // Only valid team members can spawn + if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST ) + return false; + + // Only players with a valid class can spawn + if ( pPlayer->GetClass() == CS_CLASS_NONE ) + return false; + + // Player cannot respawn until next round if more than 20 seconds in + + // Tabulate the number of players on each team. + m_iNumCT = GetGlobalTeam( TEAM_CT )->GetNumPlayers(); + m_iNumTerrorist = GetGlobalTeam( TEAM_TERRORIST )->GetNumPlayers(); + + if ( m_iNumTerrorist > 0 && m_iNumCT > 0 ) + { + if ( gpGlobals->curtime > (m_fRoundStartTime + 20) ) + { + //If this player just connected and fadetoblack is on, then maybe + //the server admin doesn't want him peeking around. + color32_s clr = {0,0,0,255}; + if ( mp_fadetoblack.GetBool() ) + { + UTIL_ScreenFade( pPlayer, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT ); + } + + return false; + } + } + + // Player cannot respawn while in the Choose Appearance menu + //if ( pPlayer->m_iMenu == Menu_ChooseAppearance ) + // return false; + + return true; + } + + void CCSGameRules::TerminateRound(float tmDelay, int iReason ) + { + variant_t emptyVariant; + int iWinnerTeam = WINNER_NONE; + const char *text = "UNKNOWN"; + + // UTIL_ClientPrintAll( HUD_PRINTCENTER, sentence ); + + switch ( iReason ) + { +// Terror wins: + case Target_Bombed: + text = "#Target_Bombed"; + iWinnerTeam = WINNER_TER; + break; + + case VIP_Assassinated: + text = "#VIP_Assassinated"; + iWinnerTeam = WINNER_TER; + break; + + case Terrorists_Escaped: + text = "#Terrorists_Escaped"; + iWinnerTeam = WINNER_TER; + break; + + case Terrorists_Win: + text = "#Terrorists_Win"; + iWinnerTeam = WINNER_TER; + break; + + case Hostages_Not_Rescued: + text = "#Hostages_Not_Rescued"; + iWinnerTeam = WINNER_TER; + break; + + case VIP_Not_Escaped: + text = "#VIP_Not_Escaped"; + iWinnerTeam = WINNER_TER; + break; +// CT wins: + case VIP_Escaped: + text = "#VIP_Escaped"; + iWinnerTeam = WINNER_CT; + break; + + case CTs_PreventEscape: + text = "#CTs_PreventEscape"; + iWinnerTeam = WINNER_CT; + break; + + case Escaping_Terrorists_Neutralized: + text = "#Escaping_Terrorists_Neutralized"; + iWinnerTeam = WINNER_CT; + break; + + case Bomb_Defused: + text = "#Bomb_Defused"; + iWinnerTeam = WINNER_CT; + break; + + case CTs_Win: + text = "#CTs_Win"; + iWinnerTeam = WINNER_CT; + break; + + case All_Hostages_Rescued: + text = "#All_Hostages_Rescued"; + iWinnerTeam = WINNER_CT; + break; + + case Target_Saved: + text = "#Target_Saved"; + iWinnerTeam = WINNER_CT; + break; + + case Terrorists_Not_Escaped: + text = "#Terrorists_Not_Escaped"; + iWinnerTeam = WINNER_CT; + break; +// no winners: + case Game_Commencing: + text = "#Game_Commencing"; + iWinnerTeam = WINNER_DRAW; + break; + + case Round_Draw: + text = "#Round_Draw"; + iWinnerTeam = WINNER_DRAW; + break; + + default: + DevMsg("TerminateRound: unknown round end ID %i\n", iReason ); + break; + } + + m_iRoundWinStatus = iWinnerTeam; + m_flRestartRoundTime = gpGlobals->curtime + tmDelay; + + if ( iWinnerTeam == WINNER_CT ) + { + for( int i=0;i<g_Hostages.Count();i++ ) + g_Hostages[i]->AcceptInput( "CTsWin", NULL, NULL, emptyVariant, 0 ); + } + + else if ( iWinnerTeam == WINNER_TER ) + { + for( int i=0;i<g_Hostages.Count();i++ ) + g_Hostages[i]->AcceptInput( "TerroristsWin", NULL, NULL, emptyVariant, 0 ); + } + else + { + Assert( iWinnerTeam == WINNER_NONE || iWinnerTeam == WINNER_DRAW ); + } + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] Check for any non-player-specific achievements. + ProcessEndOfRoundAchievements(iWinnerTeam, iReason); + + if( iReason != Game_Commencing ) + { + // [pfreese] Setup and send win panel event (primarily funfact data) + + FunFact funfact; + funfact.szLocalizationToken = ""; + funfact.iPlayer = 0; + funfact.iData1 = 0; + funfact.iData2 = 0; + funfact.iData3 = 0; + + m_pFunFactManager->GetRoundEndFunFact( iWinnerTeam, iReason, funfact); + + //Send all the info needed for the win panel + IGameEvent *winEvent = gameeventmanager->CreateEvent( "cs_win_panel_round" ); + + if ( winEvent ) + { + // determine what categories to send + if ( GetRoundRemainingTime() <= 0 ) + { + // timer expired, defenders win + // show total time that was defended + winEvent->SetBool( "show_timer_defend", true ); + winEvent->SetInt( "timer_time", m_iRoundTime ); + } + else + { + // attackers win + // show time it took for them to win + winEvent->SetBool( "show_timer_attack", true ); + + int iTimeElapsed = m_iRoundTime - GetRoundRemainingTime(); + winEvent->SetInt( "timer_time", iTimeElapsed ); + } + + winEvent->SetInt( "final_event", iReason ); + + // Set the fun fact data in the event + winEvent->SetString( "funfact_token", funfact.szLocalizationToken); + winEvent->SetInt( "funfact_player", funfact.iPlayer ); + winEvent->SetInt( "funfact_data1", funfact.iData1 ); + winEvent->SetInt( "funfact_data2", funfact.iData2 ); + winEvent->SetInt( "funfact_data3", funfact.iData3 ); + gameeventmanager->FireEvent( winEvent ); + } + } + + // [tj] Inform players that the round is over + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + if(pPlayer) + { + pPlayer->OnRoundEnd(iWinnerTeam, iReason); + } + } + //============================================================================= + // HPE_END + //============================================================================= + + IGameEvent * event = gameeventmanager->CreateEvent( "round_end" ); + if ( event ) + { + event->SetInt( "winner", iWinnerTeam ); + event->SetInt( "reason", iReason ); + event->SetString( "message", text ); + event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted + gameeventmanager->FireEvent( event ); + } + + if ( GetMapRemainingTime() == 0.0f ) + { + UTIL_LogPrintf("World triggered \"Intermission_Time_Limit\"\n"); + GoToIntermission(); + } + } + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // Helper to determine if all players on a team are playing for the same clan + static bool IsClanTeam( CTeam *pTeam ) + { + uint32 iTeamClan = 0; + for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) + { + CBasePlayer *pPlayer = pTeam->GetPlayer( iPlayer ); + if ( !pPlayer ) + return false; + + const char *pClanID = engine->GetClientConVarValue( pPlayer->entindex(), "cl_clanid" ); + uint32 iPlayerClan = atoi( pClanID ); + if ( iPlayer == 0 ) + { + // Initialize the team clan + iTeamClan = iPlayerClan; + } + else + { + if ( iPlayerClan != iTeamClan || iPlayerClan == 0 ) + return false; + } + } + return iTeamClan != 0; + } + + // [tj] This is where we check non-player-specific that occur at the end of the round + void CCSGameRules::ProcessEndOfRoundAchievements(int iWinnerTeam, int iReason) + { + if (iWinnerTeam == WINNER_CT || iWinnerTeam == WINNER_TER) + { + int losingTeamId = (iWinnerTeam == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT; + CTeam* losingTeam = GetGlobalTeam(losingTeamId); + + + //Check for players we should ignore when checking team size. + int ignoreCount = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); + if (pPlayer) + { + int teamNum = pPlayer->GetTeamNumber(); + if ( teamNum == losingTeamId ) + { + if (pPlayer->WasNotKilledNaturally()) + { + ignoreCount++; + } + } + } + } + + + // [tj] Check extermination with no losses achievement + if ( ( ( iReason == CTs_Win && m_bNoCTsKilled ) || ( iReason == Terrorists_Win && m_bNoTerroristsKilled ) ) + && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement) + { + CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); + + for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); + Assert( pPlayer ); + if ( !pPlayer ) + continue; + + pPlayer->AwardAchievement(CSLosslessExtermination); + } + } + + // [tj] Check flawless victory achievement - currently requiring extermination + if (((iReason == CTs_Win && m_bNoCTsDamaged) || (iReason == Terrorists_Win && m_bNoTerroristsDamaged)) + && losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement) + { + CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); + + for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); + Assert( pPlayer ); + if ( !pPlayer ) + continue; + + pPlayer->AwardAchievement(CSFlawlessVictory); + } + } + + // [tj] Check bloodless victory achievement + if (((iWinnerTeam == TEAM_TERRORIST && m_bNoCTsKilled) || (iWinnerTeam == Terrorists_Win && m_bNoTerroristsKilled)) + && losingTeam && losingTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement) + { + CTeam *pTeam = GetGlobalTeam( iWinnerTeam ); + + for ( int iPlayer=0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pTeam->GetPlayer( iPlayer ) ); + Assert( pPlayer ); + if ( !pPlayer ) + continue; + + pPlayer->AwardAchievement(CSBloodlessVictory); + } + } + + // Check the clan match achievement + CTeam *pWinningTeam = GetGlobalTeam( iWinnerTeam ); + if ( pWinningTeam && pWinningTeam->GetNumPlayers() >= AchievementConsts::DefaultMinOpponentsForAchievement && + losingTeam && losingTeam->GetNumPlayers() - ignoreCount >= AchievementConsts::DefaultMinOpponentsForAchievement && + IsClanTeam( pWinningTeam ) && IsClanTeam( losingTeam ) ) + { + for ( int iPlayer=0; iPlayer < pWinningTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pWinningTeam->GetPlayer( iPlayer ) ); + if ( !pPlayer ) + continue; + + pPlayer->AwardAchievement( CSWinClanMatch ); + } + } + } + } + + //[tj] Counts the number of players in each category in the struct (dead, alive, etc...) + void CCSGameRules::GetPlayerCounts(TeamPlayerCounts teamCounts[TEAM_MAXCOUNT]) + { + memset(teamCounts, 0, sizeof(TeamPlayerCounts) * TEAM_MAXCOUNT); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); + if (pPlayer) + { + int iTeam = pPlayer->GetTeamNumber(); + + if (iTeam >= 0 && iTeam < TEAM_MAXCOUNT) + { + ++teamCounts[iTeam].totalPlayers; + if (pPlayer->IsAlive()) + { + ++teamCounts[iTeam].totalAlivePlayers; + } + else + { + ++teamCounts[iTeam].totalDeadPlayers; + + //If the player has joined a team bit isn't in the game yet + if (pPlayer->State_Get() == STATE_PICKINGCLASS) + { + ++teamCounts[iTeam].unenteredPlayers; + } + else if (pPlayer->WasNotKilledNaturally()) + { + ++teamCounts[iTeam].suicidedPlayers; + } + else + { + ++teamCounts[iTeam].killedPlayers; + } + } + } + } + } + } + //============================================================================= + // HPE_END + //============================================================================= + + void CCSGameRules::UpdateTeamScores() + { + CTeam *pTerrorists = GetGlobalTeam( TEAM_TERRORIST ); + CTeam *pCTs = GetGlobalTeam( TEAM_CT ); + + Assert( pTerrorists && pCTs ); + + if( pTerrorists ) + pTerrorists->SetScore( m_iNumTerroristWins ); + + if( pCTs ) + pCTs->SetScore( m_iNumCTWins ); + } + + + void CCSGameRules::CheckMapConditions() + { + // Check to see if this map has a bomb target in it + if ( gEntList.FindEntityByClassname( NULL, "func_bomb_target" ) ) + { + m_bMapHasBombTarget = true; + m_bMapHasBombZone = true; + } + else if ( gEntList.FindEntityByClassname( NULL, "info_bomb_target" ) ) + { + m_bMapHasBombTarget = true; + m_bMapHasBombZone = false; + } + else + { + m_bMapHasBombTarget = false; + m_bMapHasBombZone = false; + } + + // See if the map has func_buyzone entities + // Used by CBasePlayer::HandleSignals() to support maps without these entities + if ( gEntList.FindEntityByClassname( NULL, "func_buyzone" ) ) + { + m_bMapHasBuyZone = true; + } + else + { + m_bMapHasBuyZone = false; + } + + // Check to see if this map has hostage rescue zones + if ( gEntList.FindEntityByClassname( NULL, "func_hostage_rescue" ) ) + { + m_bMapHasRescueZone = true; + } + else + { + m_bMapHasRescueZone = false; + } + + // GOOSEMAN : See if this map has func_escapezone entities + if ( gEntList.FindEntityByClassname( NULL, "func_escapezone" ) ) + { + m_bMapHasEscapeZone = true; + } + else + { + m_bMapHasEscapeZone = false; + } + + // Check to see if this map has VIP safety zones + if ( gEntList.FindEntityByClassname( NULL, "func_vip_safetyzone" ) ) + { + m_iMapHasVIPSafetyZone = 1; + } + else + { + m_iMapHasVIPSafetyZone = 2; + } + } + + + void CCSGameRules::SwapAllPlayers() + { + // MOTODO we have to make sure that enought spaning points exits + Assert ( 0 ); + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + /* CCSPlayer *pPlayer = CCSPlayer::Instance( i ); + if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) + pPlayer->SwitchTeam(); */ + } + + // Swap Team victories + int iTemp; + + iTemp = m_iNumCTWins; + m_iNumCTWins = m_iNumTerroristWins; + m_iNumTerroristWins = iTemp; + + // Update the clients team score + UpdateTeamScores(); + } + + + bool CS_FindInList( const char **pStrings, const char *pToFind ) + { + return FindInList( pStrings, pToFind ); + } + + void CCSGameRules::CleanUpMap() + { + if (IsLogoMap()) + return; + + // Recreate all the map entities from the map data (preserving their indices), + // then remove everything else except the players. + + // Get rid of all entities except players. + CBaseEntity *pCur = gEntList.FirstEnt(); + while ( pCur ) + { + CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pCur ); + // Weapons with owners don't want to be removed.. + if ( pWeapon ) + { + //============================================================================= + // HPE_BEGIN: + // [dwenger] Handle round restart processing for the weapon. + //============================================================================= + + pWeapon->OnRoundRestart(); + + //============================================================================= + // HPE_END + //============================================================================= + + if ( pWeapon->ShouldRemoveOnRoundRestart() ) + { + UTIL_Remove( pCur ); + } + } + // remove entities that has to be restored on roundrestart (breakables etc) + else if ( !CS_FindInList( s_PreserveEnts, pCur->GetClassname() ) ) + { + UTIL_Remove( pCur ); + } + + pCur = gEntList.NextEnt( pCur ); + } + + // Really remove the entities so we can have access to their slots below. + gEntList.CleanupDeleteList(); + + // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that + // could kill respawning CTs + g_EventQueue.Clear(); + + // Now reload the map entities. + class CCSMapEntityFilter : public IMapEntityFilter + { + public: + virtual bool ShouldCreateEntity( const char *pClassname ) + { + // Don't recreate the preserved entities. + if ( !CS_FindInList( s_PreserveEnts, pClassname ) ) + { + return true; + } + else + { + // Increment our iterator since it's not going to call CreateNextEntity for this ent. + if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); + + return false; + } + } + + + virtual CBaseEntity* CreateNextEntity( const char *pClassname ) + { + if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) + { + // This shouldn't be possible. When we loaded the map, it should have used + // CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list + // with the same list of entities we're referring to here. + Assert( false ); + return NULL; + } + else + { + CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. + + if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) + { + // Doh! The entity was delete and its slot was reused. + // Just use any old edict slot. This case sucks because we lose the baseline. + return CreateEntityByName( pClassname ); + } + else + { + // Cool, the slot where this entity was is free again (most likely, the entity was + // freed above). Now create an entity with this specific index. + return CreateEntityByName( pClassname, ref.m_iEdict ); + } + } + } + + public: + int m_iIterator; // Iterator into g_MapEntityRefs. + }; + CCSMapEntityFilter filter; + filter.m_iIterator = g_MapEntityRefs.Head(); + + // DO NOT CALL SPAWN ON info_node ENTITIES! + + MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); + } + + + bool CCSGameRules::IsThereABomber() + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = CCSPlayer::Instance( i ); + + if ( pPlayer && !FNullEnt( pPlayer->edict() ) ) + { + if ( pPlayer->GetTeamNumber() == TEAM_CT ) + continue; + + if ( pPlayer->HasC4() ) + return true; //There you are. + } + } + + //Didn't find a bomber. + return false; + } + + + void CCSGameRules::EndRound() + { + // fake a round end + CSGameRules()->TerminateRound( 0.0f, Round_Draw ); + } + + CBaseEntity *CCSGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) + { + // gat valid spwan point + CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint(); + + // drop down to ground + Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX ); + + // Move the player to the place it said. + pPlayer->Teleport( &pSpawnSpot->GetAbsOrigin(), &pSpawnSpot->GetLocalAngles(), &vec3_origin ); + pPlayer->m_Local.m_vecPunchAngle = vec3_angle; + + return pSpawnSpot; + } + + // checks if the spot is clear of players + bool CCSGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ) + { + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return false; + } + + Vector mins = GetViewVectors()->m_vHullMin; + Vector maxs = GetViewVectors()->m_vHullMax; + + Vector vTestMins = pSpot->GetAbsOrigin() + mins; + Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs; + + // First test the starting origin. + return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs ); + } + + + bool CCSGameRules::IsThereABomb() + { + bool bBombFound = false; + + /* are there any bombs, either laying around, or in someone's inventory? */ + if( gEntList.FindEntityByClassname( NULL, WEAPON_C4_CLASSNAME ) != 0 ) + { + bBombFound = true; + } + /* what about planted bombs!? */ + else if( gEntList.FindEntityByClassname( NULL, PLANTED_C4_CLASSNAME ) != 0 ) + { + bBombFound = true; + } + + return bBombFound; + } + + void CCSGameRules::HostageTouched() + { + if( gpGlobals->curtime > m_flNextHostageAnnouncement && m_iRoundWinStatus == WINNER_NONE ) + { + //BroadcastSound( "Event.HostageTouched" ); + m_flNextHostageAnnouncement = gpGlobals->curtime + 60.0; + } + } + + void CCSGameRules::CreateStandardEntities() + { + // Create the player resource + g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "cs_player_manager", vec3_origin, vec3_angle ); + + // Create the entity that will send our data to the client. +#ifdef DBGFLAG_ASSERT + CBaseEntity *pEnt = +#endif + CBaseEntity::Create( "cs_gamerules", vec3_origin, vec3_angle ); + Assert( pEnt ); + } + +#define MY_USHRT_MAX 0xffff +#define MY_UCHAR_MAX 0xff + +bool DataHasChanged( void ) +{ + for ( int i = 0; i < CS_NUM_LEVELS; i++ ) + { + if ( g_iTerroristVictories[i] != 0 || g_iCounterTVictories[i] != 0 ) + return true; + } + + for ( int i = 0; i < WEAPON_MAX; i++ ) + { + if ( g_iWeaponPurchases[i] != 0 ) + return true; + } + + return false; +} + +void CCSGameRules::UploadGameStats( void ) +{ + g_flGameStatsUpdateTime -= gpGlobals->curtime; + + if ( g_flGameStatsUpdateTime > 0 ) + return; + + if ( IsBlackMarket() == false ) + return; + + if ( m_bDontUploadStats == true ) + return; + + if ( DataHasChanged() == true ) + { + cs_gamestats_t stats; + memset( &stats, 0, sizeof(stats) ); + + // Header + stats.header.iVersion = CS_STATS_BLOB_VERSION; + Q_strncpy( stats.header.szGameName, "cstrike", sizeof(stats.header.szGameName) ); + Q_strncpy( stats.header.szMapName, STRING( gpGlobals->mapname ), sizeof( stats.header.szMapName ) ); + + ConVar *hostip = cvar->FindVar( "hostip" ); + if ( hostip ) + { + int ip = hostip->GetInt(); + stats.header.ipAddr[0] = ip >> 24; + stats.header.ipAddr[1] = ( ip >> 16 ) & MY_UCHAR_MAX; + stats.header.ipAddr[2] = ( ip >> 8 ) & MY_UCHAR_MAX; + stats.header.ipAddr[3] = ( ip ) & MY_UCHAR_MAX; + } + + ConVar *hostport = cvar->FindVar( "hostip" ); + if ( hostport ) + { + stats.header.port = hostport->GetInt(); + } + + stats.header.serverid = 0; + + stats.iMinutesPlayed = clamp( (short)( gpGlobals->curtime / 60 ), 0, MY_USHRT_MAX ); + + memcpy( stats.iTerroristVictories, g_iTerroristVictories, sizeof( g_iTerroristVictories) ); + memcpy( stats.iCounterTVictories, g_iCounterTVictories, sizeof( g_iCounterTVictories) ); + memcpy( stats.iBlackMarketPurchases, g_iWeaponPurchases, sizeof( g_iWeaponPurchases) ); + + stats.iAutoBuyPurchases = g_iAutoBuyPurchases; + stats.iReBuyPurchases = g_iReBuyPurchases; + + stats.iAutoBuyM4A1Purchases = g_iAutoBuyM4A1Purchases; + stats.iAutoBuyAK47Purchases = g_iAutoBuyAK47Purchases; + stats.iAutoBuyFamasPurchases = g_iAutoBuyFamasPurchases; + stats.iAutoBuyGalilPurchases = g_iAutoBuyGalilPurchases; + stats.iAutoBuyVestHelmPurchases = g_iAutoBuyVestHelmPurchases; + stats.iAutoBuyVestPurchases = g_iAutoBuyVestPurchases; + + const void *pvBlobData = ( const void * )( &stats ); + unsigned int uBlobSize = sizeof( stats ); + + if ( gamestatsuploader ) + { + gamestatsuploader->UploadGameStats( + STRING( gpGlobals->mapname ), + CS_STATS_BLOB_VERSION, + uBlobSize, + pvBlobData ); + } + + + memset( g_iWeaponPurchases, 0, sizeof( g_iWeaponPurchases) ); + memset( g_iTerroristVictories, 0, sizeof( g_iTerroristVictories) ); + memset( g_iCounterTVictories, 0, sizeof( g_iTerroristVictories) ); + + g_iAutoBuyPurchases = 0; + g_iReBuyPurchases = 0; + + g_iAutoBuyM4A1Purchases = 0; + g_iAutoBuyAK47Purchases = 0; + g_iAutoBuyFamasPurchases = 0; + g_iAutoBuyGalilPurchases = 0; + g_iAutoBuyVestHelmPurchases = 0; + g_iAutoBuyVestPurchases = 0; + } + + g_flGameStatsUpdateTime = CS_GAME_STATS_UPDATE; //Next update is between 22 and 24 hours. +} +#endif // CLIENT_DLL + +CBaseCombatWeapon *CCSGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) +{ + CBaseCombatWeapon *bestWeapon = NULL; + + // search all the weapons looking for the closest next + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + CBaseCombatWeapon *weapon = pPlayer->GetWeapon(i); + if ( !weapon ) + continue; + + if ( !weapon->CanBeSelected() || weapon == pCurrentWeapon ) + continue; + +#ifndef CLIENT_DLL + CCSPlayer *csPlayer = ToCSPlayer(pPlayer); + CWeaponCSBase *csWeapon = static_cast< CWeaponCSBase * >(weapon); + if ( csPlayer && csPlayer->IsBot() && !TheCSBots()->IsWeaponUseable( csWeapon ) ) + continue; +#endif // CLIENT_DLL + + if ( bestWeapon ) + { + if ( weapon->GetSlot() < bestWeapon->GetSlot() ) + { + bestWeapon = weapon; + } + else if ( weapon->GetSlot() == bestWeapon->GetSlot() && weapon->GetPosition() < bestWeapon->GetPosition() ) + { + bestWeapon = weapon; + } + } + else + { + bestWeapon = weapon; + } + } + + return bestWeapon; +} + +float CCSGameRules::GetMapRemainingTime() +{ +#ifdef GAME_DLL + if ( nextlevel.GetString() && *nextlevel.GetString() ) + { + return 0; + } +#endif + + // if timelimit is disabled, return -1 + if ( mp_timelimit.GetInt() <= 0 ) + return -1; + + // timelimit is in minutes + float flTimeLeft = ( m_flGameStartTime + mp_timelimit.GetInt() * 60 ) - gpGlobals->curtime; + + // never return a negative value + if ( flTimeLeft < 0 ) + flTimeLeft = 0; + + return flTimeLeft; +} + +float CCSGameRules::GetMapElapsedTime( void ) +{ + return gpGlobals->curtime; +} + +float CCSGameRules::GetRoundRemainingTime() +{ + return (float) (m_fRoundStartTime + m_iRoundTime) - gpGlobals->curtime; +} + +float CCSGameRules::GetRoundStartTime() +{ + return m_fRoundStartTime; +} + + +float CCSGameRules::GetRoundElapsedTime() +{ + return gpGlobals->curtime - m_fRoundStartTime; +} + + +bool CCSGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) +{ + if ( collisionGroup0 > collisionGroup1 ) + { + // swap so that lowest is always first + ::V_swap(collisionGroup0,collisionGroup1); + } + + //Don't stand on COLLISION_GROUP_WEAPONs + if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT && + collisionGroup1 == COLLISION_GROUP_WEAPON ) + { + return false; + } + + // TODO: make a CS-SPECIFIC COLLISION GROUP FOR PHYSICS PROPS THAT USE THIS COLLISION BEHAVIOR. + + + if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && + collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) + { + return false; + } + + if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) + { + // let debris and multiplayer objects collide + return true; + } + + return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); +} + + +bool CCSGameRules::IsFreezePeriod() +{ + return m_bFreezePeriod; +} + + +bool CCSGameRules::IsVIPMap() const +{ + //MIKETODO: VIP mode + return false; +} + + +bool CCSGameRules::IsBombDefuseMap() const +{ + return m_bMapHasBombTarget; +} + +bool CCSGameRules::IsHostageRescueMap() const +{ + return m_bMapHasRescueZone; +} + +bool CCSGameRules::IsLogoMap() const +{ + return m_bLogoMap; +} + +float CCSGameRules::GetBuyTimeLength() const +{ + return ( mp_buytime.GetFloat() * 60 ); +} + +bool CCSGameRules::IsBuyTimeElapsed() +{ + return ( GetRoundElapsedTime() > GetBuyTimeLength() ); +} + +int CCSGameRules::DefaultFOV() +{ + return 90; +} + +const CViewVectors* CCSGameRules::GetViewVectors() const +{ + return &g_CSViewVectors; +} + + +//----------------------------------------------------------------------------- +// Purpose: Init CS ammo definitions +//----------------------------------------------------------------------------- + +// shared ammo definition +// JAY: Trying to make a more physical bullet response +#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) +#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) + +// exaggerate all of the forces, but use real numbers to keep them consistent +#define BULLET_IMPULSE_EXAGGERATION 1 + +// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s +#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) + + +static CCSAmmoDef ammoDef; +CCSAmmoDef* GetCSAmmoDef() +{ + GetAmmoDef(); // to initialize the ammo info + return &ammoDef; +} + +CAmmoDef* GetAmmoDef() +{ + static bool bInitted = false; + + if ( !bInitted ) + { + bInitted = true; + + ammoDef.AddAmmoType( BULLET_PLAYER_50AE, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_50AE_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); + ammoDef.AddAmmoType( BULLET_PLAYER_762MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_762mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); + ammoDef.AddAmmoType( BULLET_PLAYER_556MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_max", 2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); + ammoDef.AddAmmoType( BULLET_PLAYER_556MM_BOX, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_556mm_box_max",2400 * BULLET_IMPULSE_EXAGGERATION, 0, 10, 14 ); + ammoDef.AddAmmoType( BULLET_PLAYER_338MAG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_338mag_max", 2800 * BULLET_IMPULSE_EXAGGERATION, 0, 12, 16 ); + ammoDef.AddAmmoType( BULLET_PLAYER_9MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_9mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 5, 10 ); + ammoDef.AddAmmoType( BULLET_PLAYER_BUCKSHOT, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_buckshot_max", 600 * BULLET_IMPULSE_EXAGGERATION, 0, 3, 6 ); + ammoDef.AddAmmoType( BULLET_PLAYER_45ACP, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_45acp_max", 2100 * BULLET_IMPULSE_EXAGGERATION, 0, 6, 10 ); + ammoDef.AddAmmoType( BULLET_PLAYER_357SIG, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_357sig_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 ); + ammoDef.AddAmmoType( BULLET_PLAYER_57MM, DMG_BULLET, TRACER_LINE, 0, 0, "ammo_57mm_max", 2000 * BULLET_IMPULSE_EXAGGERATION, 0, 4, 8 ); + ammoDef.AddAmmoType( AMMO_TYPE_HEGRENADE, DMG_BLAST, TRACER_LINE, 0, 0, "ammo_hegrenade_max", 1, 0 ); + ammoDef.AddAmmoType( AMMO_TYPE_FLASHBANG, 0, TRACER_LINE, 0, 0, "ammo_flashbang_max", 1, 0 ); + ammoDef.AddAmmoType( AMMO_TYPE_SMOKEGRENADE, 0, TRACER_LINE, 0, 0, "ammo_smokegrenade_max", 1, 0 ); + + //Adrian: I set all the prices to 0 just so the rest of the buy code works + //This should be revisited. + ammoDef.AddAmmoCost( BULLET_PLAYER_50AE, 0, 7 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_762MM, 0, 30 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_556MM, 0, 30 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_556MM_BOX, 0, 30 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_338MAG, 0, 10 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_9MM, 0, 30 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_BUCKSHOT, 0, 8 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_45ACP, 0, 25 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_357SIG, 0, 13 ); + ammoDef.AddAmmoCost( BULLET_PLAYER_57MM, 0, 50 ); + } + + return &ammoDef; +} + +#ifndef CLIENT_DLL +const char *CCSGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer ) +{ + const char *pszPrefix = NULL; + + if ( !pPlayer ) // dedicated server output + { + pszPrefix = ""; + } + else + { + // team only + if ( bTeamOnly == TRUE ) + { + if ( pPlayer->GetTeamNumber() == TEAM_CT ) + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + pszPrefix = "(Counter-Terrorist)"; + } + else + { + pszPrefix = "*DEAD*(Counter-Terrorist)"; + } + } + else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + pszPrefix = "(Terrorist)"; + } + else + { + pszPrefix = "*DEAD*(Terrorist)"; + } + } + else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) + { + pszPrefix = "(Spectator)"; + } + } + // everyone + else + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + pszPrefix = ""; + } + else + { + if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) + { + pszPrefix = "*DEAD*"; + } + else + { + pszPrefix = "*SPEC*"; + } + } + } + } + + return pszPrefix; +} + +const char *CCSGameRules::GetChatLocation( bool bTeamOnly, CBasePlayer *pPlayer ) +{ + if ( !pPlayer ) // dedicated server output + { + return NULL; + } + + // only teammates see locations + if ( !bTeamOnly ) + return NULL; + + // only living players have locations + if ( pPlayer->GetTeamNumber() != TEAM_CT && pPlayer->GetTeamNumber() != TEAM_TERRORIST ) + return NULL; + + if ( !pPlayer->IsAlive() ) + return NULL; + + return pPlayer->GetLastKnownPlaceName(); +} + +const char *CCSGameRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ) +{ + if ( !pPlayer ) // dedicated server output + { + return NULL; + } + + const char *pszFormat = NULL; + + // team only + if ( bTeamOnly == TRUE ) + { + if ( pPlayer->GetTeamNumber() == TEAM_CT ) + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); + if ( chatLocation && *chatLocation ) + { + pszFormat = "Cstrike_Chat_CT_Loc"; + } + else + { + pszFormat = "Cstrike_Chat_CT"; + } + } + else + { + pszFormat = "Cstrike_Chat_CT_Dead"; + } + } + else if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST ) + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); + if ( chatLocation && *chatLocation ) + { + pszFormat = "Cstrike_Chat_T_Loc"; + } + else + { + pszFormat = "Cstrike_Chat_T"; + } + } + else + { + pszFormat = "Cstrike_Chat_T_Dead"; + } + } + else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) + { + pszFormat = "Cstrike_Chat_Spec"; + } + } + // everyone + else + { + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + pszFormat = "Cstrike_Chat_All"; + } + else + { + if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) + { + pszFormat = "Cstrike_Chat_AllDead"; + } + else + { + pszFormat = "Cstrike_Chat_AllSpec"; + } + } + } + + return pszFormat; +} + +void CCSGameRules::ClientSettingsChanged( CBasePlayer *pPlayer ) +{ + const char *pszNewName = engine->GetClientConVarValue( pPlayer->entindex(), "name" ); + const char *pszOldName = pPlayer->GetPlayerName(); + CCSPlayer *pCSPlayer = (CCSPlayer*)pPlayer; + if ( pszOldName[0] != 0 && Q_strncmp( pszOldName, pszNewName, MAX_PLAYER_NAME_LENGTH-1 ) ) + { + pCSPlayer->ChangeName( pszNewName ); + } + + pCSPlayer->m_bShowHints = true; + if ( pCSPlayer->IsNetClient() ) + { + const char *pShowHints = engine->GetClientConVarValue( engine->IndexOfEdict( pCSPlayer->edict() ), "cl_autohelp" ); + if ( pShowHints && atoi( pShowHints ) <= 0 ) + { + pCSPlayer->m_bShowHints = false; + } + } +} + +bool CCSGameRules::FAllowNPCs( void ) +{ + return false; +} + +bool CCSGameRules::IsFriendlyFireOn( void ) +{ + return friendlyfire.GetBool(); +} + + +CON_COMMAND( map_showspawnpoints, "Shows player spawn points (red=invalid)" ) +{ + CSGameRules()->ShowSpawnPoints(); +} + +void DrawSphere( const Vector& pos, float radius, int r, int g, int b, float lifetime ) +{ + Vector edge, lastEdge; + NDebugOverlay::Line( pos, pos + Vector( 0, 0, 50 ), r, g, b, true, lifetime ); + + lastEdge = Vector( radius + pos.x, pos.y, pos.z ); + float angle; + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = radius * BotCOS( angle ) + pos.x; + edge.y = pos.y; + edge.z = radius * BotSIN( angle ) + pos.z; + + NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); + + lastEdge = edge; + } + + lastEdge = Vector( pos.x, radius + pos.y, pos.z ); + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = pos.x; + edge.y = radius * BotCOS( angle ) + pos.y; + edge.z = radius * BotSIN( angle ) + pos.z; + + NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); + + lastEdge = edge; + } + + lastEdge = Vector( pos.x, radius + pos.y, pos.z ); + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = radius * BotCOS( angle ) + pos.x; + edge.y = radius * BotSIN( angle ) + pos.y; + edge.z = pos.z; + + NDebugOverlay::Line( edge, lastEdge, r, g, b, true, lifetime ); + + lastEdge = edge; + } +} + +CON_COMMAND_F( map_showbombradius, "Shows bomb radius from the center of each bomb site and planted bomb.", FCVAR_CHEAT ) +{ + float flBombDamage = 500.0f; + if ( g_pMapInfo ) + flBombDamage = g_pMapInfo->m_flBombRadius; + float flBombRadius = flBombDamage * 3.5f; + Msg( "Bomb Damage is %.0f, Radius is %.0f\n", flBombDamage, flBombRadius ); + + CBaseEntity* ent = NULL; + while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL ) + { + const Vector &pos = ent->WorldSpaceCenter(); + DrawSphere( pos, flBombRadius, 255, 255, 0, 10 ); + } + + ent = NULL; + while ( ( ent = gEntList.FindEntityByClassname( ent, "planted_c4" ) ) != NULL ) + { + const Vector &pos = ent->WorldSpaceCenter(); + DrawSphere( pos, flBombRadius, 255, 0, 0, 10 ); + } +} + +CON_COMMAND_F( map_setbombradius, "Sets the bomb radius for the map.", FCVAR_CHEAT ) +{ + if ( args.ArgC() != 2 ) + return; + + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( !g_pMapInfo ) + CBaseEntity::Create( "info_map_parameters", vec3_origin, vec3_angle ); + + if ( !g_pMapInfo ) + return; + + g_pMapInfo->m_flBombRadius = atof( args[1] ); + map_showbombradius( args ); +} + +void CreateBlackMarketString( void ) +{ + g_StringTableBlackMarket = networkstringtable->CreateStringTable( "BlackMarketTable" , 1 ); +} + +int CCSGameRules::GetStartMoney( void ) +{ + if ( IsBlackMarket() ) + { + return atoi( mp_startmoney.GetDefault() ); + } + + return mp_startmoney.GetInt(); +} + + + +//============================================================================= +// HPE_BEGIN: +// [menglish] Set up anything for all players that changes based on new players spawning mid-game +// Find and return fun fact data +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: Called when a player joins the game after it's started yet can still spawn in +//----------------------------------------------------------------------------- +void CCSGameRules::SpawningLatePlayer( CCSPlayer* pLatePlayer ) +{ + //Reset the round kills number of enemies for the opposite team + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + if(pPlayer) + { + if(pPlayer->GetTeamNumber() == pLatePlayer->GetTeamNumber()) + { + continue; + } + pPlayer->m_NumEnemiesAtRoundStart++; + } + } +} + +//============================================================================= +// HPE_END +//============================================================================= + +//============================================================================= +// HPE_BEGIN: +// [pfreese] Test for "pistol" round, defined as the default starting round +// when players cannot purchase anything primary weapons +//============================================================================= + +bool CCSGameRules::IsPistolRound() +{ + return m_iTotalRoundsPlayed == 0 && GetStartMoney() <= 800; +} + +//============================================================================= +// HPE_END +//============================================================================= + +//============================================================================= +// HPE_BEGIN: +// [tj] So game rules can react to damage taken +// [menglish] +//============================================================================= + +void CCSGameRules::PlayerTookDamage(CCSPlayer* player, const CTakeDamageInfo &damageInfo) +{ + CBaseEntity *pInflictor = damageInfo.GetInflictor(); + CBaseEntity *pAttacker = damageInfo.GetAttacker(); + CCSPlayer *pCSScorer = (CCSPlayer *)(GetDeathScorer( pAttacker, pInflictor )); + + if ( player && pCSScorer ) + { + if (player->GetTeamNumber() == TEAM_CT) + { + m_bNoCTsDamaged = false; + } + + if (player->GetTeamNumber() == TEAM_TERRORIST) + { + m_bNoTerroristsDamaged = false; + } + // set the first blood if this is the first and the victim is on a different team then the player + if ( m_pFirstBlood == NULL && pCSScorer != player && pCSScorer->GetTeamNumber() != player ->GetTeamNumber() ) + { + m_pFirstBlood = pCSScorer; + m_firstBloodTime = gpGlobals->curtime - m_fRoundStartTime; + } + } +} + + +//============================================================================= +// HPE_END +//============================================================================= +#endif + +bool CCSGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ) +{ +#ifdef GAME_DLL + if( pPlayer ) + { + int iPlayerTeam = pPlayer->GetTeamNumber(); + if( ( iPlayerTeam == TEAM_CT ) || ( iPlayerTeam == TEAM_TERRORIST ) ) + return false; + } +#else + int iLocalPlayerTeam = GetLocalPlayerTeam(); + if( ( iLocalPlayerTeam == TEAM_CT ) || ( iLocalPlayerTeam == TEAM_TERRORIST ) ) + return false; +#endif + + return true; +} + +#ifdef GAME_DLL + +struct convar_tags_t +{ + const char *pszConVar; + const char *pszTag; +}; + +// The list of convars that automatically turn on tags when they're changed. +// Convars in this list need to have the FCVAR_NOTIFY flag set on them, so the +// tags are recalculated and uploaded to the master server when the convar is changed. +convar_tags_t convars_to_check_for_tags[] = +{ + { "mp_friendlyfire", "friendlyfire" }, + { "bot_quota", "bots" }, + { "sv_nostats", "nostats" }, + { "mp_startmoney", "startmoney" }, + { "sv_allowminmodels", "nominmodels" }, + { "sv_enablebunnyhopping", "bunnyhopping" }, + { "sv_competitive_minspec", "compspec" }, + { "mp_holiday_nogifts", "nogifts" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: Engine asks for the list of convars that should tag the server +//----------------------------------------------------------------------------- +void CCSGameRules::GetTaggedConVarList( KeyValues *pCvarTagList ) +{ + BaseClass::GetTaggedConVarList( pCvarTagList ); + + for ( int i = 0; i < ARRAYSIZE(convars_to_check_for_tags); i++ ) + { + KeyValues *pKV = new KeyValues( "tag" ); + pKV->SetString( "convar", convars_to_check_for_tags[i].pszConVar ); + pKV->SetString( "tag", convars_to_check_for_tags[i].pszTag ); + + pCvarTagList->AddSubKey( pKV ); + } +} + +#endif + + +int CCSGameRules::GetBlackMarketPriceForWeapon( int iWeaponID ) +{ + if ( m_pPrices == NULL ) + { + GetBlackMarketPriceList(); + } + + if ( m_pPrices ) + return m_pPrices->iCurrentPrice[iWeaponID]; + else + return 0; +} + +int CCSGameRules::GetBlackMarketPreviousPriceForWeapon( int iWeaponID ) +{ + if ( m_pPrices == NULL ) + { + GetBlackMarketPriceList(); + } + + if ( m_pPrices ) + return m_pPrices->iPreviousPrice[iWeaponID]; + else + return 0; +} + +const weeklyprice_t *CCSGameRules::GetBlackMarketPriceList( void ) +{ + if ( m_StringTableBlackMarket == NULL ) + { + m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME); + } + + if ( m_pPrices == NULL ) + { + int iSize = 0; + INetworkStringTable *pTable = m_StringTableBlackMarket; + if ( pTable && pTable->GetNumStrings() > 0 ) + { + m_pPrices = (const weeklyprice_t *)pTable->GetStringUserData( 0, &iSize ); + } + } + + if ( m_pPrices ) + { + PrepareEquipmentInfo(); + } + + return m_pPrices; +} + +void CCSGameRules::SetBlackMarketPrices( bool bSetDefaults ) +{ + for ( int i = 1; i < WEAPON_MAX; i++ ) + { + if ( i == WEAPON_SHIELDGUN ) + continue; + + CCSWeaponInfo *info = GetWeaponInfo( (CSWeaponID)i ); + + if ( info == NULL ) + continue; + + if ( bSetDefaults == false ) + { + info->SetWeaponPrice( GetBlackMarketPriceForWeapon( i ) ); + info->SetPreviousPrice( GetBlackMarketPreviousPriceForWeapon( i ) ); + } + else + { + info->SetWeaponPrice( info->GetDefaultPrice() ); + } + } +} + +#ifdef CLIENT_DLL + +CCSGameRules::CCSGameRules() +{ + CSGameRules()->m_StringTableBlackMarket = NULL; + m_pPrices = NULL; + m_bBlackMarket = false; +} + +void TestTable( void ) +{ + CSGameRules()->m_StringTableBlackMarket = networkstringtable->FindTable( CS_GAMERULES_BLACKMARKET_TABLE_NAME); + + if ( CSGameRules()->m_StringTableBlackMarket == NULL ) + return; + + int iIndex = CSGameRules()->m_StringTableBlackMarket->FindStringIndex( "blackmarket_prices" ); + int iSize = 0; + + const weeklyprice_t *pPrices = NULL; + + pPrices = (const weeklyprice_t *)(CSGameRules()->m_StringTableBlackMarket)->GetStringUserData( iIndex, &iSize ); +} + +#ifdef DEBUG +ConCommand cs_testtable( "cs_testtable", TestTable ); +#endif + +//----------------------------------------------------------------------------- +// Enforce certain values on the specified convar. +//----------------------------------------------------------------------------- +void EnforceCompetitiveCVar( const char *szCvarName, float fMinValue, float fMaxValue = FLT_MAX, int iArgs = 0, ... ) +{ + // Doing this check first because OK values might be outside the min/max range + ConVarRef competitiveConvar(szCvarName); + float fValue = competitiveConvar.GetFloat(); + va_list vl; + va_start(vl, iArgs); + for( int i=0; i< iArgs; ++i ) + { + if( (int)fValue == (int)va_arg(vl,double) ) + return; + } + va_end(vl); + + if( fValue < fMinValue || fValue > fMaxValue ) + { + float fNewValue = MAX( MIN( fValue, fMaxValue ), fMinValue ); + competitiveConvar.SetValue( fNewValue ); + DevMsg( "Convar %s enforced by server (see sv_competitive_minspec.) Set to %2f.\n", szCvarName, fNewValue ); + } +} + +//----------------------------------------------------------------------------- +// An interface used by ENABLE_COMPETITIVE_CONVAR macro that lets the classes +// defined in the macro to be stored and acted on. +//----------------------------------------------------------------------------- +class ICompetitiveConvar +{ +public: + // It is a best practice to always have a virtual destructor in an interface + // class. Otherwise if the derived classes have destructors they will not be + // called. + virtual ~ICompetitiveConvar() {} + virtual void BackupConvar() = 0; + virtual void EnforceRestrictions() = 0; + virtual void RestoreOriginalValue() = 0; + virtual void InstallChangeCallback() = 0; +}; + +//----------------------------------------------------------------------------- +// A manager for all enforced competitive convars. +//----------------------------------------------------------------------------- +class CCompetitiveCvarManager : public CAutoGameSystem +{ +public: + typedef CUtlVector<ICompetitiveConvar*> CompetitiveConvarList_t; + static void AddConvarToList( ICompetitiveConvar* pCVar ) + { + GetConvarList()->AddToTail( pCVar ); + } + + static void BackupAllConvars() + { + FOR_EACH_VEC( *GetConvarList(), i ) + { + (*GetConvarList())[i]->BackupConvar(); + } + } + + static void EnforceRestrictionsOnAllConvars() + { + FOR_EACH_VEC( *GetConvarList(), i ) + { + (*GetConvarList())[i]->EnforceRestrictions(); + } + } + + static void RestoreAllOriginalValues() + { + FOR_EACH_VEC( *GetConvarList(), i ) + { + (*GetConvarList())[i]->RestoreOriginalValue(); + } + } + + static CompetitiveConvarList_t* GetConvarList() + { + if( !s_pCompetitiveConvars ) + { + s_pCompetitiveConvars = new CompetitiveConvarList_t(); + } + return s_pCompetitiveConvars; + } + + static KeyValues* GetConVarBackupKV() + { + if( !s_pConVarBackups ) + { + s_pConVarBackups = new KeyValues("ConVarBackups"); + } + return s_pConVarBackups; + } + + virtual bool Init() + { + FOR_EACH_VEC( *GetConvarList(), i ) + { + (*GetConvarList())[i]->InstallChangeCallback(); + } + return true; + } + + virtual void Shutdown() + { + FOR_EACH_VEC( *GetConvarList(), i ) + { + delete (*GetConvarList())[i]; + } + delete s_pCompetitiveConvars; + s_pCompetitiveConvars = null; + s_pConVarBackups->deleteThis(); + s_pConVarBackups = null; + } +private: + static CompetitiveConvarList_t* s_pCompetitiveConvars; + static KeyValues* s_pConVarBackups; +}; +static CCompetitiveCvarManager *s_pCompetitiveCvarManager = new CCompetitiveCvarManager(); +CCompetitiveCvarManager::CompetitiveConvarList_t* CCompetitiveCvarManager::s_pCompetitiveConvars = null; +KeyValues* CCompetitiveCvarManager::s_pConVarBackups = null; + +//----------------------------------------------------------------------------- +// Macro to define restrictions on convars with "sv_competitive_minspec 1" +// Usage: ENABLE_COMPETITIVE_CONVAR( convarName, minValue, maxValue, optionalValues, opVal1, opVal2, ... +//----------------------------------------------------------------------------- +#define ENABLE_COMPETITIVE_CONVAR( convarName, ... ) \ +class CCompetitiveMinspecConvar##convarName : public ICompetitiveConvar { \ +public: \ + CCompetitiveMinspecConvar##convarName(){ CCompetitiveCvarManager::AddConvarToList(this);} \ + static void on_changed_##convarName( IConVar *var, const char *pOldValue, float flOldValue ){ \ + if( sv_competitive_minspec.GetBool() ) { \ + EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); }\ + else {\ + CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } } \ + virtual void BackupConvar() { CCompetitiveCvarManager::GetConVarBackupKV()->SetFloat( #convarName, ConVarRef( #convarName ).GetFloat() ); } \ + virtual void EnforceRestrictions() { EnforceCompetitiveCVar( #convarName , __VA_ARGS__ ); } \ + virtual void RestoreOriginalValue() { ConVarRef(#convarName).SetValue(CCompetitiveCvarManager::GetConVarBackupKV()->GetFloat( #convarName ) ); } \ + virtual void InstallChangeCallback() { static_cast<ConVar*>(ConVarRef( #convarName ).GetLinkedConVar())->InstallChangeCallback( CCompetitiveMinspecConvar##convarName::on_changed_##convarName); } \ +}; \ +static CCompetitiveMinspecConvar##convarName *s_pCompetitiveConvar##convarName = new CCompetitiveMinspecConvar##convarName(); + +//----------------------------------------------------------------------------- +// Callback function for sv_competitive_minspec convar value change. +//----------------------------------------------------------------------------- +void sv_competitive_minspec_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + ConVar *pCvar = static_cast<ConVar*>(var); + + if( pCvar->GetBool() == true && (bool)flOldValue == false ) + { + // Backup the values of each cvar and enforce new ones + CCompetitiveCvarManager::BackupAllConvars(); + CCompetitiveCvarManager::EnforceRestrictionsOnAllConvars(); + } + else if( pCvar->GetBool() == false && (bool)flOldValue == true ) + { + // If sv_competitive_minspec is disabled, restore old client values + CCompetitiveCvarManager::RestoreAllOriginalValues(); + } +} +#endif + +static ConVar sv_competitive_minspec( "sv_competitive_minspec", + "0", + FCVAR_REPLICATED | FCVAR_NOTIFY, + "Enable to force certain client convars to minimum/maximum values to help prevent competitive advantages:\n \ + r_drawdetailprops = 1\n \ + r_staticprop_lod = minimum -1 maximum 3\n \ + fps_max minimum 59 (0 works too)\n \ + cl_detailfade minimum 400\n \ + cl_detaildist minimum 1200\n \ + cl_interp_ratio = minimum 1 maximum 2\n \ + cl_interp = minimum 0 maximum 0.031\n \ + " +#ifdef CLIENT_DLL + ,sv_competitive_minspec_changed_f +#endif + ); + +#ifdef CLIENT_DLL + +ENABLE_COMPETITIVE_CONVAR( r_drawdetailprops, true, true ); // force r_drawdetailprops on +ENABLE_COMPETITIVE_CONVAR( r_staticprop_lod, -1, 3 ); // force r_staticprop_lod from -1 to 3 +ENABLE_COMPETITIVE_CONVAR( fps_max, 59, FLT_MAX, 1, 0 ); // force fps_max above 59. One additional value (0) works +ENABLE_COMPETITIVE_CONVAR( cl_detailfade, 400 ); // force cl_detailfade above 400. +ENABLE_COMPETITIVE_CONVAR( cl_detaildist, 1200 ); // force cl_detaildist above 1200. +ENABLE_COMPETITIVE_CONVAR( cl_interp_ratio, 1, 2 ); // force cl_interp_ratio from 1 to 2 +ENABLE_COMPETITIVE_CONVAR( cl_interp, 0, 0.031 ); // force cl_interp from 0.0152 to 0.031 + +// Stubs for replay client code +const char *GetMapDisplayName( const char *pMapName ) +{ + return pMapName; +} + +bool IsTakingAFreezecamScreenshot() +{ + return false; +} + +#endif diff --git a/game/shared/cstrike/cs_gamerules.h b/game/shared/cstrike/cs_gamerules.h new file mode 100644 index 0000000..02bda1b --- /dev/null +++ b/game/shared/cstrike/cs_gamerules.h @@ -0,0 +1,537 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The TF Game rules object +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef CS_GAMERULES_H +#define CS_GAMERULES_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "teamplay_gamerules.h" +#include "convar.h" +#include "cs_shareddefs.h" +#include "gamevars_shared.h" + +#ifdef CLIENT_DLL + #include "c_cs_player.h" + #include "networkstringtable_clientdll.h" +#else + #include "cs_player.h" + #include "funfactmgr_cs.h" +#endif + +#include "cs_urlretrieveprices.h" + +//extern ConVar mp_dynamicpricing; + +#define CS_GAMERULES_BLACKMARKET_TABLE_NAME "BlackMarketTable" + +#define WINNER_NONE 0 +#define WINNER_DRAW 1 +#define WINNER_TER TEAM_TERRORIST +#define WINNER_CT TEAM_CT + +//============================================================================= +// HPE_BEGIN: +// [tj] Forward declaration so we can track bot suicides in the game rules. +//============================================================================= + +class CCSBot; + +//============================================================================= +// HPE_END +//============================================================================= + +extern ConVar mp_startmoney; +extern ConVar mp_tkpunish; +extern ConVar mp_c4timer; +extern ConVar mp_buytime; +extern ConVar mp_freezetime; +extern ConVar mp_playerid; + +#ifndef CLIENT_DLL + extern ConVar mp_autoteambalance; +#endif // !CLIENT_DLL + + +#ifdef CLIENT_DLL + #define CCSGameRules C_CSGameRules + #define CCSGameRulesProxy C_CSGameRulesProxy +#endif + +#ifndef CLIENT_DLL + struct playerscore_t + { + int iPlayerIndex; + int iScore; + }; +#endif + + +class CCSGameRulesProxy : public CGameRulesProxy +{ +public: + DECLARE_CLASS( CCSGameRulesProxy, CGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + + +class CCSGameRules : public CTeamplayRules +{ +public: + DECLARE_CLASS( CCSGameRules, CTeamplayRules ); + + // Stuff that is shared between client and server. + bool IsFreezePeriod(); + + virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ); + + float GetMapRemainingTime(); // time till end of map, -1 if timelimit is disabled + float GetMapElapsedTime(); // How much time has elapsed since the map started. + float GetRoundRemainingTime(); // time till end of round + float GetRoundStartTime(); // When this round started. + float GetRoundElapsedTime(); // How much time has elapsed since the round started. + float GetBuyTimeLength() const; + int GetRoundLength() const { return m_iRoundTime; } + int SelectDefaultTeam( bool ignoreBots = false ); + int GetHumanTeam(); // TEAM_UNASSIGNED if no restrictions + + bool IsVIPMap() const; + bool IsBombDefuseMap() const; + bool IsHostageRescueMap() const; + bool IsIntermission() const; + bool IsLogoMap() const; + bool IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ); + + bool IsBuyTimeElapsed(); + + virtual int DefaultFOV(); + + // Get the view vectors for this mod. + virtual const CViewVectors* GetViewVectors() const; + + void UploadGameStats( void ); + int GetStartMoney( void ); + + virtual bool IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ); + +private: + + float GetExplosionDamageAdjustment(Vector & vecSrc, Vector & vecEnd, CBaseEntity *pEntityToIgnore); // returns multiplier between 0.0 and 1.0 that is the percentage of any damage done from vecSrc to vecEnd that actually makes it. + float GetAmountOfEntityVisible(Vector & src, CBaseEntity *player); // returns a value from 0 to 1 that is the percentage of player visible from src. + + CNetworkVar( bool, m_bFreezePeriod ); // TRUE at beginning of round, set to FALSE when the period expires + CNetworkVar( int, m_iRoundTime ); // (From mp_roundtime) - How many seconds long this round is. + CNetworkVar( float, m_fRoundStartTime ); // time round has started + CNetworkVar( float, m_flGameStartTime ); + CNetworkVar( int, m_iHostagesRemaining ); + CNetworkVar( bool, m_bMapHasBombTarget ); + CNetworkVar( bool, m_bMapHasRescueZone ); + CNetworkVar( bool, m_bLogoMap ); // If there's an info_player_logo entity, then it's a logo map. + CNetworkVar( bool, m_bBlackMarket ); + + bool m_bDontUploadStats; + +public: + + bool IsBlackMarket( void ) { return m_bBlackMarket; } + int GetNumHostagesRemaining( void ) { return m_iHostagesRemaining; } + + virtual CBaseCombatWeapon *GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ); + + virtual const unsigned char *GetEncryptionKey( void ) { return (unsigned char *)"d7NSuLq2"; } // both the client and server need this key + +#ifdef CLIENT_DLL + + DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. + CCSGameRules(); + +#else + + DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. + + CCSGameRules(); + virtual ~CCSGameRules(); + + void DumpTimers( void ) const; // debugging to help track down a stuck server (rare?) + + CBaseEntity *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + static void EndRound(); + + virtual void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + virtual void Think(); + + // Called at the end of GameFrame (i.e. after all game logic has run this frame) + virtual void EndGameFrame( void ); + + // Called when game rules are destroyed by CWorld + virtual void LevelShutdown( void ); + + virtual bool ClientCommand( CBaseEntity *pEdict, const CCommand &args ); + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + void ShowSpawnPoints(); + + virtual void ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ); + + //============================================================================= + // HPE_BEGIN: + // [menglish] Set up anything for all players that changes based on new players spawning mid-game + // Find and return fun fact data + // [pfreese] Tracking of "pistol" round + //============================================================================= + virtual void SpawningLatePlayer(CCSPlayer* pLatePlayer); + + bool IsPistolRound(); + + void HostageKilled() { m_hostageWasKilled = true; } + void HostageInjured() { m_hostageWasInjured = true; } + + bool WasHostageKilled() { return m_hostageWasKilled; } + bool WasHostageInjured() { return m_hostageWasInjured; } + + //============================================================================= + // HPE_END + //============================================================================= + + //============================================================================= + // HPE_BEGIN: + // [tj] So game rules can react to damage taken + //============================================================================= + + void PlayerTookDamage(CCSPlayer* player, const CTakeDamageInfo &damageInfo); + + //============================================================================= + // HPE_END + //============================================================================= + + + virtual bool PlayTextureSounds( void ) { return true; } + // Let the game rules specify if fall death should fade screen to black + virtual bool FlPlayerFallDeathDoesScreenFade( CBasePlayer *pl ) { return FALSE; } + + virtual void RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore ); + void RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, bool bIgnoreWorld ); + + virtual void UpdateClientData( CBasePlayer *pl ); + + // Death notices + virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + + virtual void InitDefaultAIRelationships( void ); + + virtual const char *GetGameDescription( void ) { return "Counter-Strike: Source"; } // this is the game name that gets seen in the server browser + virtual const char *AIClassText(int classType); + + virtual bool FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ); + + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + + // Called before entities are created + virtual void LevelInitPreEntity(); + + // Called after the map has finished loading. + virtual void LevelInitPostEntity(); + + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + + virtual void ClientDisconnected( edict_t *pClient ); + + // Recreate all the map entities from the map data (preserving their indices), + // then remove everything else except the players. + // Also get rid of all world decals. + void CleanUpMap(); + + void CheckFreezePeriodExpired(); + void CheckRoundTimeExpired(); + + // check if the scenario has been won/lost + // return true if the scenario is over, false if the scenario is still in progress + bool CheckWinConditions( void ); + + void TerminateRound( float tmDelay, int reason ); + + //============================================================================= + // HPE_BEGIN: + // [tj] A place to check achievements that occur at the end of the round + //============================================================================= + void ProcessEndOfRoundAchievements(int iWinnerTeam, int iReason); + //============================================================================= + // HPE_END + //============================================================================= + + void RestartRound( void ); + void BalanceTeams( void ); + void MoveHumansToHumanTeam( void ); + bool TeamFull( int team_id ); + bool TeamStacked( int newTeam_id, int curTeam_id ); + bool FPlayerCanRespawn( CBasePlayer *pPlayer ); + void UpdateTeamScores(); + void CheckMapConditions(); + void MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int team); + + // Check various conditions to end the map. + bool CheckGameOver(); + bool CheckMaxRounds(); + bool CheckWinLimit(); + bool CheckFragLimit(); + + void CheckLevelInitialized(); + void CheckRestartRound(); + + + // Checks if it still needs players to start a round, or if it has enough players to start rounds. + // Starts a round and returns true if there are enough players. + bool NeededPlayersCheck( bool &bNeededPlayers ); + + // Setup counts for m_iNumTerrorist, m_iNumCT, m_iNumSpawnableTerrorist, m_iNumSpawnableCT, etc. + void InitializePlayerCounts( + int &NumAliveTerrorist, + int &NumAliveCT, + int &NumDeadTerrorist, + int &NumDeadCT + ); + + // Check to see if the round is over for the various game types. Terminates the round + // and returns true if the round should end. + bool PrisonRoundEndCheck(); + bool BombRoundEndCheck( bool bNeededPlayers ); + bool HostageRescueRoundEndCheck( bool bNeededPlayers ); + + // Check to see if the teams exterminated each other. Ends the round and returns true if so. + bool TeamExterminationCheck( + int NumAliveTerrorist, + int NumAliveCT, + int NumDeadTerrorist, + int NumDeadCT, + bool bNeededPlayers + ); + + void ReadMultiplayCvars(); + void SwapAllPlayers(); + + void BroadcastSound( const char *sound, int team = -1 ); + + + // VIP FUNCTIONS + bool VIPRoundEndCheck( bool bNeededPlayers ); + void PickNextVIP(); + + + // BOMB MAP FUNCTIONS + void GiveC4(); + bool IsThereABomber(); + bool IsThereABomb(); + + // HOSTAGE MAP FUNCTIONS + void HostageTouched(); + + + // Sets up g_pPlayerResource. + virtual void CreateStandardEntities(); + virtual const char *GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer ); + virtual const char *GetChatLocation( bool bTeamOnly, CBasePlayer *pPlayer ); + virtual const char *GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ); + void ClientSettingsChanged( CBasePlayer *pPlayer ); + + bool IsCareer( void ) const { return false; } // returns true if this is a CZ "career" game + + virtual bool FAllowNPCs( void ); + +protected: + virtual void GoToIntermission( void ); + +public: + + bool IsFriendlyFireOn(); + + virtual void SetAllowWeaponSwitch( bool allow ); + virtual bool GetAllowWeaponSwitch( void ); + + // VARIABLES FOR ALL TYPES OF MAPS + bool m_bLevelInitialized; + int m_iRoundWinStatus; // 1 == CT's won last round, 2 == Terrorists did, 3 == Draw, no winner + int m_iTotalRoundsPlayed; + int m_iUnBalancedRounds; // keeps track of the # of consecutive rounds that have gone by where one team outnumbers the other team by more than 2 + + // GAME TIMES + int m_iFreezeTime; // (From mp_freezetime) - How many seconds long the intro round (when players are frozen) is. + float m_flRestartRoundTime; // the global time when the round is supposed to end, if this is not 0 + + int m_iNumTerrorist; // The number of terrorists on the team (this is generated at the end of a round) + int m_iNumCT; // The number of CTs on the team (this is generated at the end of a round) + int m_iNumSpawnableTerrorist; + int m_iNumSpawnableCT; + + bool m_bFirstConnected; + bool m_bCompleteReset; // Set to TRUE to have the scores reset next time round restarts + + int m_iAccountTerrorist; + int m_iAccountCT; + + short m_iNumCTWins; + short m_iNumTerroristWins; + + int m_iNumConsecutiveCTLoses; //SupraFiend: the number of rounds the CTs have lost in a row. + int m_iNumConsecutiveTerroristLoses;//SupraFiend: the number of rounds the Terrorists have lost in a row. + + int m_iSpawnPointCount_Terrorist; // Number of Terrorist spawn points + int m_iSpawnPointCount_CT; // Number of CT spawn points + + bool m_bTCantBuy; // Who can and can't buy. + bool m_bCTCantBuy; + bool m_bMapHasBuyZone; + + int m_iLoserBonus; // SupraFiend: the amount of money the losing team gets. This scales up as they lose more rounds in a row + float m_tmNextPeriodicThink; + + + // HOSTAGE RESCUE VARIABLES + int m_iHostagesRescued; + int m_iHostagesTouched; + float m_flNextHostageAnnouncement; + + //============================================================================= + // HPE_BEGIN + //============================================================================= + + // [tj] Accessor for weapons donation ability + bool GetCanDonateWeapon() { return m_bCanDonateWeapons; } + + // [tj] flawless and lossless round related flags + bool m_bNoTerroristsKilled; + bool m_bNoCTsKilled; + bool m_bNoTerroristsDamaged; + bool m_bNoCTsDamaged; + + // [tj] Find out if dropped weapons count as donations + bool m_bCanDonateWeapons; + + // [tj] Keep track of first kill + CHandle<CCSPlayer> m_pFirstKill; + float m_firstKillTime; + + // [menglish] Keep track of first blood + CHandle<CCSPlayer> m_pFirstBlood; + float m_firstBloodTime; + + + // [dwenger] Rescue-related achievement values + CHandle<CCSPlayer> m_pLastRescuer; + int m_iNumRescuers; + + bool m_hostageWasInjured; + bool m_hostageWasKilled; + + // [menglish] Fun Fact Manager + CCSFunFactMgr *m_pFunFactManager; + + // [tj] To avoid rewriting the same piece of code, we can get all the information + // we want from one call that fills in an array of structures. + struct TeamPlayerCounts + { + int totalPlayers; + int totalAlivePlayers; + int totalDeadPlayers; //sum of killedPlayers + suicidedPlayers + unenteredPlayers + int killedPlayers; + int suicidedPlayers; + int unenteredPlayers; + }; + + void GetPlayerCounts(TeamPlayerCounts teamCounts[TEAM_MAXCOUNT]); + + //============================================================================= + // HPE_END + //============================================================================= + + + // PRISON ESCAPE VARIABLES + int m_iHaveEscaped; + bool m_bMapHasEscapeZone; + int m_iNumEscapers; + int m_iNumEscapeRounds; // keeps track of the # of consecutive rounds of escape played.. Teams will be swapped after 8 rounds + + + // VIP VARIABLES + int m_iMapHasVIPSafetyZone; // 0 = uninitialized; 1 = has VIP safety zone; 2 = DOES not have VIP safetyzone + CHandle<CCSPlayer> m_pVIP; + int m_iConsecutiveVIP; + + + // BOMB MAP VARIABLES + bool m_bTargetBombed; // whether or not the bomb has been bombed + bool m_bBombDefused; // whether or not the bomb has been defused + bool m_bMapHasBombZone; + bool m_bBombDropped; + bool m_bBombPlanted; + EHANDLE m_pLastBombGuy; + +private: + + // Don't allow switching weapons while gaining new technologies + bool m_bAllowWeaponSwitch; + +public: + + + + void AddPricesToTable( weeklyprice_t prices ); + virtual void CreateCustomNetworkStringTables( void ); + +#endif + + +#ifdef GAME_DLL +public: + virtual void GetTaggedConVarList( KeyValues *pCvarTagList ); +#endif + +public: + const weeklyprice_t *GetBlackMarketPriceList( void ); + + int GetBlackMarketPriceForWeapon( int iWeaponID ); + int GetBlackMarketPreviousPriceForWeapon( int iWeaponID ); + + void SetBlackMarketPrices( bool bSetDefaults ); + + // Black market + INetworkStringTable *m_StringTableBlackMarket; + const weeklyprice_t *m_pPrices; +}; + +//----------------------------------------------------------------------------- +// Gets us at the team fortress game rules +//----------------------------------------------------------------------------- + +inline CCSGameRules* CSGameRules() +{ + return static_cast<CCSGameRules*>(g_pGameRules); +} + +#define IGNORE_SPECTATORS true +int UTIL_HumansInGame( bool ignoreSpectators = false ); + + + +//----------------------------------------------------------------------------- +// Purpose: Useful utility functions +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL + +#else + + class CTFTeam; + CTFTeam *GetOpposingTeam( CTeam *pTeam ); + bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround ); + +#endif + +#endif // TF_GAMERULES_H diff --git a/game/shared/cstrike/cs_gamestats_shared.cpp b/game/shared/cstrike/cs_gamestats_shared.cpp new file mode 100644 index 0000000..cd250c8 --- /dev/null +++ b/game/shared/cstrike/cs_gamestats_shared.cpp @@ -0,0 +1,410 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "fmtstr.h" +#ifdef GAME_DLL +#include "gamestats.h" +#endif +#include "cs_gamestats_shared.h" + +ConVar sv_noroundstats( "sv_noroundstats", "1", FCVAR_REPLICATED, "A temporary variable that can be used for disabling upload of round stats." ); + +const MapName_MapStatId MapName_StatId_Table[] = +{ + {"cs_assault", CSSTAT_MAP_WINS_CS_ASSAULT, CSSTAT_MAP_ROUNDS_CS_ASSAULT }, + {"cs_compound", CSSTAT_MAP_WINS_CS_COMPOUND, CSSTAT_MAP_ROUNDS_CS_COMPOUND }, + {"cs_havana", CSSTAT_MAP_WINS_CS_HAVANA, CSSTAT_MAP_ROUNDS_CS_HAVANA }, + {"cs_italy", CSSTAT_MAP_WINS_CS_ITALY, CSSTAT_MAP_ROUNDS_CS_ITALY }, + {"cs_militia", CSSTAT_MAP_WINS_CS_MILITIA, CSSTAT_MAP_ROUNDS_CS_MILITIA }, + {"cs_office", CSSTAT_MAP_WINS_CS_OFFICE, CSSTAT_MAP_ROUNDS_CS_OFFICE }, + {"de_aztec", CSSTAT_MAP_WINS_DE_AZTEC, CSSTAT_MAP_ROUNDS_DE_AZTEC }, + {"de_cbble", CSSTAT_MAP_WINS_DE_CBBLE, CSSTAT_MAP_ROUNDS_DE_CBBLE }, + {"de_chateau", CSSTAT_MAP_WINS_DE_CHATEAU, CSSTAT_MAP_ROUNDS_DE_CHATEAU }, + {"de_dust2", CSSTAT_MAP_WINS_DE_DUST2, CSSTAT_MAP_ROUNDS_DE_DUST2 }, + {"de_dust", CSSTAT_MAP_WINS_DE_DUST, CSSTAT_MAP_ROUNDS_DE_DUST }, + {"de_inferno", CSSTAT_MAP_WINS_DE_INFERNO, CSSTAT_MAP_ROUNDS_DE_INFERNO }, + {"de_nuke", CSSTAT_MAP_WINS_DE_NUKE, CSSTAT_MAP_ROUNDS_DE_NUKE }, + {"de_piranesi", CSSTAT_MAP_WINS_DE_PIRANESI, CSSTAT_MAP_ROUNDS_DE_PIRANESI }, + {"de_port", CSSTAT_MAP_WINS_DE_PORT, CSSTAT_MAP_ROUNDS_DE_PORT }, + {"de_prodigy", CSSTAT_MAP_WINS_DE_PRODIGY, CSSTAT_MAP_ROUNDS_DE_PRODIGY }, + {"de_tides", CSSTAT_MAP_WINS_DE_TIDES, CSSTAT_MAP_ROUNDS_DE_TIDES }, + {"de_train", CSSTAT_MAP_WINS_DE_TRAIN, CSSTAT_MAP_ROUNDS_DE_TRAIN }, + {"", CSSTAT_UNDEFINED, CSSTAT_UNDEFINED }, +}; + +const WeaponName_StatId WeaponName_StatId_Table[] = +{ + { WEAPON_DEAGLE, CSSTAT_KILLS_DEAGLE, CSSTAT_SHOTS_DEAGLE, CSSTAT_HITS_DEAGLE, CSSTAT_DAMAGE_DEAGLE }, + { WEAPON_USP, CSSTAT_KILLS_USP, CSSTAT_SHOTS_USP, CSSTAT_HITS_USP, CSSTAT_DAMAGE_USP }, + { WEAPON_GLOCK, CSSTAT_KILLS_GLOCK, CSSTAT_SHOTS_GLOCK, CSSTAT_HITS_GLOCK, CSSTAT_DAMAGE_GLOCK }, + { WEAPON_P228, CSSTAT_KILLS_P228, CSSTAT_SHOTS_P228, CSSTAT_HITS_P228, CSSTAT_DAMAGE_P228 }, + { WEAPON_ELITE, CSSTAT_KILLS_ELITE, CSSTAT_SHOTS_ELITE, CSSTAT_HITS_ELITE, CSSTAT_DAMAGE_ELITE }, + { WEAPON_FIVESEVEN, CSSTAT_KILLS_FIVESEVEN, CSSTAT_SHOTS_FIVESEVEN, CSSTAT_HITS_FIVESEVEN, CSSTAT_DAMAGE_FIVESEVEN }, + { WEAPON_AWP, CSSTAT_KILLS_AWP, CSSTAT_SHOTS_AWP, CSSTAT_HITS_AWP, CSSTAT_DAMAGE_AWP }, + { WEAPON_AK47, CSSTAT_KILLS_AK47, CSSTAT_SHOTS_AK47, CSSTAT_HITS_AK47, CSSTAT_DAMAGE_AK47 }, + { WEAPON_M4A1, CSSTAT_KILLS_M4A1, CSSTAT_SHOTS_M4A1, CSSTAT_HITS_M4A1, CSSTAT_DAMAGE_M4A1 }, + { WEAPON_AUG, CSSTAT_KILLS_AUG, CSSTAT_SHOTS_AUG, CSSTAT_HITS_AUG, CSSTAT_DAMAGE_AUG }, + { WEAPON_SG552, CSSTAT_KILLS_SG552, CSSTAT_SHOTS_SG552, CSSTAT_HITS_SG552, CSSTAT_DAMAGE_SG552 }, + { WEAPON_SG550, CSSTAT_KILLS_SG550, CSSTAT_SHOTS_SG550, CSSTAT_HITS_SG550, CSSTAT_DAMAGE_SG550 }, + { WEAPON_GALIL, CSSTAT_KILLS_GALIL, CSSTAT_SHOTS_GALIL, CSSTAT_HITS_GALIL, CSSTAT_DAMAGE_GALIL }, + { WEAPON_FAMAS, CSSTAT_KILLS_FAMAS, CSSTAT_SHOTS_FAMAS, CSSTAT_HITS_FAMAS, CSSTAT_DAMAGE_FAMAS }, + { WEAPON_SCOUT, CSSTAT_KILLS_SCOUT, CSSTAT_SHOTS_SCOUT, CSSTAT_HITS_SCOUT, CSSTAT_DAMAGE_SCOUT }, + { WEAPON_G3SG1, CSSTAT_KILLS_G3SG1, CSSTAT_SHOTS_G3SG1, CSSTAT_HITS_G3SG1, CSSTAT_DAMAGE_G3SG1 }, + { WEAPON_P90, CSSTAT_KILLS_P90, CSSTAT_SHOTS_P90, CSSTAT_HITS_P90, CSSTAT_DAMAGE_P90 }, + { WEAPON_MP5NAVY, CSSTAT_KILLS_MP5NAVY, CSSTAT_SHOTS_MP5NAVY, CSSTAT_HITS_MP5NAVY, CSSTAT_DAMAGE_MP5NAVY }, + { WEAPON_TMP, CSSTAT_KILLS_TMP, CSSTAT_SHOTS_TMP, CSSTAT_HITS_TMP, CSSTAT_DAMAGE_TMP }, + { WEAPON_MAC10, CSSTAT_KILLS_MAC10, CSSTAT_SHOTS_MAC10, CSSTAT_HITS_MAC10, CSSTAT_DAMAGE_MAC10 }, + { WEAPON_UMP45, CSSTAT_KILLS_UMP45, CSSTAT_SHOTS_UMP45, CSSTAT_HITS_UMP45, CSSTAT_DAMAGE_UMP45 }, + { WEAPON_M3, CSSTAT_KILLS_M3, CSSTAT_SHOTS_M3, CSSTAT_HITS_M3, CSSTAT_DAMAGE_M3 }, + { WEAPON_XM1014, CSSTAT_KILLS_XM1014, CSSTAT_SHOTS_XM1014, CSSTAT_HITS_XM1014, CSSTAT_DAMAGE_XM1014 }, + { WEAPON_M249, CSSTAT_KILLS_M249, CSSTAT_SHOTS_M249, CSSTAT_HITS_M249, CSSTAT_DAMAGE_M249 }, + { WEAPON_KNIFE, CSSTAT_KILLS_KNIFE, CSSTAT_SHOTS_KNIFE, CSSTAT_HITS_KNIFE, CSSTAT_DAMAGE_KNIFE }, + { WEAPON_HEGRENADE, CSSTAT_KILLS_HEGRENADE, CSSTAT_SHOTS_HEGRENADE, CSSTAT_HITS_HEGRENADE, CSSTAT_DAMAGE_HEGRENADE }, + + { WEAPON_NONE, CSSTAT_UNDEFINED, CSSTAT_UNDEFINED, CSSTAT_UNDEFINED, CSSTAT_UNDEFINED }, // This is a sentinel value so we can loop through all the stats +}; + +CSStatProperty CSStatProperty_Table[] = +{ +// StatId Steam Name Localization Token Client Update Priority + { CSSTAT_SHOTS_HIT, "i_NumShotsHit", "#GAMEUI_Stat_NumHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_FIRED, "i_NumShotsFired", "#GAMEUI_Stat_NumShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_KILLS, "i_Number_Of_Kills", "#GAMEUI_Stat_NumKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_DEATHS, "i_Number_Of_Deaths", "#GAMEUI_Stat_NumDeaths", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_DAMAGE, "i_Damage_Done", "#GAMEUI_Stat_DamageDone", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_NUM_BOMBS_PLANTED, "i_Number_Of_PlantedBombs", "#GAMEUI_Stat_NumPlantedBombs", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_NUM_BOMBS_DEFUSED, "i_Number_Of_DefusedBombs", "#GAMEUI_Stat_NumDefusedBombs", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_PLAYTIME, "i_Time_Played", "#GAMEUI_Stat_TimePlayed", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_ROUNDS_WON, "total_wins", "#GAMEUI_Stat_TotalWins", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_T_ROUNDS_WON, NULL, NULL, CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_CT_ROUNDS_WON, NULL, NULL, CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_ROUNDS_PLAYED, "i_Total_Rounds", "#GAMEUI_Stat_TotalRounds", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_PISTOLROUNDS_WON, "total_wins_pistolround", "#GAMEUI_Stat_PistolRoundWins", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MONEY_EARNED, "i_Money_Earned", "#GAMEUI_Stat_MoneyEarned", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_OBJECTIVES_COMPLETED, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_BOMBS_DEFUSED_WITHKIT, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_KILLS_DEAGLE, "total_kills_deagle", "#GAMEUI_Stat_DeagleKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_USP, "total_kills_usp", "#GAMEUI_Stat_USPKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_GLOCK, "total_kills_glock", "#GAMEUI_Stat_GlockKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_P228, "total_kills_p228", "#GAMEUI_Stat_P228Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_ELITE, "total_kills_elite", "#GAMEUI_Stat_EliteKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_FIVESEVEN, "total_kills_fiveseven", "#GAMEUI_Stat_FiveSevenKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_AWP, "total_kills_awp", "#GAMEUI_Stat_AWPKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_AK47, "total_kills_ak47", "#GAMEUI_Stat_AK47Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_M4A1, "total_kills_m4a1", "#GAMEUI_Stat_M4A1Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_AUG, "total_kills_aug", "#GAMEUI_Stat_AUGKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_SG552, "total_kills_sg552", "#GAMEUI_Stat_SG552Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_SG550, "total_kills_sg550", "#GAMEUI_Stat_SG550Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_GALIL, "total_kills_galil", "#GAMEUI_Stat_GALILKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_FAMAS, "total_kills_famas", "#GAMEUI_Stat_FAMASKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_SCOUT, "total_kills_scout", "#GAMEUI_Stat_ScoutKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_G3SG1, "total_kills_g3sg1", "#GAMEUI_Stat_G3SG1Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_P90, "total_kills_p90", "#GAMEUI_Stat_P90Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_MP5NAVY, "total_kills_mp5navy", "#GAMEUI_Stat_MP5NavyKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_TMP, "total_kills_tmp", "#GAMEUI_Stat_TMPKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_MAC10, "total_kills_mac10", "#GAMEUI_Stat_MAC10Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_UMP45, "total_kills_ump45", "#GAMEUI_Stat_UMP45Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_M3, "total_kills_m3", "#GAMEUI_Stat_M3Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_XM1014, "total_kills_xm1014", "#GAMEUI_Stat_XM1014Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_M249, "total_kills_m249", "#GAMEUI_Stat_M249Kills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_KNIFE, "total_kills_knife", "#GAMEUI_Stat_KnifeKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_HEGRENADE, "total_kills_hegrenade", "#GAMEUI_Stat_HEGrenadeKills", CSSTAT_PRIORITY_HIGH, }, + + { CSSTAT_SHOTS_DEAGLE, "total_shots_deagle", "#GAMEUI_Stat_DeagleShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_USP, "total_shots_usp", "#GAMEUI_Stat_USPShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_GLOCK, "total_shots_glock", "#GAMEUI_Stat_GlockShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_P228, "total_shots_p228", "#GAMEUI_Stat_P228Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_ELITE, "total_shots_elite", "#GAMEUI_Stat_EliteShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_FIVESEVEN, "total_shots_fiveseven", "#GAMEUI_Stat_FiveSevenShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_AWP, "total_shots_awp", "#GAMEUI_Stat_AWPShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_AK47, "total_shots_ak47", "#GAMEUI_Stat_AK47Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_M4A1, "total_shots_m4a1", "#GAMEUI_Stat_M4A1Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_AUG, "total_shots_aug", "#GAMEUI_Stat_AUGShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_SG552, "total_shots_sg552", "#GAMEUI_Stat_SG552Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_SG550, "total_shots_sg550", "#GAMEUI_Stat_SG550Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_GALIL, "total_shots_galil", "#GAMEUI_Stat_GALILShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_FAMAS, "total_shots_famas", "#GAMEUI_Stat_FAMASShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_SCOUT, "total_shots_scout", "#GAMEUI_Stat_ScoutShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_G3SG1, "total_shots_g3sg1", "#GAMEUI_Stat_G3SG1Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_P90, "total_shots_p90", "#GAMEUI_Stat_P90Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_MP5NAVY, "total_shots_mp5navy", "#GAMEUI_Stat_MP5NavyShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_TMP, "total_shots_tmp", "#GAMEUI_Stat_TMPShots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_MAC10, "total_shots_mac10", "#GAMEUI_Stat_MAC10Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_UMP45, "total_shots_ump45", "#GAMEUI_Stat_UMP45Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_M3, "total_shots_m3", "#GAMEUI_Stat_M3Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_XM1014, "total_shots_xm1014", "#GAMEUI_Stat_XM1014Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_M249, "total_shots_m249", "#GAMEUI_Stat_M249Shots", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_SHOTS_KNIFE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_SHOTS_HEGRENADE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_HITS_DEAGLE, "total_hits_deagle", "#GAMEUI_Stat_DeagleHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_USP, "total_hits_usp", "#GAMEUI_Stat_USPHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_GLOCK, "total_hits_glock", "#GAMEUI_Stat_GlockHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_P228, "total_hits_p228", "#GAMEUI_Stat_P228Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_ELITE, "total_hits_elite", "#GAMEUI_Stat_EliteHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_FIVESEVEN, "total_hits_fiveseven", "#GAMEUI_Stat_FiveSevenHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_AWP, "total_hits_awp", "#GAMEUI_Stat_AWPHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_AK47, "total_hits_ak47", "#GAMEUI_Stat_AK47Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_M4A1, "total_hits_m4a1", "#GAMEUI_Stat_M4A1Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_AUG, "total_hits_aug", "#GAMEUI_Stat_AUGHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_SG552, "total_hits_sg552", "#GAMEUI_Stat_SG552Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_SG550, "total_hits_sg550", "#GAMEUI_Stat_SG550Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_GALIL, "total_hits_galil", "#GAMEUI_Stat_GALILHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_FAMAS, "total_hits_famas", "#GAMEUI_Stat_FAMASHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_SCOUT, "total_hits_scout", "#GAMEUI_Stat_ScoutHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_G3SG1, "total_hits_g3sg1", "#GAMEUI_Stat_G3SG1Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_P90, "total_hits_p90", "#GAMEUI_Stat_P90Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_MP5NAVY, "total_hits_mp5navy", "#GAMEUI_Stat_MP5NavyHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_TMP, "total_hits_tmp", "#GAMEUI_Stat_TMPHits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_MAC10, "total_hits_mac10", "#GAMEUI_Stat_MAC10Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_UMP45, "total_hits_ump45", "#GAMEUI_Stat_UMP45Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_M3, "total_hits_m3", "#GAMEUI_Stat_M3Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_XM1014, "total_hits_xm1014", "#GAMEUI_Stat_XM1014Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_M249, "total_hits_m249", "#GAMEUI_Stat_M249Hits", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_HITS_KNIFE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_HITS_HEGRENADE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_DAMAGE_DEAGLE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_USP, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_GLOCK, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_P228, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_ELITE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_FIVESEVEN, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_AWP, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_AK47, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_M4A1, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_AUG, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_SG552, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_SG550, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_GALIL, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_FAMAS, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_SCOUT, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_G3SG1, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_P90, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_MP5NAVY, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_TMP, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_MAC10, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_UMP45, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_M3, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_XM1014, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_M249, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_KNIFE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_DAMAGE_HEGRENADE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_KILLS_HEADSHOT, "total_kills_headshot", "#GAMEUI_Stat_HeadshotKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_ENEMY_BLINDED, "total_kills_enemy_blinded", "#GAMEUI_Stat_BlindedEnemyKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_WHILE_BLINDED, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_KILLS_WITH_LAST_ROUND, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_KILLS_ENEMY_WEAPON, "total_kills_enemy_weapon", "#GAMEUI_Stat_EnemyWeaponKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_KNIFE_FIGHT, "total_kills_knife_fight", "#GAMEUI_Stat_KnifeFightKills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_KILLS_WHILE_DEFENDING_BOMB, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_DECAL_SPRAYS, "total_decal_sprays", "#GAMEUI_Stat_DecalSprays", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_TOTAL_JUMPS, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_NIGHTVISION_DAMAGE, "total_nightvision_damage", "#GAMEUI_Stat_NightvisionDamage", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_KILLS_ENEMY_WOUNDED, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_FALL_DAMAGE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_NUM_HOSTAGES_RESCUED, "i_Number_Of_RescuedHostages", "#GAMEUI_Stat_NumRescuedHostages", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_NUM_BROKEN_WINDOWS, "i_Number_Of_BrokenWindows", "#GAMEUI_Stat_NumBrokenWindows", CSSTAT_PRIORITY_LOW, }, + { CSSTAT_PROPSBROKEN_ALL, NULL, NULL, CSSTAT_PRIORITY_LOW, }, + { CSSTAT_PROPSBROKEN_MELON, NULL, NULL, CSSTAT_PRIORITY_LOW, }, + { CSSTAT_PROPSBROKEN_OFFICEELECTRONICS, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_PROPSBROKEN_OFFICERADIO, NULL, NULL, CSSTAT_PRIORITY_LOW, }, + { CSSTAT_PROPSBROKEN_OFFICEJUNK, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_PROPSBROKEN_ITALY_MELON, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, "total_kills_against_zoomed_sniper","#GAMEUI_Stat_ZoomedSniperKills", CSSTAT_PRIORITY_HIGH, }, + + { CSSTAT_WEAPONS_DONATED, "total_weapons_donated", "#GAMEUI_Stat_WeaponsDonated", CSSTAT_PRIORITY_HIGH, }, + + { CSSTAT_ITEMS_PURCHASED, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_MONEY_SPENT, NULL, NULL, CSSTAT_PRIORITY_LOW, }, + + { CSSTAT_DOMINATIONS, "total_dominations", "#GAMEUI_Stat_Dominations", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_DOMINATION_OVERKILLS, "total_domination_overkills", "#GAMEUI_Stat_DominationOverkills", CSSTAT_PRIORITY_HIGH, }, + { CSSTAT_REVENGES, "total_revenges", "#GAMEUI_Stat_Revenges", CSSTAT_PRIORITY_HIGH, }, + + { CSSTAT_MVPS, "total_mvps", "#GAMEUI_Stat_MVPs", CSSTAT_PRIORITY_HIGH, }, + + { CSSTAT_GRENADE_DAMAGE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_GRENADE_POSTHUMOUSKILLS, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSSTAT_GRENADES_THROWN, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + { CSTAT_ITEMS_DROPPED_VALUE, NULL, NULL, CSSTAT_PRIORITY_NEVER, }, + + { CSSTAT_MAP_WINS_CS_ASSAULT, "total_wins_map_cs_assault", "#GAMEUI_Stat_WinsMapCSAssault", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_CS_COMPOUND, "total_wins_map_cs_compound", "#GAMEUI_Stat_WinsMapCSCompound", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_CS_HAVANA, "total_wins_map_cs_havana", "#GAMEUI_Stat_WinsMapCSHavana", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_CS_ITALY, "total_wins_map_cs_italy", "#GAMEUI_Stat_WinsMapCSItaly", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_CS_MILITIA, "total_wins_map_cs_militia", "#GAMEUI_Stat_WinsMapCSMilitia", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_CS_OFFICE, "total_wins_map_cs_office", "#GAMEUI_Stat_WinsMapCSOffice", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_AZTEC, "total_wins_map_de_aztec", "#GAMEUI_Stat_WinsMapDEAztec", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_CBBLE, "total_wins_map_de_cbble", "#GAMEUI_Stat_WinsMapDECbble", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_CHATEAU, "total_wins_map_de_chateau", "#GAMEUI_Stat_WinsMapDEChateau", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_DUST2, "total_wins_map_de_dust2", "#GAMEUI_Stat_WinsMapDEDust2", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_DUST, "total_wins_map_de_dust", "#GAMEUI_Stat_WinsMapDEDust", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_INFERNO, "total_wins_map_de_inferno", "#GAMEUI_Stat_WinsMapDEInferno", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_NUKE, "total_wins_map_de_nuke", "#GAMEUI_Stat_WinsMapDENuke", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_PIRANESI, "total_wins_map_de_piranesi", "#GAMEUI_Stat_WinsMapDEPiranesi", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_PORT, "total_wins_map_de_port", "#GAMEUI_Stat_WinsMapDEPort", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_PRODIGY, "total_wins_map_de_prodigy", "#GAMEUI_Stat_WinsMapDEProdigy", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_TIDES, "total_wins_map_de_tides", "#GAMEUI_Stat_WinsMapDETides", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_WINS_DE_TRAIN, "total_wins_map_de_train", "#GAMEUI_Stat_WinsMapDETrain", CSSTAT_PRIORITY_ENDROUND, }, + + { CSSTAT_MAP_ROUNDS_CS_ASSAULT, "total_rounds_map_cs_assault", "#GAMEUI_Stat_RoundsMapCSAssault", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_CS_COMPOUND, "total_rounds_map_cs_compound", "#GAMEUI_Stat_RoundsMapCSCompound", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_CS_HAVANA, "total_rounds_map_cs_havana", "#GAMEUI_Stat_RoundsMapCSHavana", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_CS_ITALY, "total_rounds_map_cs_italy", "#GAMEUI_Stat_RoundsMapCSItaly", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_CS_MILITIA, "total_rounds_map_cs_militia", "#GAMEUI_Stat_RoundsMapCSMilitia", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_CS_OFFICE, "total_rounds_map_cs_office", "#GAMEUI_Stat_RoundsMapCSOffice", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_AZTEC, "total_rounds_map_de_aztec", "#GAMEUI_Stat_RoundsMapDEAztec", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_CBBLE, "total_rounds_map_de_cbble", "#GAMEUI_Stat_RoundsMapDECbble", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_CHATEAU, "total_rounds_map_de_chateau", "#GAMEUI_Stat_RoundsMapDEChateau", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_DUST2, "total_rounds_map_de_dust2", "#GAMEUI_Stat_RoundsMapDEDust2", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_DUST, "total_rounds_map_de_dust", "#GAMEUI_Stat_RoundsMapDEDust", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_INFERNO, "total_rounds_map_de_inferno", "#GAMEUI_Stat_RoundsMapDEInferno", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_NUKE, "total_rounds_map_de_nuke", "#GAMEUI_Stat_RoundsMapDENuke", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_PIRANESI, "total_rounds_map_de_piranesi", "#GAMEUI_Stat_RoundsMapDEPiranesi", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_PORT, "total_rounds_map_de_port", "#GAMEUI_Stat_RoundsMapDEPort", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_PRODIGY, "total_rounds_map_de_prodigy", "#GAMEUI_Stat_RoundsMapDEProdigy", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_TIDES, "total_rounds_map_de_tides", "#GAMEUI_Stat_RoundsMapDETides", CSSTAT_PRIORITY_ENDROUND, }, + { CSSTAT_MAP_ROUNDS_DE_TRAIN, "total_rounds_map_de_train", "#GAMEUI_Stat_RoundsMapDETrain", CSSTAT_PRIORITY_ENDROUND, }, + + // only client tracks these + { CSSTAT_LASTMATCH_T_ROUNDS_WON, "last_match_t_wins", "#GameUI_Stat_LastMatch_TWins", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_CT_ROUNDS_WON, "last_match_ct_wins", "#GameUI_Stat_LastMatch_CTWins", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_ROUNDS_WON, "last_match_wins", "#GameUI_Stat_LastMatch_RoundsWon", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_KILLS, "last_match_kills", "#GameUI_Stat_LastMatch_Kills", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_DEATHS, "last_match_deaths", "#GameUI_Stat_LastMatch_Deaths", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_MVPS, "last_match_mvps", "#GameUI_Stat_LastMatch_MVPS", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_DAMAGE, "last_match_damage", "#GameUI_Stat_LastMatch_Damage", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_MONEYSPENT, "last_match_money_spent", "#GameUI_Stat_LastMatch_MoneySpent", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_DOMINATIONS, "last_match_dominations", "#GameUI_Stat_LastMatch_Dominations", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_REVENGES, "last_match_revenges", "#GameUI_Stat_LastMatch_Revenges", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_MAX_PLAYERS, "last_match_max_players", "#GameUI_Stat_LastMatch_MaxPlayers", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_FAVWEAPON_ID, "last_match_favweapon_id", "#GameUI_Stat_LastMatch_FavWeapon", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_FAVWEAPON_SHOTS, "last_match_favweapon_shots", "#GameUI_Stat_LastMatch_FavWeaponShots",CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_FAVWEAPON_HITS, "last_match_favweapon_hits", "#GameUI_Stat_LastMatch_FavWeaponHits", CSSTAT_PRIORITY_NEVER }, + { CSSTAT_LASTMATCH_FAVWEAPON_KILLS, "last_match_favweapon_kills", "#GameUI_Stat_LastMatch_FavWeaponKills",CSSTAT_PRIORITY_NEVER }, + + { CSSTAT_UNDEFINED }, // sentinel +}; + +const WeaponName_StatId& GetWeaponTableEntryFromWeaponId( CSWeaponID id ) +{ + int i; + + //yes this for loop has no statement block. All we are doing is incrementing i to the appropriate point. + for (i = 0 ; WeaponName_StatId_Table[i].weaponId != WEAPON_NONE ; ++i) + { + if (WeaponName_StatId_Table[i].weaponId == id ) + { + break; + } + } + return WeaponName_StatId_Table[i]; +} + +void StatsCollection_t::Aggregate( const StatsCollection_t& other ) +{ + for ( int i = 0; i < CSSTAT_MAX; ++i ) + { + m_iValue[i] += other[i]; + } +} + +//============================================================================= +// +// Helper functions for creating key values +// +void AddDataToKV( KeyValues* pKV, const char* name, int data ) +{ + pKV->SetInt( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, uint64 data ) +{ + pKV->SetUint64( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, float data ) +{ + pKV->SetFloat( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, bool data ) +{ + pKV->SetInt( name, data ? true : false ); +} +void AddDataToKV( KeyValues* pKV, const char* name, const char* data ) +{ + pKV->SetString( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, const Color& data ) +{ + pKV->SetColor( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, short data ) +{ + pKV->SetInt( name, data ); +} +void AddDataToKV( KeyValues* pKV, const char* name, unsigned data ) +{ + pKV->SetInt( name, data ); +} +void AddPositionDataToKV( KeyValues* pKV, const char* name, const Vector &data ) +{ + // Append the data name to the member + pKV->SetFloat( CFmtStr("%s%s", name, "_X"), data.x ); + pKV->SetFloat( CFmtStr("%s%s", name, "_Y"), data.y ); + pKV->SetFloat( CFmtStr("%s%s", name, "_Z"), data.z ); +} + +//=============================================================================// + +//============================================================================= +// +// Helper functions for creating key values from arrays +// +void AddArrayDataToKV( KeyValues* pKV, const char* name, const short *data, unsigned size ) +{ + for( unsigned i=0; i<size; ++i ) + pKV->SetInt( CFmtStr("%s_%d", name, i) , data[i] ); +} +void AddArrayDataToKV( KeyValues* pKV, const char* name, const byte *data, unsigned size ) +{ + for( unsigned i=0; i<size; ++i ) + pKV->SetInt( CFmtStr("%s_%d", name, i), data[i] ); +} +void AddArrayDataToKV( KeyValues* pKV, const char* name, const unsigned *data, unsigned size ) +{ + for( unsigned i=0; i<size; ++i ) + pKV->SetInt( CFmtStr("%s_%d", name, i), data[i] ); +} +void AddStringDataToKV( KeyValues* pKV, const char* name, const char*data ) +{ + if( name == NULL ) + return; + + pKV->SetString( name, data ); +} +//=============================================================================// + + +void IGameStatTracker::PrintGamestatMemoryUsage( void ) +{ + StatContainerList_t* pStatList = GetStatContainerList(); + if( !pStatList ) + return; + + int iListSize = pStatList->Count(); + + // For every stat list being tracked, print out its memory usage + for( int i=0; i < iListSize; ++i ) + { + pStatList->operator []( i )->PrintMemoryUsage(); + } +} diff --git a/game/shared/cstrike/cs_gamestats_shared.h b/game/shared/cstrike/cs_gamestats_shared.h new file mode 100644 index 0000000..dc58a77 --- /dev/null +++ b/game/shared/cstrike/cs_gamestats_shared.h @@ -0,0 +1,824 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#ifndef CS_GAMESTATS_SHARED_H +#define CS_GAMESTATS_SHARED_H +#ifdef _WIN32 +#pragma once +#endif +#include "cbase.h" +// #include "tier1/utlvector.h" +// #include "tier1/utldict.h" +#include "shareddefs.h" +#include "cs_shareddefs.h" +#include "cs_weapon_parse.h" +#include "fmtstr.h" + + +#define CS_NUM_LEVELS 18 + +//============================================================================= +// Helper class for simple manipulation of bit arrays. +// Used for server->client packets containing delta stats +//============================================================================= + +template <int BitLength> +class BitArray +{ + enum { ByteLength = (BitLength + 7) / 8 }; +public: + BitArray() { ClearAll(); } + + void SetBit(int n) { m_bytes[n / 8] |= 1 << (n & 7); } + void ClearBit(int n) { m_bytes[n / 8] &= (~(1 << (n & 7))); } + bool IsBitSet(int n) const { return (m_bytes[n / 8] & (1 << (n & 7))) != 0;} + + void ClearAll() { V_memset(m_bytes, 0, sizeof(m_bytes)); } + int NumBits() { return BitLength; } + int NumBytes() { return ByteLength; } + + byte* RawPointer() { return m_bytes; } + +private: + byte m_bytes[ByteLength]; +}; + + +//============================================================================= +// +// CS Game Stats Enums +// +// WARNING! ANY CHANGE TO THE ORDERING OR NUMBER OF STATS WILL REQUIRE +// SYNCHRONOUS UPDATE OF CLIENT AND SERVER DLLS. If you change these enums +// (including adding new stats) without forcing an update of both the client +// and server, stats will become corrupted on the game client and these +// corrupted values will be uploaded to steam, which is very very bad. +// +// If you add new stats, if will be safest to add them at the end of the enum +// (although this will still require a server update); make sure you also add +// the stats to the CSStatProperty_Table in cs_gamestats_shared.cpp at the +// appropriate location. + +enum CSStatType_t +{ + CSSTAT_UNDEFINED = -1, + CSSTAT_SHOTS_HIT, + CSSTAT_SHOTS_FIRED, + CSSTAT_KILLS, + CSSTAT_DEATHS, + CSSTAT_DAMAGE, + CSSTAT_NUM_BOMBS_PLANTED, + CSSTAT_NUM_BOMBS_DEFUSED, + CSSTAT_PLAYTIME, + CSSTAT_ROUNDS_WON, + CSSTAT_T_ROUNDS_WON, + CSSTAT_CT_ROUNDS_WON, + CSSTAT_ROUNDS_PLAYED, + CSSTAT_PISTOLROUNDS_WON, + CSSTAT_MONEY_EARNED, + CSSTAT_OBJECTIVES_COMPLETED, + CSSTAT_BOMBS_DEFUSED_WITHKIT, + + CSSTAT_KILLS_DEAGLE, + CSSTAT_KILLS_USP, + CSSTAT_KILLS_GLOCK, + CSSTAT_KILLS_P228, + CSSTAT_KILLS_ELITE, + CSSTAT_KILLS_FIVESEVEN, + CSSTAT_KILLS_AWP, + CSSTAT_KILLS_AK47, + CSSTAT_KILLS_M4A1, + CSSTAT_KILLS_AUG, + CSSTAT_KILLS_SG552, + CSSTAT_KILLS_SG550, + CSSTAT_KILLS_GALIL, + CSSTAT_KILLS_FAMAS, + CSSTAT_KILLS_SCOUT, + CSSTAT_KILLS_G3SG1, + CSSTAT_KILLS_P90, + CSSTAT_KILLS_MP5NAVY, + CSSTAT_KILLS_TMP, + CSSTAT_KILLS_MAC10, + CSSTAT_KILLS_UMP45, + CSSTAT_KILLS_M3, + CSSTAT_KILLS_XM1014, + CSSTAT_KILLS_M249, + CSSTAT_KILLS_KNIFE, + CSSTAT_KILLS_HEGRENADE, + + CSSTAT_SHOTS_DEAGLE, + CSSTAT_SHOTS_USP, + CSSTAT_SHOTS_GLOCK, + CSSTAT_SHOTS_P228, + CSSTAT_SHOTS_ELITE, + CSSTAT_SHOTS_FIVESEVEN, + CSSTAT_SHOTS_AWP, + CSSTAT_SHOTS_AK47, + CSSTAT_SHOTS_M4A1, + CSSTAT_SHOTS_AUG, + CSSTAT_SHOTS_SG552, + CSSTAT_SHOTS_SG550, + CSSTAT_SHOTS_GALIL, + CSSTAT_SHOTS_FAMAS, + CSSTAT_SHOTS_SCOUT, + CSSTAT_SHOTS_G3SG1, + CSSTAT_SHOTS_P90, + CSSTAT_SHOTS_MP5NAVY, + CSSTAT_SHOTS_TMP, + CSSTAT_SHOTS_MAC10, + CSSTAT_SHOTS_UMP45, + CSSTAT_SHOTS_M3, + CSSTAT_SHOTS_XM1014, + CSSTAT_SHOTS_M249, + CSSTAT_SHOTS_KNIFE, + CSSTAT_SHOTS_HEGRENADE, + + CSSTAT_HITS_DEAGLE, + CSSTAT_HITS_USP, + CSSTAT_HITS_GLOCK, + CSSTAT_HITS_P228, + CSSTAT_HITS_ELITE, + CSSTAT_HITS_FIVESEVEN, + CSSTAT_HITS_AWP, + CSSTAT_HITS_AK47, + CSSTAT_HITS_M4A1, + CSSTAT_HITS_AUG, + CSSTAT_HITS_SG552, + CSSTAT_HITS_SG550, + CSSTAT_HITS_GALIL, + CSSTAT_HITS_FAMAS, + CSSTAT_HITS_SCOUT, + CSSTAT_HITS_G3SG1, + CSSTAT_HITS_P90, + CSSTAT_HITS_MP5NAVY, + CSSTAT_HITS_TMP, + CSSTAT_HITS_MAC10, + CSSTAT_HITS_UMP45, + CSSTAT_HITS_M3, + CSSTAT_HITS_XM1014, + CSSTAT_HITS_M249, + CSSTAT_HITS_KNIFE, + CSSTAT_HITS_HEGRENADE, + + CSSTAT_DAMAGE_DEAGLE, + CSSTAT_DAMAGE_USP, + CSSTAT_DAMAGE_GLOCK, + CSSTAT_DAMAGE_P228, + CSSTAT_DAMAGE_ELITE, + CSSTAT_DAMAGE_FIVESEVEN, + CSSTAT_DAMAGE_AWP, + CSSTAT_DAMAGE_AK47, + CSSTAT_DAMAGE_M4A1, + CSSTAT_DAMAGE_AUG, + CSSTAT_DAMAGE_SG552, + CSSTAT_DAMAGE_SG550, + CSSTAT_DAMAGE_GALIL, + CSSTAT_DAMAGE_FAMAS, + CSSTAT_DAMAGE_SCOUT, + CSSTAT_DAMAGE_G3SG1, + CSSTAT_DAMAGE_P90, + CSSTAT_DAMAGE_MP5NAVY, + CSSTAT_DAMAGE_TMP, + CSSTAT_DAMAGE_MAC10, + CSSTAT_DAMAGE_UMP45, + CSSTAT_DAMAGE_M3, + CSSTAT_DAMAGE_XM1014, + CSSTAT_DAMAGE_M249, + CSSTAT_DAMAGE_KNIFE, + CSSTAT_DAMAGE_HEGRENADE, + + CSSTAT_KILLS_HEADSHOT, + CSSTAT_KILLS_ENEMY_BLINDED, + CSSTAT_KILLS_WHILE_BLINDED, + CSSTAT_KILLS_WITH_LAST_ROUND, + CSSTAT_KILLS_ENEMY_WEAPON, + CSSTAT_KILLS_KNIFE_FIGHT, + CSSTAT_KILLS_WHILE_DEFENDING_BOMB, + + CSSTAT_DECAL_SPRAYS, + CSSTAT_TOTAL_JUMPS, + CSSTAT_NIGHTVISION_DAMAGE, + CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE, + CSSTAT_KILLS_ENEMY_WOUNDED, + CSSTAT_FALL_DAMAGE, + + CSSTAT_NUM_HOSTAGES_RESCUED, + + CSSTAT_NUM_BROKEN_WINDOWS, + CSSTAT_PROPSBROKEN_ALL, + CSSTAT_PROPSBROKEN_MELON, + CSSTAT_PROPSBROKEN_OFFICEELECTRONICS, + CSSTAT_PROPSBROKEN_OFFICERADIO, + CSSTAT_PROPSBROKEN_OFFICEJUNK, + CSSTAT_PROPSBROKEN_ITALY_MELON, + + CSSTAT_KILLS_AGAINST_ZOOMED_SNIPER, + + CSSTAT_WEAPONS_DONATED, + + CSSTAT_ITEMS_PURCHASED, + CSSTAT_MONEY_SPENT, + + CSSTAT_DOMINATIONS, + CSSTAT_DOMINATION_OVERKILLS, + CSSTAT_REVENGES, + + CSSTAT_MVPS, + + CSSTAT_GRENADE_DAMAGE, + CSSTAT_GRENADE_POSTHUMOUSKILLS, + CSSTAT_GRENADES_THROWN, + + CSTAT_ITEMS_DROPPED_VALUE, + + //Map win stats + CSSTAT_MAP_WINS_CS_ASSAULT, + CSSTAT_MAP_WINS_CS_COMPOUND, + CSSTAT_MAP_WINS_CS_HAVANA, + CSSTAT_MAP_WINS_CS_ITALY, + CSSTAT_MAP_WINS_CS_MILITIA, + CSSTAT_MAP_WINS_CS_OFFICE, + CSSTAT_MAP_WINS_DE_AZTEC, + CSSTAT_MAP_WINS_DE_CBBLE, + CSSTAT_MAP_WINS_DE_CHATEAU, + CSSTAT_MAP_WINS_DE_DUST2, + CSSTAT_MAP_WINS_DE_DUST, + CSSTAT_MAP_WINS_DE_INFERNO, + CSSTAT_MAP_WINS_DE_NUKE, + CSSTAT_MAP_WINS_DE_PIRANESI, + CSSTAT_MAP_WINS_DE_PORT, + CSSTAT_MAP_WINS_DE_PRODIGY, + CSSTAT_MAP_WINS_DE_TIDES, + CSSTAT_MAP_WINS_DE_TRAIN, + + CSSTAT_MAP_ROUNDS_CS_ASSAULT, + CSSTAT_MAP_ROUNDS_CS_COMPOUND, + CSSTAT_MAP_ROUNDS_CS_HAVANA, + CSSTAT_MAP_ROUNDS_CS_ITALY, + CSSTAT_MAP_ROUNDS_CS_MILITIA, + CSSTAT_MAP_ROUNDS_CS_OFFICE, + CSSTAT_MAP_ROUNDS_DE_AZTEC, + CSSTAT_MAP_ROUNDS_DE_CBBLE, + CSSTAT_MAP_ROUNDS_DE_CHATEAU, + CSSTAT_MAP_ROUNDS_DE_DUST2, + CSSTAT_MAP_ROUNDS_DE_DUST, + CSSTAT_MAP_ROUNDS_DE_INFERNO, + CSSTAT_MAP_ROUNDS_DE_NUKE, + CSSTAT_MAP_ROUNDS_DE_PIRANESI, + CSSTAT_MAP_ROUNDS_DE_PORT, + CSSTAT_MAP_ROUNDS_DE_PRODIGY, + CSSTAT_MAP_ROUNDS_DE_TIDES, + CSSTAT_MAP_ROUNDS_DE_TRAIN, + + CSSTAT_LASTMATCH_T_ROUNDS_WON, + CSSTAT_LASTMATCH_CT_ROUNDS_WON, + CSSTAT_LASTMATCH_ROUNDS_WON, + CSSTAT_LASTMATCH_KILLS, + CSSTAT_LASTMATCH_DEATHS, + CSSTAT_LASTMATCH_MVPS, + CSSTAT_LASTMATCH_DAMAGE, + CSSTAT_LASTMATCH_MONEYSPENT, + CSSTAT_LASTMATCH_DOMINATIONS, + CSSTAT_LASTMATCH_REVENGES, + CSSTAT_LASTMATCH_MAX_PLAYERS, + CSSTAT_LASTMATCH_FAVWEAPON_ID, + CSSTAT_LASTMATCH_FAVWEAPON_SHOTS, + CSSTAT_LASTMATCH_FAVWEAPON_HITS, + CSSTAT_LASTMATCH_FAVWEAPON_KILLS, + + CSSTAT_MAX //Must be last entry. +}; + + +#define CSSTAT_FIRST (CSSTAT_UNDEFINED+1) +#define CSSTAT_LAST (CSSTAT_MAX-1) + +// +// CS Game Stats Flags +// +#define CSSTAT_PRIORITY_MASK 0x000F +#define CSSTAT_PRIORITY_NEVER 0x0000 // not sent to client +#define CSSTAT_PRIORITY_ENDROUND 0x0001 // sent at end of round +#define CSSTAT_PRIORITY_LOW 0x0002 // sent every 2500ms +#define CSSTAT_PRIORITY_HIGH 0x0003 // sent every 250ms + +struct CSStatProperty +{ + int statId; // verify that table ordering is correct + const char* szSteamName; // name of the stat on steam + const char* szLocalizationToken; // localization token for the stat + uint flags; // priority flags for sending to client +}; + +extern CSStatProperty CSStatProperty_Table[]; + + +//============================================================================= +// +// CS Player Round Stats +// +struct StatsCollection_t +{ + StatsCollection_t() { Reset(); } + + inline int Get( int i ) const + { + AssertMsg( i >= CSSTAT_FIRST && i < CSSTAT_MAX, "Stat index out of range!" ); + if ( i >= 0 ) + return m_iValue[ i ]; + return 0; + } + + inline void Set( int i, int nValue ) + { + AssertMsg( i >= CSSTAT_FIRST && i < CSSTAT_MAX, "Stat index out of range!" ); + if ( i >= 0 ) + m_iValue[ i ] = nValue; + } + + void Reset() + { + for ( int i = 0; i < ARRAYSIZE( m_iValue ); i++ ) + { + m_iValue[i] = 0; + } + } + + int operator[] ( int index ) const + { + Assert(index >= 0 && index < ARRAYSIZE(m_iValue)); + return m_iValue[index]; + } + + int& operator[] ( int index ) + { + Assert(index >= 0 && index < ARRAYSIZE(m_iValue)); + return m_iValue[index]; + } + + void Aggregate( const StatsCollection_t& other ); + +private: + int m_iValue[CSSTAT_MAX]; +}; + + +//============================================================================= +// HPE_BEGIN: +// [tj] A couple variations on the RoundStats structure to handle extra operations +// for averaging and accumulating +//============================================================================= +struct RoundStatsDirectAverage_t +{ + float m_fStat[CSSTAT_MAX]; + + + RoundStatsDirectAverage_t() + { + Reset(); + } + + void Reset() + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] = 0; + } + } + + RoundStatsDirectAverage_t& operator +=( const StatsCollection_t &other ) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] += other[i]; + } + return *this; + } + + RoundStatsDirectAverage_t& operator /=( const float &divisor) + { + if (divisor > 0) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] /= divisor; + } + } + return *this; + } + + RoundStatsDirectAverage_t& operator *=( const float &divisor) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] *= divisor; + } + return *this; + } +}; + + +struct RoundStatsRollingAverage_t +{ + float m_fStat[CSSTAT_MAX]; + int m_numberOfDataSets; + + RoundStatsRollingAverage_t() + { + Reset(); + } + + void Reset() + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] = 0; + } + m_numberOfDataSets = 0; + } + + RoundStatsRollingAverage_t& operator +=( const RoundStatsRollingAverage_t &other ) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] += other.m_fStat[i]; + } + return *this; + } + + RoundStatsRollingAverage_t& operator +=( const StatsCollection_t &other ) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] += other[i]; + } + return *this; + } + + RoundStatsRollingAverage_t& operator /=( const float &divisor) + { + if (divisor > 0) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] /= divisor; + } + } + return *this; + } + + void RollDataSetIntoAverage ( const RoundStatsRollingAverage_t &other ) + { + for ( int i = 0; i < ARRAYSIZE( m_fStat ); i++ ) + { + m_fStat[i] *= m_numberOfDataSets; + m_fStat[i] += other.m_fStat[i]; + m_fStat[i] /= (m_numberOfDataSets + 1); + } + m_numberOfDataSets++; + } +}; +//============================================================================= +// HPE_END +//============================================================================= + +enum CSGameStatsVersions_t +{ + CS_GAMESTATS_FILE_VERSION = 006, + CS_GAMESTATS_MAGIC = 0xDEADBEEF +}; + +struct CS_Gamestats_Version_t +{ + int m_iMagic; // always CS_GAMESTATS_MAGIC + int m_iVersion; +}; + + +struct KillStats_t +{ + KillStats_t() { Reset(); } + + void Reset() + { + Q_memset( iNumKilled, 0, sizeof( iNumKilled ) ); + Q_memset( iNumKilledBy, 0, sizeof( iNumKilledBy ) ); + Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) ); + } + + int iNumKilled[MAX_PLAYERS+1]; // how many times this player has killed each other player + int iNumKilledBy[MAX_PLAYERS+1]; // how many times this player has been killed by each other player + int iNumKilledByUnanswered[MAX_PLAYERS+1]; // how many unanswered kills this player has been dealt by each other player +}; + +//============================================================================= +// +// CS Player Stats +// +struct PlayerStats_t +{ + PlayerStats_t() + { + Reset(); + } + + void Reset() + { + statsDelta.Reset(); + statsCurrentRound.Reset(); + statsCurrentMatch.Reset(); + statsKills.Reset(); + } + + PlayerStats_t( const PlayerStats_t &other ) + { + statsDelta = other.statsDelta; + statsCurrentRound = other.statsCurrentRound; + statsCurrentMatch = other.statsCurrentMatch; + } + + StatsCollection_t statsDelta; + StatsCollection_t statsCurrentRound; + StatsCollection_t statsCurrentMatch; + KillStats_t statsKills; +}; + + +struct WeaponName_StatId +{ + CSWeaponID weaponId; + CSStatType_t killStatId; + CSStatType_t shotStatId; + CSStatType_t hitStatId; + CSStatType_t damageStatId; +}; + +struct MapName_MapStatId +{ + const char* szMapName; + CSStatType_t statWinsId; + CSStatType_t statRoundsId; +}; + +extern const MapName_MapStatId MapName_StatId_Table[]; + +//A mapping from weapon names to weapon stat IDs +extern const WeaponName_StatId WeaponName_StatId_Table[]; + +//Used to look up the appropriate entry by the ID of the actual weapon +const WeaponName_StatId& GetWeaponTableEntryFromWeaponId(CSWeaponID id); + +#include "steamworks_gamestats.h" + +//============================================================================= +// +// Helper functions for creating key values +// +void AddDataToKV( KeyValues* pKV, const char* name, int data ); +void AddDataToKV( KeyValues* pKV, const char* name, uint64 data ); +void AddDataToKV( KeyValues* pKV, const char* name, float data ); +void AddDataToKV( KeyValues* pKV, const char* name, bool data ); +void AddDataToKV( KeyValues* pKV, const char* name, const char* data ); +void AddDataToKV( KeyValues* pKV, const char* name, const Color& data ); +void AddDataToKV( KeyValues* pKV, const char* name, short data ); +void AddDataToKV( KeyValues* pKV, const char* name, unsigned data ); +void AddDataToKV( KeyValues* pKV, const char* name, const Vector& data ); +void AddPositionDataToKV( KeyValues* pKV, const char* name, const Vector &data ); +//============================================================================= + +//============================================================================= +// +// Helper functions for creating key values from arrays +// +void AddArrayDataToKV( KeyValues* pKV, const char* name, const short *data, unsigned size ); +void AddArrayDataToKV( KeyValues* pKV, const char* name, const byte *data, unsigned size ); +void AddArrayDataToKV( KeyValues* pKV, const char* name, const unsigned *data, unsigned size ); +void AddStringDataToKV( KeyValues* pKV, const char* name, const char *data ); + +//============================================================================= + +// Macros to ease the creation of SendData method for stats structs/classes +#define BEGIN_STAT_TABLE( tableName ) \ + static const char* GetStatTableName( void ) { return tableName; } \ + void BuildGamestatDataTable( KeyValues* pKV ) \ +{ \ + pKV->SetName( GetStatTableName() ); + +#define REGISTER_STAT( varName ) \ + AddDataToKV(pKV, #varName, varName); + +#define REGISTER_STAT_NAMED( varName, dbName ) \ + AddDataToKV(pKV, dbName, varName); + +#define REGISTER_STAT_POSITION( varName ) \ + AddPositionDataToKV(pKV, #varName, varName); + +#define REGISTER_STAT_POSITION_NAMED( varName, dbName ) \ + AddPositionDataToKV(pKV, dbName, varName); + +#define REGISTER_STAT_ARRAY( varName ) \ + AddArrayDataToKV( pKV, #varName, varName, ARRAYSIZE( varName ) ); + +#define REGISTER_STAT_ARRAY_NAMED( varName, dbName ) \ + AddArrayDataToKV( pKV, dbName, varName, ARRAYSIZE( varName ) ); + +#define REGISTER_STAT_STRING( varName ) \ + AddStringDataToKV( pKV, #varName, varName ); + +#define REGISTER_STAT_STRING_NAMED( varName, dbName ) \ + AddStringDataToKV( pKV, dbName, varName ); + +#define AUTO_STAT_TABLE_KEY() \ + pKV->SetInt( "TimeSubmitted", GetUniqueIDForStatTable( *this ) ); + +#define END_STAT_TABLE() \ + pKV->SetUint64( ::BaseStatData::m_bUseGlobalData ? "TimeSubmitted" : "SessionTime", ::BaseStatData::TimeSubmitted ); \ + GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKV ); \ +} + +//----------------------------------------------------------------------------- +// Purpose: Templatized class for getting unique ID's for stat tables that need +// to be submitted multiple times per-session. +//----------------------------------------------------------------------------- + +template < typename T > +class UniqueStatID_t +{ +public: + static unsigned GetNext( void ) + { + return ++s_nLastID; + } + + static void Reset( void ) + { + s_nLastID = 0; + } + +private: + static unsigned s_nLastID; +}; + +template < typename T > +unsigned UniqueStatID_t< T >::s_nLastID = 0; + +template < typename T > +unsigned GetUniqueIDForStatTable( const T &table ) +{ + return UniqueStatID_t< T >::GetNext(); +} + + +//============================================================================= +// +// An interface for tracking gamestats. +// +class IGameStatTracker +{ +public: + + //----------------------------------------------------------------------------- + // Templatized methods to track a per-mission stat. + // The stat is copied, then deleted after it's sent to the SQL server. + //----------------------------------------------------------------------------- + template < typename T > + void SubmitStat( T& stat ) + { + // Make a copy of the stat. All of the stat lists require pointers, + // so we need to protect against a stat allocated on the stack + T* pT = new T(); + if( !pT ) + return; + + *pT = stat; + SubmitStat( pT ); + } + + //----------------------------------------------------------------------------- + // Templatized methods to track a per-mission stat (by pointer) + // The stat is deleted after it's sent to the SQL server + //----------------------------------------------------------------------------- + template < typename T > + void SubmitStat( T* pStat ) + { + // Get the static stat table for this type and add the stat to it + GetStatTable<T>()->AddToTail( pStat ); + } + + //----------------------------------------------------------------------------- + // Add all stats to an existing key value file for submit. + //----------------------------------------------------------------------------- + virtual void SubmitGameStats( KeyValues *pKV ) = 0; + + //----------------------------------------------------------------------------- + // Prints the memory usage of all of the stats being tracked + //----------------------------------------------------------------------------- + void PrintGamestatMemoryUsage( void ); + +protected: + //============================================================================= + // + // Used as a base interface to store a list of all templatized stat containers + // + class IStatContainer + { + public: + virtual void SendData( KeyValues *pKV ) = 0; + virtual void Clear( void ) = 0; + virtual void PrintMemoryUsage( void ) = 0; + }; + + // Defines a list of stat containers. + typedef CUtlVector< IStatContainer* > StatContainerList_t; + + //----------------------------------------------------------------------------- + // Used to get a list of all stats containers being tracked by the deriving class + //----------------------------------------------------------------------------- + virtual StatContainerList_t* GetStatContainerList( void ) = 0; + +private: + + //============================================================================= + // + // Templatized list of stats submitted + // + template < typename T > + class CGameStatList : public IStatContainer, public CUtlVector< T* > + { + public: + //----------------------------------------------------------------------------- + // Get data ready to send to the SQL server + //----------------------------------------------------------------------------- + virtual void SendData( KeyValues *pKV ) + { + //ASSERT( pKV != NULL ); + + // Duplicate the master KeyValue for each stat instance + for( int i=0; i < this->m_Size; ++i ) + { + // Make a copy of the master key value and build the stat table + KeyValues *pKVCopy = this->operator [](i)->m_bUseGlobalData ? pKV->MakeCopy() : new KeyValues( "" ); + this->operator [](i)->BuildGamestatDataTable( pKVCopy ); + } + + // Reset unique ID counter for the stat type + UniqueStatID_t< T >::Reset(); + } + + //----------------------------------------------------------------------------- + // Clear and delete every stat in this list + //----------------------------------------------------------------------------- + virtual void Clear( void ) + { + this->PurgeAndDeleteElements(); + } + + //----------------------------------------------------------------------------- + // Print out details about this lists memory usage + //----------------------------------------------------------------------------- + virtual void PrintMemoryUsage( void ) + { + if( this->m_Size == 0 ) + return; + + // Compute the memory used as the size of type times the list count + unsigned uMemoryUsed = this->m_Size * ( sizeof( T ) ); + + Msg( " %d\tbytes used by %s table\n", uMemoryUsed, T::GetStatTableName() ); + } + }; + + //----------------------------------------------------------------------------- + // Templatized method to get a single instance of a stat list per data type. + //----------------------------------------------------------------------------- + template < typename T > + CGameStatList< T >* GetStatTable( void ) + { + static CGameStatList< T > *s_vecOfType = 0; + if( s_vecOfType == 0 ) + { + s_vecOfType = new CGameStatList< T >(); + GetStatContainerList()->AddToTail( s_vecOfType ); + } + return s_vecOfType; + } + +}; + +struct BaseStatData +{ + BaseStatData( bool bUseGlobalData = true ) : m_bUseGlobalData( bUseGlobalData ) + { + TimeSubmitted = GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch(); + } + + bool m_bUseGlobalData; + uint64 TimeSubmitted; + +}; + +extern ConVar sv_noroundstats; + +#endif // CS_GAMESTATS_SHARED_H diff --git a/game/shared/cstrike/cs_player_shared.cpp b/game/shared/cstrike/cs_player_shared.cpp new file mode 100644 index 0000000..e0f3d91 --- /dev/null +++ b/game/shared/cstrike/cs_player_shared.cpp @@ -0,0 +1,943 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "decals.h" +#include "cs_gamerules.h" +#include "weapon_c4.h" +#include "in_buttons.h" +#include "datacache/imdlcache.h" + +#ifdef CLIENT_DLL + #include "c_cs_player.h" +#else + #include "cs_player.h" + #include "soundent.h" + #include "bot/cs_bot.h" + #include "KeyValues.h" + #include "triggers.h" + #include "cs_gamestats.h" +#endif + +#include "cs_playeranimstate.h" +#include "basecombatweapon_shared.h" +#include "util_shared.h" +#include "takedamageinfo.h" +#include "effect_dispatch_data.h" +#include "engine/ivdebugoverlay.h" +#include "obstacle_pushaway.h" +#include "props_shared.h" + +ConVar sv_showimpacts("sv_showimpacts", "0", FCVAR_REPLICATED, "Shows client (red) and server (blue) bullet impact point (1=both, 2=client-only, 3=server-only)" ); +ConVar sv_showplayerhitboxes( "sv_showplayerhitboxes", "0", FCVAR_REPLICATED, "Show lag compensated hitboxes for the specified player index whenever a player fires." ); + +#define CS_MASK_SHOOT (MASK_SOLID|CONTENTS_DEBRIS) + +void DispatchEffect( const char *pName, const CEffectData &data ); + + +#ifdef _DEBUG + + // This is some extra code to collect weapon accuracy stats: + + struct bulletdata_s + { + float timedelta; // time delta since first shot of this round + float derivation; // derivation for first shoot view angle + int count; + }; + + #define STATS_MAX_BULLETS 50 + + static bulletdata_s s_bullet_stats[STATS_MAX_BULLETS]; + + Vector s_firstImpact = Vector(0,0,0); + float s_firstTime = 0; + float s_LastTime = 0; + int s_bulletCount = 0; + + void ResetBulletStats() + { + s_firstTime = 0; + s_LastTime = 0; + s_bulletCount = 0; + s_firstImpact = Vector(0,0,0); + Q_memset( s_bullet_stats, 0, sizeof(s_bullet_stats) ); + } + + void PrintBulletStats() + { + for (int i=0; i<STATS_MAX_BULLETS; i++ ) + { + if (s_bullet_stats[i].count == 0) + break; + + Msg("%3i;%3i;%.4f;%.4f\n", i, s_bullet_stats[i].count, + s_bullet_stats[i].timedelta, s_bullet_stats[i].derivation ); + } + } + + void AddBulletStat( float time, float dist, Vector &impact ) + { + if ( time > s_LastTime + 2.0f ) + { + // time delta since last shoot is bigger than 2 seconds, start new row + s_LastTime = s_firstTime = time; + s_bulletCount = 0; + s_firstImpact = impact; + + } + else + { + s_LastTime = time; + s_bulletCount++; + } + + if ( s_bulletCount >= STATS_MAX_BULLETS ) + s_bulletCount = STATS_MAX_BULLETS -1; + + if ( dist < 1 ) + dist = 1; + + int i = s_bulletCount; + + float offset = VectorLength( s_firstImpact - impact ); + + float timedelta = time - s_firstTime; + float derivation = offset / dist; + + float weight = (float)s_bullet_stats[i].count/(float)(s_bullet_stats[i].count+1); + + s_bullet_stats[i].timedelta *= weight; + s_bullet_stats[i].timedelta += (1.0f-weight) * timedelta; + + s_bullet_stats[i].derivation *= weight; + s_bullet_stats[i].derivation += (1.0f-weight) * derivation; + + s_bullet_stats[i].count++; + } + + CON_COMMAND( stats_bullets_reset, "Reset bullet stats") + { + ResetBulletStats(); + } + + CON_COMMAND( stats_bullets_print, "Print bullet stats") + { + PrintBulletStats(); + } + +#endif + +float CCSPlayer::GetPlayerMaxSpeed() +{ + if ( GetMoveType() == MOVETYPE_NONE ) + { + return CS_PLAYER_SPEED_STOPPED; + } + + if ( IsObserver() ) + { + // Player gets speed bonus in observer mode + return CS_PLAYER_SPEED_OBSERVER; + } + + bool bValidMoveState = ( State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE ); + if ( !bValidMoveState || m_bIsDefusing || CSGameRules()->IsFreezePeriod() ) + { + // Player should not move during the freeze period + return CS_PLAYER_SPEED_STOPPED; + } + + float speed = BaseClass::GetPlayerMaxSpeed(); + + if ( IsVIP() == true ) // VIP is slow due to the armour he's wearing + { + speed = MIN(speed, CS_PLAYER_SPEED_VIP); + } + else + { + + CWeaponCSBase *pWeapon = dynamic_cast<CWeaponCSBase*>( GetActiveWeapon() ); + + if ( pWeapon ) + { + if ( HasShield() && IsShieldDrawn() ) + { + speed = MIN(speed, CS_PLAYER_SPEED_SHIELD); + } + else + { + speed = MIN(speed, pWeapon->GetMaxSpeed()); + } + } + } + + return speed; +} + + +void CCSPlayer::GetBulletTypeParameters( + int iBulletType, + float &fPenetrationPower, + float &flPenetrationDistance ) +{ + //MIKETODO: make ammo types come from a script file. + if ( IsAmmoType( iBulletType, BULLET_PLAYER_50AE ) ) + { + fPenetrationPower = 30; + flPenetrationDistance = 1000.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_762MM ) ) + { + fPenetrationPower = 39; + flPenetrationDistance = 5000.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_556MM ) || + IsAmmoType( iBulletType, BULLET_PLAYER_556MM_BOX ) ) + { + fPenetrationPower = 35; + flPenetrationDistance = 4000.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_338MAG ) ) + { + fPenetrationPower = 45; + flPenetrationDistance = 8000.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_9MM ) ) + { + fPenetrationPower = 21; + flPenetrationDistance = 800.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_BUCKSHOT ) ) + { + fPenetrationPower = 0; + flPenetrationDistance = 0.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_45ACP ) ) + { + fPenetrationPower = 15; + flPenetrationDistance = 500.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_357SIG ) ) + { + fPenetrationPower = 25; + flPenetrationDistance = 800.0; + } + else if ( IsAmmoType( iBulletType, BULLET_PLAYER_57MM ) ) + { + fPenetrationPower = 30; + flPenetrationDistance = 2000.0; + } + else + { + // What kind of ammo is this? + Assert( false ); + fPenetrationPower = 0; + flPenetrationDistance = 0.0; + } +} + +static void GetMaterialParameters( int iMaterial, float &flPenetrationModifier, float &flDamageModifier ) +{ + switch ( iMaterial ) + { + case CHAR_TEX_METAL : + flPenetrationModifier = 0.5; // If we hit metal, reduce the thickness of the brush we can't penetrate + flDamageModifier = 0.3; + break; + case CHAR_TEX_DIRT : + flPenetrationModifier = 0.5; + flDamageModifier = 0.3; + break; + case CHAR_TEX_CONCRETE : + flPenetrationModifier = 0.4; + flDamageModifier = 0.25; + break; + case CHAR_TEX_GRATE : + flPenetrationModifier = 1.0; + flDamageModifier = 0.99; + break; + case CHAR_TEX_VENT : + flPenetrationModifier = 0.5; + flDamageModifier = 0.45; + break; + case CHAR_TEX_TILE : + flPenetrationModifier = 0.65; + flDamageModifier = 0.3; + break; + case CHAR_TEX_COMPUTER : + flPenetrationModifier = 0.4; + flDamageModifier = 0.45; + break; + case CHAR_TEX_WOOD : + flPenetrationModifier = 1.0; + flDamageModifier = 0.6; + break; + default : + flPenetrationModifier = 1.0; + flDamageModifier = 0.5; + break; + } + + Assert( flPenetrationModifier > 0 ); + Assert( flDamageModifier < 1.0f ); // Less than 1.0f for avoiding infinite loops +} + + +static bool TraceToExit(Vector &start, Vector &dir, Vector &end, float flStepSize, float flMaxDistance ) +{ + float flDistance = 0; + Vector last = start; + + while ( flDistance <= flMaxDistance ) + { + flDistance += flStepSize; + + end = start + flDistance *dir; + + if ( (UTIL_PointContents ( end ) & MASK_SOLID) == 0 ) + { + // found first free point + return true; + } + } + + return false; +} + +inline void UTIL_TraceLineIgnoreTwoEntities( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, + const IHandleEntity *ignore, const IHandleEntity *ignore2, int collisionGroup, trace_t *ptr ) +{ + Ray_t ray; + ray.Init( vecAbsStart, vecAbsEnd ); + CTraceFilterSkipTwoEntities traceFilter( ignore, ignore2, collisionGroup ); + enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); + if( r_visualizetraces.GetBool() ) + { + DebugDrawLine( ptr->startpos, ptr->endpos, 255, 0, 0, true, -1.0f ); + } +} + +void CCSPlayer::FireBullet( + Vector vecSrc, // shooting postion + const QAngle &shootAngles, //shooting angle + float flDistance, // max distance + int iPenetration, // how many obstacles can be penetrated + int iBulletType, // ammo type + int iDamage, // base damage + float flRangeModifier, // damage range modifier + CBaseEntity *pevAttacker, // shooter + bool bDoEffects, + float xSpread, float ySpread + ) +{ + float fCurrentDamage = iDamage; // damage of the bullet at it's current trajectory + float flCurrentDistance = 0.0; //distance that the bullet has traveled so far + + Vector vecDirShooting, vecRight, vecUp; + AngleVectors( shootAngles, &vecDirShooting, &vecRight, &vecUp ); + + // MIKETODO: put all the ammo parameters into a script file and allow for CS-specific params. + float flPenetrationPower = 0; // thickness of a wall that this bullet can penetrate + float flPenetrationDistance = 0; // distance at which the bullet is capable of penetrating a wall + float flDamageModifier = 0.5; // default modification of bullets power after they go through a wall. + float flPenetrationModifier = 1.f; + + GetBulletTypeParameters( iBulletType, flPenetrationPower, flPenetrationDistance ); + + + if ( !pevAttacker ) + pevAttacker = this; // the default attacker is ourselves + + // add the spray + Vector vecDir = vecDirShooting + xSpread * vecRight + ySpread * vecUp; + + VectorNormalize( vecDir ); + + //Adrian: visualize server/client player positions + //This is used to show where the lag compesator thinks the player should be at. +#if 0 + for ( int k = 1; k <= gpGlobals->maxClients; k++ ) + { + CBasePlayer *clientClass = (CBasePlayer *)CBaseEntity::Instance( k ); + + if ( clientClass == NULL ) + continue; + + if ( k == entindex() ) + continue; + +#ifdef CLIENT_DLL + debugoverlay->AddBoxOverlay( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), QAngle( 0, 0, 0), 255,0,0,127, 4 ); +#else + NDebugOverlay::Box( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), 0,0,255,127, 4 ); +#endif + + } + +#endif + + +//============================================================================= +// HPE_BEGIN: +//============================================================================= + +#ifndef CLIENT_DLL + // [pfreese] Track number player entities killed with this bullet + int iPenetrationKills = 0; + + // [menglish] Increment the shots fired for this player + CCS_GameStats.Event_ShotFired( this, GetActiveWeapon() ); +#endif + +//============================================================================= +// HPE_END +//============================================================================= + + bool bFirstHit = true; + + CBasePlayer *lastPlayerHit = NULL; + + if( sv_showplayerhitboxes.GetInt() > 0 ) + { + CBasePlayer *lagPlayer = UTIL_PlayerByIndex( sv_showplayerhitboxes.GetInt() ); + if( lagPlayer ) + { +#ifdef CLIENT_DLL + lagPlayer->DrawClientHitboxes(4, true); +#else + lagPlayer->DrawServerHitboxes(4, true); +#endif + } + } + + MDLCACHE_CRITICAL_SECTION(); + while ( fCurrentDamage > 0 ) + { + Vector vecEnd = vecSrc + vecDir * flDistance; + + trace_t tr; // main enter bullet trace + + UTIL_TraceLineIgnoreTwoEntities( vecSrc, vecEnd, CS_MASK_SHOOT|CONTENTS_HITBOX, this, lastPlayerHit, COLLISION_GROUP_NONE, &tr ); + { + CTraceFilterSkipTwoEntities filter( this, lastPlayerHit, COLLISION_GROUP_NONE ); + + // Check for player hitboxes extending outside their collision bounds + const float rayExtension = 40.0f; + UTIL_ClipTraceToPlayers( vecSrc, vecEnd + vecDir * rayExtension, CS_MASK_SHOOT|CONTENTS_HITBOX, &filter, &tr ); + } + + lastPlayerHit = ToBasePlayer(tr.m_pEnt); + + if ( tr.fraction == 1.0f ) + break; // we didn't hit anything, stop tracing shoot + +#ifdef _DEBUG + if ( bFirstHit ) + AddBulletStat( gpGlobals->realtime, VectorLength( vecSrc-tr.endpos), tr.endpos ); +#endif + + bFirstHit = false; + +#ifndef CLIENT_DLL + // + // Propogate a bullet impact event + // @todo Add this for shotgun pellets (which dont go thru here) + // + IGameEvent * event = gameeventmanager->CreateEvent( "bullet_impact" ); + if ( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetFloat( "x", tr.endpos.x ); + event->SetFloat( "y", tr.endpos.y ); + event->SetFloat( "z", tr.endpos.z ); + gameeventmanager->FireEvent( event ); + } +#endif + + /************* MATERIAL DETECTION ***********/ + surfacedata_t *pSurfaceData = physprops->GetSurfaceData( tr.surface.surfaceProps ); + int iEnterMaterial = pSurfaceData->game.material; + + GetMaterialParameters( iEnterMaterial, flPenetrationModifier, flDamageModifier ); + + bool hitGrate = tr.contents & CONTENTS_GRATE; + + // since some railings in de_inferno are CONTENTS_GRATE but CHAR_TEX_CONCRETE, we'll trust the + // CONTENTS_GRATE and use a high damage modifier. + if ( hitGrate ) + { + // If we're a concrete grate (TOOLS/TOOLSINVISIBLE texture) allow more penetrating power. + flPenetrationModifier = 1.0f; + flDamageModifier = 0.99f; + } + +#ifdef CLIENT_DLL + if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 2 ) + { + // draw red client impact markers + debugoverlay->AddBoxOverlay( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), QAngle( 0, 0, 0), 255,0,0,127, 4 ); + + if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() ) + { + C_BasePlayer *player = ToBasePlayer( tr.m_pEnt ); + player->DrawClientHitboxes( 4, true ); + } + } +#else + if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 3 ) + { + // draw blue server impact markers + NDebugOverlay::Box( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,0,255,127, 4 ); + + if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() ) + { + CBasePlayer *player = ToBasePlayer( tr.m_pEnt ); + player->DrawServerHitboxes( 4, true ); + } + } +#endif + + //calculate the damage based on the distance the bullet travelled. + flCurrentDistance += tr.fraction * flDistance; + fCurrentDamage *= pow (flRangeModifier, (flCurrentDistance / 500)); + + // check if we reach penetration distance, no more penetrations after that + if (flCurrentDistance > flPenetrationDistance && iPenetration > 0) + iPenetration = 0; + +#ifndef CLIENT_DLL + // This just keeps track of sounds for AIs (it doesn't play anything). + CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 400, 0.2f, this ); +#endif + + int iDamageType = DMG_BULLET | DMG_NEVERGIB; + + if( bDoEffects ) + { + // See if the bullet ended up underwater + started out of the water + if ( enginetrace->GetPointContents( tr.endpos ) & (CONTENTS_WATER|CONTENTS_SLIME) ) + { + trace_t waterTrace; + UTIL_TraceLine( vecSrc, tr.endpos, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &waterTrace ); + + if( waterTrace.allsolid != 1 ) + { + CEffectData data; + data.m_vOrigin = waterTrace.endpos; + data.m_vNormal = waterTrace.plane.normal; + data.m_flScale = random->RandomFloat( 8, 12 ); + + if ( waterTrace.contents & CONTENTS_SLIME ) + { + data.m_fFlags |= FX_WATER_IN_SLIME; + } + + DispatchEffect( "gunshotsplash", data ); + } + } + else + { + //Do Regular hit effects + + // Don't decal nodraw surfaces + if ( !( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) ) ) + { + CBaseEntity *pEntity = tr.m_pEnt; + if ( !( !friendlyfire.GetBool() && pEntity && pEntity->GetTeamNumber() == GetTeamNumber() ) ) + { + UTIL_ImpactTrace( &tr, iDamageType ); + } + } + } + } // bDoEffects + + // add damage to entity that we hit + +#ifndef CLIENT_DLL + ClearMultiDamage(); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Check if enemy players were killed by this bullet, and if so, + // add them to the iPenetrationKills count + //============================================================================= + + CBaseEntity *pEntity = tr.m_pEnt; + + CTakeDamageInfo info( pevAttacker, pevAttacker, fCurrentDamage, iDamageType ); + CalculateBulletDamageForce( &info, iBulletType, vecDir, tr.endpos ); + pEntity->DispatchTraceAttack( info, vecDir, &tr ); + + bool bWasAlive = pEntity->IsAlive(); + + TraceAttackToTriggers( info, tr.startpos, tr.endpos, vecDir ); + + ApplyMultiDamage(); + + if (bWasAlive && !pEntity->IsAlive() && pEntity->IsPlayer() && pEntity->GetTeamNumber() != GetTeamNumber()) + { + ++iPenetrationKills; + } + + //============================================================================= + // HPE_END + //============================================================================= + +#endif + + // check if bullet can penetrate another entity + if ( iPenetration == 0 && !hitGrate ) + break; // no, stop + + // If we hit a grate with iPenetration == 0, stop on the next thing we hit + if ( iPenetration < 0 ) + break; + + Vector penetrationEnd; + + // try to penetrate object, maximum penetration is 128 inch + if ( !TraceToExit( tr.endpos, vecDir, penetrationEnd, 24, 128 ) ) + break; + + // find exact penetration exit + trace_t exitTr; + UTIL_TraceLine( penetrationEnd, tr.endpos, CS_MASK_SHOOT|CONTENTS_HITBOX, NULL, &exitTr ); + + if( exitTr.m_pEnt != tr.m_pEnt && exitTr.m_pEnt != NULL ) + { + // something was blocking, trace again + UTIL_TraceLine( penetrationEnd, tr.endpos, CS_MASK_SHOOT|CONTENTS_HITBOX, exitTr.m_pEnt, COLLISION_GROUP_NONE, &exitTr ); + } + + // get material at exit point + pSurfaceData = physprops->GetSurfaceData( exitTr.surface.surfaceProps ); + int iExitMaterial = pSurfaceData->game.material; + + hitGrate = hitGrate && ( exitTr.contents & CONTENTS_GRATE ); + + // if enter & exit point is wood or metal we assume this is + // a hollow crate or barrel and give a penetration bonus + if ( iEnterMaterial == iExitMaterial ) + { + if( iExitMaterial == CHAR_TEX_WOOD || + iExitMaterial == CHAR_TEX_METAL ) + { + flPenetrationModifier *= 2; + } + } + + float flTraceDistance = VectorLength( exitTr.endpos - tr.endpos ); + + // check if bullet has enough power to penetrate this distance for this material + if ( flTraceDistance > ( flPenetrationPower * flPenetrationModifier ) ) + break; // bullet hasn't enough power to penetrate this distance + + // penetration was successful + + // bullet did penetrate object, exit Decal + if ( bDoEffects ) + { + UTIL_ImpactTrace( &exitTr, iDamageType ); + } + + //setup new start end parameters for successive trace + + flPenetrationPower -= flTraceDistance / flPenetrationModifier; + flCurrentDistance += flTraceDistance; + + // NDebugOverlay::Box( exitTr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,127, 8 ); + + vecSrc = exitTr.endpos; + flDistance = (flDistance - flCurrentDistance) * 0.5; + + // reduce damage power each time we hit something other than a grate + fCurrentDamage *= flDamageModifier; + + // reduce penetration counter + iPenetration--; + } + +#ifndef CLIENT_DLL + //============================================================================= + // HPE_BEGIN: + // [pfreese] If we killed at least two enemies with a single bullet, award the + // TWO_WITH_ONE_SHOT achievement + //============================================================================= + + if (iPenetrationKills >= 2) + { + AwardAchievement(CSKillTwoWithOneShot); + } + + //============================================================================= + // HPE_END + //============================================================================= +#endif +} + + +void CCSPlayer::UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity ) +{ + float speedSqr = vecVelocity.AsVector2D().LengthSqr(); + + // the fastest walk is 135 ( scout ), see CCSGameMovement::CheckParameters() + if ( speedSqr < 150.0 * 150.0 ) + return; // player is not running, no footsteps + + BaseClass::UpdateStepSound( psurface, vecOrigin, vecVelocity ); +} + + +// GOOSEMAN : Kick the view.. +void CCSPlayer::KickBack( float up_base, float lateral_base, float up_modifier, float lateral_modifier, float up_max, float lateral_max, int direction_change ) +{ + float flKickUp; + float flKickLateral; + + if (m_iShotsFired == 1) // This is the first round fired + { + flKickUp = up_base; + flKickLateral = lateral_base; + } + else + { + flKickUp = up_base + m_iShotsFired*up_modifier; + flKickLateral = lateral_base + m_iShotsFired*lateral_modifier; + } + + + QAngle angle = GetPunchAngle(); + + angle.x -= flKickUp; + if ( angle.x < -1 * up_max ) + angle.x = -1 * up_max; + + if ( m_iDirection == 1 ) + { + angle.y += flKickLateral; + if (angle.y > lateral_max) + angle.y = lateral_max; + } + else + { + angle.y -= flKickLateral; + if ( angle.y < -1 * lateral_max ) + angle.y = -1 * lateral_max; + } + + if ( !SharedRandomInt( "KickBack", 0, direction_change ) ) + m_iDirection = 1 - m_iDirection; + + SetPunchAngle( angle ); +} + + +bool CCSPlayer::CanMove() const +{ + // When we're in intro camera mode, it's important to return false here + // so our physics object doesn't fall out of the world. + if ( GetMoveType() == MOVETYPE_NONE ) + return false; + + if ( IsObserver() ) + return true; // observers can move all the time + + bool bValidMoveState = (State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE); + + if ( m_bIsDefusing || !bValidMoveState || CSGameRules()->IsFreezePeriod() ) + { + return false; + } + else + { + // Can't move while planting C4. + CC4 *pC4 = dynamic_cast< CC4* >( GetActiveWeapon() ); + if ( pC4 && pC4->m_bStartedArming ) + return false; + + return true; + } +} + + +void CCSPlayer::OnJump( float fImpulse ) +{ + CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon(); + if ( pActiveWeapon != NULL ) + pActiveWeapon->OnJump(fImpulse); +} + + +void CCSPlayer::OnLand( float fVelocity ) +{ + CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon(); + if ( pActiveWeapon != NULL ) + pActiveWeapon->OnLand(fVelocity); +} + + +//------------------------------------------------------------------------------------------------------------------------------- +/** +* Track the last time we were on a ladder, along with the ladder's normal and where we +* were grabbing it, so we don't reach behind us and grab it again as we are trying to +* dismount. +*/ +void CCSPlayer::SurpressLadderChecks( const Vector& pos, const Vector& normal ) +{ + m_ladderSurpressionTimer.Start( 1.0f ); + m_lastLadderPos = pos; + m_lastLadderNormal = normal; +} + + +//------------------------------------------------------------------------------------------------------------------------------- +/** +* Prevent us from re-grabbing the same ladder we were just on: +* - if the timer is elapsed, let us grab again +* - if the normal is different, let us grab +* - if the 2D pos is very different, let us grab, since it's probably a different ladder +*/ +bool CCSPlayer::CanGrabLadder( const Vector& pos, const Vector& normal ) +{ + if ( m_ladderSurpressionTimer.GetRemainingTime() <= 0.0f ) + { + return true; + } + + const float MaxDist = 64.0f; + if ( pos.AsVector2D().DistToSqr( m_lastLadderPos.AsVector2D() ) < MaxDist * MaxDist ) + { + return false; + } + + if ( normal != m_lastLadderNormal ) + { + return true; + } + + return false; +} + + +void CCSPlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + // In CS, its CPlayerAnimState object manages ALL the animation state. + return; +} + + +CWeaponCSBase* CCSPlayer::CSAnim_GetActiveWeapon() +{ + return GetActiveCSWeapon(); +} + + +bool CCSPlayer::CSAnim_CanMove() +{ + return CanMove(); +} + +//-------------------------------------------------------------------------------------------------------------- + +#define MATERIAL_NAME_LENGTH 16 + +#ifdef GAME_DLL + +class CFootstepControl : public CBaseTrigger +{ +public: + DECLARE_CLASS( CFootstepControl, CBaseTrigger ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual int UpdateTransmitState( void ); + virtual void Spawn( void ); + + CNetworkVar( string_t, m_source ); + CNetworkVar( string_t, m_destination ); +}; + +LINK_ENTITY_TO_CLASS( func_footstep_control, CFootstepControl ); + + +BEGIN_DATADESC( CFootstepControl ) + DEFINE_KEYFIELD( m_source, FIELD_STRING, "Source" ), + DEFINE_KEYFIELD( m_destination, FIELD_STRING, "Destination" ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CFootstepControl, DT_FootstepControl ) + SendPropStringT( SENDINFO(m_source) ), + SendPropStringT( SENDINFO(m_destination) ), +END_SEND_TABLE() + +int CFootstepControl::UpdateTransmitState( void ) +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CFootstepControl::Spawn( void ) +{ + InitTrigger(); +} + +#else + +//-------------------------------------------------------------------------------------------------------------- + +class C_FootstepControl : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FootstepControl, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_FootstepControl( void ); + ~C_FootstepControl(); + + char m_source[MATERIAL_NAME_LENGTH]; + char m_destination[MATERIAL_NAME_LENGTH]; +}; + +IMPLEMENT_CLIENTCLASS_DT(C_FootstepControl, DT_FootstepControl, CFootstepControl) + RecvPropString( RECVINFO(m_source) ), + RecvPropString( RECVINFO(m_destination) ), +END_RECV_TABLE() + +CUtlVector< C_FootstepControl * > s_footstepControllers; + +C_FootstepControl::C_FootstepControl( void ) +{ + s_footstepControllers.AddToTail( this ); +} + +C_FootstepControl::~C_FootstepControl() +{ + s_footstepControllers.FindAndRemove( this ); +} + +surfacedata_t * CCSPlayer::GetFootstepSurface( const Vector &origin, const char *surfaceName ) +{ + for ( int i=0; i<s_footstepControllers.Count(); ++i ) + { + C_FootstepControl *control = s_footstepControllers[i]; + + if ( FStrEq( control->m_source, surfaceName ) ) + { + if ( control->CollisionProp()->IsPointInBounds( origin ) ) + { + return physprops->GetSurfaceData( physprops->GetSurfaceIndex( control->m_destination ) ); + } + } + } + + return physprops->GetSurfaceData( physprops->GetSurfaceIndex( surfaceName ) ); +} + +#endif + + diff --git a/game/shared/cstrike/cs_playeranimstate.cpp b/game/shared/cstrike/cs_playeranimstate.cpp new file mode 100644 index 0000000..19c3a18 --- /dev/null +++ b/game/shared/cstrike/cs_playeranimstate.cpp @@ -0,0 +1,1057 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "cs_playeranimstate.h" +#include "base_playeranimstate.h" +#include "tier0/vprof.h" +#include "animation.h" +#include "weapon_csbase.h" +#include "studio.h" +#include "apparent_velocity_helper.h" +#include "utldict.h" +#include "weapon_basecsgrenade.h" +#include "datacache/imdlcache.h" + +#ifdef CLIENT_DLL + #include "c_cs_player.h" + #include "bone_setup.h" + #include "interpolatedvar.h" + #include "c_cs_hostage.h" +#else + #include "cs_player.h" + #include "cs_simple_hostage.h" + #include "cs_gamestats.h" +#endif + +#define ANIM_TOPSPEED_WALK 100 +#define ANIM_TOPSPEED_RUN 250 +#define ANIM_TOPSPEED_RUN_CROUCH 85 + +#define DEFAULT_IDLE_NAME "idle_upper_" +#define DEFAULT_CROUCH_IDLE_NAME "crouch_idle_upper_" +#define DEFAULT_CROUCH_WALK_NAME "crouch_walk_upper_" +#define DEFAULT_WALK_NAME "walk_upper_" +#define DEFAULT_RUN_NAME "run_upper_" + +#define DEFAULT_FIRE_IDLE_NAME "idle_shoot_" +#define DEFAULT_FIRE_CROUCH_NAME "crouch_idle_shoot_" +#define DEFAULT_FIRE_CROUCH_WALK_NAME "crouch_walk_shoot_" +#define DEFAULT_FIRE_WALK_NAME "walk_shoot_" +#define DEFAULT_FIRE_RUN_NAME "run_shoot_" + + +#define FIRESEQUENCE_LAYER (AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS+1) +#define RELOADSEQUENCE_LAYER (FIRESEQUENCE_LAYER + 1) +#define GRENADESEQUENCE_LAYER (RELOADSEQUENCE_LAYER + 1) +#define NUM_LAYERS_WANTED (GRENADESEQUENCE_LAYER + 1) + + + +// ------------------------------------------------------------------------------------------------ // +// CCSPlayerAnimState declaration. +// ------------------------------------------------------------------------------------------------ // + +class CCSPlayerAnimState : public CBasePlayerAnimState, public ICSPlayerAnimState +{ +public: + DECLARE_CLASS( CCSPlayerAnimState, CBasePlayerAnimState ); + friend ICSPlayerAnimState* CreatePlayerAnimState( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ); + + CCSPlayerAnimState(); + + virtual void DoAnimationEvent( PlayerAnimEvent_t event, int nData ); + virtual bool IsThrowingGrenade(); + virtual int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ); + virtual void ClearAnimationState(); + virtual bool CanThePlayerMove(); + virtual float GetCurrentMaxGroundSpeed(); + virtual Activity CalcMainActivity(); + virtual void DebugShowAnimState( int iStartLine ); + virtual void ComputeSequences( CStudioHdr *pStudioHdr ); + virtual void ClearAnimationLayers(); + virtual int SelectWeightedSequence( Activity activity ); + + void InitCS( CBaseAnimatingOverlay *pPlayer, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ); + +protected: + + int CalcFireLayerSequence(PlayerAnimEvent_t event); + void ComputeFireSequence( CStudioHdr *pStudioHdr ); + + void ComputeReloadSequence( CStudioHdr *pStudioHdr ); + int CalcReloadLayerSequence( PlayerAnimEvent_t event ); + + bool IsOuterGrenadePrimed(); + void ComputeGrenadeSequence( CStudioHdr *pStudioHdr ); + int CalcGrenadePrimeSequence(); + int CalcGrenadeThrowSequence(); + int GetOuterGrenadeThrowCounter(); + + const char* GetWeaponSuffix(); + bool HandleJumping(); + + void UpdateLayerSequenceGeneric( CStudioHdr *pStudioHdr, int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd ); + + virtual int CalcSequenceIndex( const char *pBaseName, ... ); + +private: + + // Current state variables. + bool m_bJumping; // Set on a jump event. + float m_flJumpStartTime; + bool m_bFirstJumpFrame; + + // Aim sequence plays reload while this is on. + bool m_bReloading; + float m_flReloadCycle; + int m_iReloadSequence; + float m_flReloadHoldEndTime; // Intermediate shotgun reloads get held a fraction of a second + + // This is set to true if ANY animation is being played in the fire layer. + bool m_bFiring; // If this is on, then it'll continue the fire animation in the fire layer + // until it completes. + int m_iFireSequence; // (For any sequences in the fire layer, including grenade throw). + float m_flFireCycle; + PlayerAnimEvent_t m_delayedFire; // if we fire while reloading, delay the fire by one frame so we can cancel the reload first + + // These control grenade animations. + bool m_bThrowingGrenade; + bool m_bPrimingGrenade; + float m_flGrenadeCycle; + int m_iGrenadeSequence; + int m_iLastThrowGrenadeCounter; // used to detect when the guy threw the grenade. + + CCSPlayer *m_pPlayer; + + ICSPlayerAnimStateHelpers *m_pHelpers; + + void CheckCachedSequenceValidity( void ); + + int m_sequenceCache[ ACT_CROUCHIDLE+1 ]; // Cache the first N sequences, since we don't have weights. + int m_cachedModelIndex; // Model index for which the sequence cache is valid. + + CUtlDict<int,int> m_namedSequence; // Dictionary of sequences computed with CalcSequenceIndex. This is because LookupSequence is a performance hit - CS:S player models have 750+ sequences! +}; + + +ICSPlayerAnimState* CreatePlayerAnimState( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ) +{ + CCSPlayerAnimState *pRet = new CCSPlayerAnimState; + pRet->InitCS( pEntity, pHelpers, legAnimType, bUseAimSequences ); + return pRet; +} + + + + +//---------------------------------------------------------------------------------------------- +/** + * Hostage animation mechanism + */ +class CCSHostageAnimState : public CCSPlayerAnimState +{ +public: + DECLARE_CLASS( CCSHostageAnimState, CCSPlayerAnimState ); + + CCSHostageAnimState(); + + virtual Activity CalcMainActivity(); + + // No need to cache sequences, and we *do* have multiple sequences per activity + virtual int SelectWeightedSequence( Activity activity ) { return GetOuter()->SelectWeightedSequence( activity ); } +}; + + +//---------------------------------------------------------------------------------------------- +ICSPlayerAnimState* CreateHostageAnimState( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ) +{ + CCSHostageAnimState *anim = new CCSHostageAnimState; + anim->InitCS( pEntity, pHelpers, legAnimType, bUseAimSequences ); + return anim; +} + + +//---------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------- +CCSHostageAnimState::CCSHostageAnimState() +{ +} + + +//---------------------------------------------------------------------------------------------- +/** + * Set hostage animation state + */ +Activity CCSHostageAnimState::CalcMainActivity() +{ + float flOuterSpeed = GetOuterXYSpeed(); + + if ( HandleJumping() ) + { + return ACT_HOP; + } + else + { + Assert( dynamic_cast<CHostage*>( m_pOuter ) ); + CHostage *me = (CHostage*)m_pOuter; + + // if we have no leader, hang out + Activity idealActivity = me->GetLeader() ? ACT_IDLE : ACT_BUSY_QUEUE; + + if ( m_pOuter->GetFlags() & FL_DUCKING ) + { + if ( flOuterSpeed > MOVING_MINIMUM_SPEED ) + idealActivity = ACT_RUN_CROUCH; + else + idealActivity = ACT_COVER_LOW; + } + else + { + if ( flOuterSpeed > MOVING_MINIMUM_SPEED ) + { + if ( flOuterSpeed > ARBITRARY_RUN_SPEED ) + idealActivity = ACT_RUN; + else + idealActivity = ACT_WALK; + } + } + + return idealActivity; + } +} + + +// ------------------------------------------------------------------------------------------------ // +// CCSPlayerAnimState implementation. +// ------------------------------------------------------------------------------------------------ // + +CCSPlayerAnimState::CCSPlayerAnimState() +{ + m_pOuter = NULL; + + m_bJumping = false; + m_flJumpStartTime = 0.0f; + m_bFirstJumpFrame = false; + + m_bReloading = false; + m_flReloadCycle = 0.0f; + m_iReloadSequence = -1; + m_flReloadHoldEndTime = 0.0f; + + m_bFiring = false; + m_iFireSequence = -1; + m_flFireCycle = 0.0f; + m_delayedFire = PLAYERANIMEVENT_COUNT; + + m_bThrowingGrenade = false; + m_bPrimingGrenade = false; + m_flGrenadeCycle = 0.0f; + m_iGrenadeSequence = -1; + m_iLastThrowGrenadeCounter = 0; + m_cachedModelIndex = -1; + + m_pPlayer = NULL; + + m_pHelpers = NULL; +} + + +void CCSPlayerAnimState::InitCS( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ) +{ + CModAnimConfig config; + config.m_flMaxBodyYawDegrees = 90; + config.m_LegAnimType = legAnimType; + config.m_bUseAimSequences = bUseAimSequences; + + m_pPlayer = ToCSPlayer( pEntity ); + + m_pHelpers = pHelpers; + + BaseClass::Init( pEntity, config ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CCSPlayerAnimState::CheckCachedSequenceValidity( void ) +{ + if ( m_cachedModelIndex != GetOuter()->GetModelIndex() ) + { + m_namedSequence.RemoveAll(); + + m_cachedModelIndex = GetOuter()->GetModelIndex(); + for ( int i=0; i<=ACT_CROUCHIDLE; ++i ) + { + m_sequenceCache[i] = -1; + } + + // precache the sequences we'll be using for movement + if ( m_cachedModelIndex > 0 ) + { + m_sequenceCache[ACT_HOP - 1] = GetOuter()->SelectWeightedSequence( ACT_HOP ); + m_sequenceCache[ACT_IDLE - 1] = GetOuter()->SelectWeightedSequence( ACT_IDLE ); + m_sequenceCache[ACT_RUN_CROUCH - 1] = GetOuter()->SelectWeightedSequence( ACT_RUN_CROUCH ); + m_sequenceCache[ACT_CROUCHIDLE - 1] = GetOuter()->SelectWeightedSequence( ACT_CROUCHIDLE ); + m_sequenceCache[ACT_RUN - 1] = GetOuter()->SelectWeightedSequence( ACT_RUN ); + m_sequenceCache[ACT_WALK - 1] = GetOuter()->SelectWeightedSequence( ACT_WALK ); + m_sequenceCache[ACT_IDLE - 1] = GetOuter()->SelectWeightedSequence( ACT_IDLE ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Cache the sequence numbers for the first ACT_HOP activities, since the CS player doesn't have multiple + * sequences per activity. + */ +int CCSPlayerAnimState::SelectWeightedSequence( Activity activity ) +{ + VPROF( "CCSPlayerAnimState::ComputeMainSequence" ); + + if ( activity > ACT_CROUCHIDLE || activity < 1 ) + { + return GetOuter()->SelectWeightedSequence( activity ); + } + + CheckCachedSequenceValidity(); + + int sequence = m_sequenceCache[ activity - 1 ]; + if ( sequence < 0 ) + { + // just in case, look up the sequence if we didn't precache it above + sequence = m_sequenceCache[ activity - 1 ] = GetOuter()->SelectWeightedSequence( activity ); + } + +#if defined(CLIENT_DLL) && defined(_DEBUG) + int realSequence = GetOuter()->SelectWeightedSequence( activity ); + Assert( realSequence == sequence ); +#endif + + return sequence; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Try to look up named sequences in a CUtlDict cache before falling back to the normal LookupSequence. It's + * best to avoid the normal LookupSequence when your models have 750+ sequences... + */ +int CCSPlayerAnimState::CalcSequenceIndex( const char *pBaseName, ... ) +{ + VPROF( "CCSPlayerAnimState::CalcSequenceIndex" ); + + CheckCachedSequenceValidity(); + + char szFullName[512]; + va_list marker; + va_start( marker, pBaseName ); + Q_vsnprintf( szFullName, sizeof( szFullName ), pBaseName, marker ); + va_end( marker ); + + int iSequence = m_namedSequence.Find( szFullName ); + if ( iSequence == m_namedSequence.InvalidIndex() ) + { + iSequence = GetOuter()->LookupSequence( szFullName ); + m_namedSequence.Insert( szFullName, iSequence ); + } + else + { + iSequence = m_namedSequence[iSequence]; + } + +#if defined(CLIENT_DLL) && defined(_DEBUG) + int realSequence = GetOuter()->LookupSequence( szFullName ); + Assert( realSequence == iSequence ); +#endif + + // Show warnings if we can't find anything here. + if ( iSequence == -1 ) + { + static CUtlDict<int,int> dict; + if ( dict.Find( szFullName ) == -1 ) + { + dict.Insert( szFullName, 0 ); + Warning( "CalcSequenceIndex: can't find '%s'.\n", szFullName ); + } + + iSequence = 0; + } + + return iSequence; +} + + +void CCSPlayerAnimState::ClearAnimationState() +{ + m_bJumping = false; + m_bFiring = false; + m_bReloading = false; + m_flReloadHoldEndTime = 0.0f; + m_bThrowingGrenade = m_bPrimingGrenade = false; + m_iLastThrowGrenadeCounter = GetOuterGrenadeThrowCounter(); + + BaseClass::ClearAnimationState(); +} + + +void CCSPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) +{ + Assert( event != PLAYERANIMEVENT_THROW_GRENADE ); + + MDLCACHE_CRITICAL_SECTION(); + switch ( event ) + { + case PLAYERANIMEVENT_FIRE_GUN_PRIMARY: + case PLAYERANIMEVENT_FIRE_GUN_SECONDARY: + // Regardless of what we're doing in the fire layer, restart it. + m_flFireCycle = 0; + m_iFireSequence = CalcFireLayerSequence( event ); + m_bFiring = m_iFireSequence != -1; + + // If we are interrupting a (shotgun) reload, cancel the reload, and fire next frame. + if ( m_bFiring && m_bReloading ) + { + m_bReloading = false; + m_iReloadSequence = -1; + + m_delayedFire = event; + m_bFiring = false; + m_iFireSequence = -1; + + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( RELOADSEQUENCE_LAYER ); + if ( pLayer ) + { + pLayer->m_flWeight = 0.0f; + pLayer->m_nOrder = 15; + } + } + +#ifdef CLIENT_DLL + if ( m_bFiring && !m_bReloading ) + { + if ( m_pPlayer ) + { + m_pPlayer->ProcessMuzzleFlashEvent(); + } + } +#endif + break; + + case PLAYERANIMEVENT_JUMP: + // Play the jump animation. + m_bJumping = true; + m_bFirstJumpFrame = true; + m_flJumpStartTime = gpGlobals->curtime; + break; + + case PLAYERANIMEVENT_RELOAD: + { + // ignore normal reload events for shotguns - they get sent to trigger sounds etc only + CWeaponCSBase *pWeapon = m_pHelpers->CSAnim_GetActiveWeapon(); + if ( pWeapon && pWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_SHOTGUN ) + { + m_iReloadSequence = CalcReloadLayerSequence( event ); + if ( m_iReloadSequence != -1 ) + { + m_bReloading = true; + m_flReloadCycle = 0; + } + else + { + m_bReloading = false; + } + } + } + break; + + case PLAYERANIMEVENT_RELOAD_START: + case PLAYERANIMEVENT_RELOAD_LOOP: + // Set the hold time for _start and _loop anims, then fall through to the _end case + m_flReloadHoldEndTime = gpGlobals->curtime + 0.75f; + + case PLAYERANIMEVENT_RELOAD_END: + { + // ignore shotgun reload events for non-shotguns + CWeaponCSBase *pWeapon = m_pHelpers->CSAnim_GetActiveWeapon(); + if ( pWeapon && pWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_SHOTGUN ) + { + m_flReloadHoldEndTime = 0.0f; // clear this out in case we set it in _START or _LOOP above + } + else + { + m_iReloadSequence = CalcReloadLayerSequence( event ); + if ( m_iReloadSequence != -1 ) + { + m_bReloading = true; + m_flReloadCycle = 0; + } + else + { + m_bReloading = false; + } + } + } + break; + + case PLAYERANIMEVENT_CLEAR_FIRING: + { + m_iFireSequence = -1; + } + break; + + default: + Assert( !"CCSPlayerAnimState::DoAnimationEvent" ); + } +} + + +float g_flThrowGrenadeFraction = 0.25; +bool CCSPlayerAnimState::IsThrowingGrenade() +{ + if ( m_bThrowingGrenade ) + { + // An animation event would be more appropriate here. + return m_flGrenadeCycle < g_flThrowGrenadeFraction; + } + else + { + bool bThrowPending = (m_iLastThrowGrenadeCounter != GetOuterGrenadeThrowCounter()); + return bThrowPending || IsOuterGrenadePrimed(); + } +} + + +int CCSPlayerAnimState::CalcReloadLayerSequence( PlayerAnimEvent_t event ) +{ + if ( m_delayedFire != PLAYERANIMEVENT_COUNT ) + return -1; + + const char *weaponSuffix = GetWeaponSuffix(); + if ( !weaponSuffix ) + return -1; + + CWeaponCSBase *pWeapon = m_pHelpers->CSAnim_GetActiveWeapon(); + if ( !pWeapon ) + return -1; + + const char *prefix = ""; + switch ( GetCurrentMainSequenceActivity() ) + { + case ACT_PLAYER_RUN_FIRE: + case ACT_RUN: + prefix = "run"; + break; + + case ACT_PLAYER_WALK_FIRE: + case ACT_WALK: + prefix = "walk"; + break; + + case ACT_PLAYER_CROUCH_FIRE: + case ACT_CROUCHIDLE: + prefix = "crouch_idle"; + break; + + case ACT_PLAYER_CROUCH_WALK_FIRE: + case ACT_RUN_CROUCH: + prefix = "crouch_walk"; + break; + + default: + case ACT_PLAYER_IDLE_FIRE: + prefix = "idle"; + break; + } + + const char *reloadSuffix = ""; + switch ( event ) + { + case PLAYERANIMEVENT_RELOAD_START: + reloadSuffix = "_start"; + break; + + case PLAYERANIMEVENT_RELOAD_LOOP: + reloadSuffix = "_loop"; + break; + + case PLAYERANIMEVENT_RELOAD_END: + reloadSuffix = "_end"; + break; + } + + // First, look for <prefix>_reload_<weapon name><_start|_loop|_end>. + char szName[512]; + Q_snprintf( szName, sizeof( szName ), "%s_reload_%s%s", prefix, weaponSuffix, reloadSuffix ); + int iReloadSequence = m_pOuter->LookupSequence( szName ); + if ( iReloadSequence != -1 ) + return iReloadSequence; + + // Next, look for reload_<weapon name><_start|_loop|_end>. + Q_snprintf( szName, sizeof( szName ), "reload_%s%s", weaponSuffix, reloadSuffix ); + iReloadSequence = m_pOuter->LookupSequence( szName ); + if ( iReloadSequence != -1 ) + return iReloadSequence; + + // Ok, look for generic categories.. pistol, shotgun, rifle, etc. + if ( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_PISTOL ) + { + Q_snprintf( szName, sizeof( szName ), "reload_pistol" ); + iReloadSequence = m_pOuter->LookupSequence( szName ); + if ( iReloadSequence != -1 ) + return iReloadSequence; + } + + // Fall back to reload_m4. + iReloadSequence = CalcSequenceIndex( "reload_m4" ); + if ( iReloadSequence > 0 ) + return iReloadSequence; + + return -1; +} + + void CCSPlayerAnimState::UpdateLayerSequenceGeneric( CStudioHdr *pStudioHdr, int iLayer, bool &bEnabled, float &flCurCycle, int &iSequence, bool bWaitAtEnd ) +{ + if ( !bEnabled || iSequence < 0 ) + return; + + // Increment the fire sequence's cycle. + flCurCycle += m_pOuter->GetSequenceCycleRate( pStudioHdr, iSequence ) * gpGlobals->frametime; + if ( flCurCycle > 1 ) + { + if ( bWaitAtEnd ) + { + flCurCycle = 1; + } + else + { + // Not firing anymore. + bEnabled = false; + iSequence = 0; + return; + } + } + + // Now dump the state into its animation layer. + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iLayer ); + + pLayer->m_flCycle = flCurCycle; + pLayer->m_nSequence = iSequence; + + pLayer->m_flPlaybackRate = 1.0f; + pLayer->m_flWeight = 1.0f; + pLayer->m_nOrder = iLayer; +#ifndef CLIENT_DLL + pLayer->m_fFlags |= ANIM_LAYER_ACTIVE; +#endif +} + +bool CCSPlayerAnimState::IsOuterGrenadePrimed() +{ + CBaseCombatCharacter *pChar = m_pOuter->MyCombatCharacterPointer(); + if ( pChar ) + { + CBaseCSGrenade *pGren = dynamic_cast<CBaseCSGrenade*>( pChar->GetActiveWeapon() ); + return pGren && pGren->IsPinPulled(); + } + else + { + return NULL; + } +} + + +void CCSPlayerAnimState::ComputeGrenadeSequence( CStudioHdr *pStudioHdr ) +{ + VPROF( "CCSPlayerAnimState::ComputeGrenadeSequence" ); + + if ( m_bThrowingGrenade ) + { + UpdateLayerSequenceGeneric( pStudioHdr, GRENADESEQUENCE_LAYER, m_bThrowingGrenade, m_flGrenadeCycle, m_iGrenadeSequence, false ); + } + else + { + if ( m_pPlayer ) + { + CBaseCombatWeapon *pWeapon = m_pPlayer->GetActiveWeapon(); + CBaseCSGrenade *pGren = dynamic_cast<CBaseCSGrenade*>( pWeapon ); + if ( !pGren ) + { + // The player no longer has a grenade equipped. Bail. + m_iLastThrowGrenadeCounter = GetOuterGrenadeThrowCounter(); + return; + } + } + + // Priming the grenade isn't an event.. we just watch the player for it. + // Also play the prime animation first if he wants to throw the grenade. + bool bThrowPending = (m_iLastThrowGrenadeCounter != GetOuterGrenadeThrowCounter()); + if ( IsOuterGrenadePrimed() || bThrowPending ) + { + if ( !m_bPrimingGrenade ) + { + // If this guy just popped into our PVS, and he's got his grenade primed, then + // let's assume that it's all the way primed rather than playing the prime + // animation from the start. + if ( TimeSinceLastAnimationStateClear() < 0.4f ) + { + m_flGrenadeCycle = 1; + } + else + { + m_flGrenadeCycle = 0; + } + + m_iGrenadeSequence = CalcGrenadePrimeSequence(); + m_bPrimingGrenade = true; + } + + UpdateLayerSequenceGeneric( pStudioHdr, GRENADESEQUENCE_LAYER, m_bPrimingGrenade, m_flGrenadeCycle, m_iGrenadeSequence, true ); + + // If we're waiting to throw and we're done playing the prime animation... + if ( bThrowPending && m_flGrenadeCycle == 1 ) + { + m_iLastThrowGrenadeCounter = GetOuterGrenadeThrowCounter(); + + // Now play the throw animation. + m_iGrenadeSequence = CalcGrenadeThrowSequence(); + if ( m_iGrenadeSequence != -1 ) + { + // Configure to start playing + m_bThrowingGrenade = true; + m_bPrimingGrenade = false; + m_flGrenadeCycle = 0; + } + } + } + else + { + m_bPrimingGrenade = false; + } + } +} + + +int CCSPlayerAnimState::CalcGrenadePrimeSequence() +{ + return CalcSequenceIndex( "idle_shoot_gren1" ); +} + + +int CCSPlayerAnimState::CalcGrenadeThrowSequence() +{ + return CalcSequenceIndex( "idle_shoot_gren2" ); +} + + +int CCSPlayerAnimState::GetOuterGrenadeThrowCounter() +{ + if ( m_pPlayer ) + return m_pPlayer->m_iThrowGrenadeCounter; + else + return 0; +} + + +void CCSPlayerAnimState::ComputeReloadSequence( CStudioHdr *pStudioHdr ) +{ + VPROF( "CCSPlayerAnimState::ComputeReloadSequence" ); + bool hold = m_flReloadHoldEndTime > gpGlobals->curtime; + UpdateLayerSequenceGeneric( pStudioHdr, RELOADSEQUENCE_LAYER, m_bReloading, m_flReloadCycle, m_iReloadSequence, hold ); + if ( !m_bReloading ) + { + m_flReloadHoldEndTime = 0.0f; + } +} + + +int CCSPlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) +{ + VPROF( "CCSPlayerAnimState::CalcAimLayerSequence" ); + + const char *pSuffix = GetWeaponSuffix(); + if ( !pSuffix ) + return 0; + + if ( bForceIdle ) + { + switch ( GetCurrentMainSequenceActivity() ) + { + case ACT_CROUCHIDLE: + case ACT_RUN_CROUCH: + return CalcSequenceIndex( "%s%s", DEFAULT_CROUCH_IDLE_NAME, pSuffix ); + + default: + return CalcSequenceIndex( "%s%s", DEFAULT_IDLE_NAME, pSuffix ); + } + } + else + { + switch ( GetCurrentMainSequenceActivity() ) + { + case ACT_RUN: + return CalcSequenceIndex( "%s%s", DEFAULT_RUN_NAME, pSuffix ); + + case ACT_WALK: + case ACT_RUNTOIDLE: + case ACT_IDLETORUN: + return CalcSequenceIndex( "%s%s", DEFAULT_WALK_NAME, pSuffix ); + + case ACT_CROUCHIDLE: + return CalcSequenceIndex( "%s%s", DEFAULT_CROUCH_IDLE_NAME, pSuffix ); + + case ACT_RUN_CROUCH: + return CalcSequenceIndex( "%s%s", DEFAULT_CROUCH_WALK_NAME, pSuffix ); + + case ACT_IDLE: + default: + return CalcSequenceIndex( "%s%s", DEFAULT_IDLE_NAME, pSuffix ); + } + } +} + + +const char* CCSPlayerAnimState::GetWeaponSuffix() +{ + VPROF( "CCSPlayerAnimState::GetWeaponSuffix" ); + + // Figure out the weapon suffix. + CWeaponCSBase *pWeapon = m_pHelpers->CSAnim_GetActiveWeapon(); + if ( !pWeapon ) + return 0; + + const char *pSuffix = pWeapon->GetCSWpnData().m_szAnimExtension; + +#ifdef CS_SHIELD_ENABLED + if ( m_pOuter->HasShield() == true ) + { + if ( m_pOuter->IsShieldDrawn() == true ) + pSuffix = "shield"; + else + pSuffix = "shield_undeployed"; + } +#endif + + return pSuffix; +} + + +int CCSPlayerAnimState::CalcFireLayerSequence(PlayerAnimEvent_t event) +{ + // Figure out the weapon suffix. + CWeaponCSBase *pWeapon = m_pHelpers->CSAnim_GetActiveWeapon(); + if ( !pWeapon ) + return -1; + + const char *pSuffix = GetWeaponSuffix(); + if ( !pSuffix ) + return -1; + + char tempsuffix[32]; + if ( pWeapon->GetWeaponID() == WEAPON_ELITE ) + { + bool bPrimary = (event == PLAYERANIMEVENT_FIRE_GUN_PRIMARY); + Q_snprintf( tempsuffix, sizeof(tempsuffix), "%s_%c", pSuffix, bPrimary?'r':'l' ); + pSuffix = tempsuffix; + } + + // Grenades handle their fire events separately + if ( event == PLAYERANIMEVENT_THROW_GRENADE || + pWeapon->GetWeaponID() == WEAPON_HEGRENADE || + pWeapon->GetWeaponID() == WEAPON_SMOKEGRENADE || + pWeapon->GetWeaponID() == WEAPON_FLASHBANG ) + { + return -1; + } + + switch ( GetCurrentMainSequenceActivity() ) + { + case ACT_PLAYER_RUN_FIRE: + case ACT_RUN: + return CalcSequenceIndex( "%s%s", DEFAULT_FIRE_RUN_NAME, pSuffix ); + + case ACT_PLAYER_WALK_FIRE: + case ACT_WALK: + return CalcSequenceIndex( "%s%s", DEFAULT_FIRE_WALK_NAME, pSuffix ); + + case ACT_PLAYER_CROUCH_FIRE: + case ACT_CROUCHIDLE: + return CalcSequenceIndex( "%s%s", DEFAULT_FIRE_CROUCH_NAME, pSuffix ); + + case ACT_PLAYER_CROUCH_WALK_FIRE: + case ACT_RUN_CROUCH: + return CalcSequenceIndex( "%s%s", DEFAULT_FIRE_CROUCH_WALK_NAME, pSuffix ); + + default: + case ACT_PLAYER_IDLE_FIRE: + return CalcSequenceIndex( "%s%s", DEFAULT_FIRE_IDLE_NAME, pSuffix ); + } +} + + +bool CCSPlayerAnimState::CanThePlayerMove() +{ + return m_pHelpers->CSAnim_CanMove(); +} + + +float CCSPlayerAnimState::GetCurrentMaxGroundSpeed() +{ + Activity currentActivity = m_pOuter->GetSequenceActivity( m_pOuter->GetSequence() ); + if ( currentActivity == ACT_WALK || currentActivity == ACT_IDLE ) + return ANIM_TOPSPEED_WALK; + else if ( currentActivity == ACT_RUN ) + { + if ( m_pPlayer ) + { + CBaseCombatWeapon *activeWeapon = m_pPlayer->GetActiveWeapon(); + if ( activeWeapon ) + { + CWeaponCSBase *csWeapon = dynamic_cast< CWeaponCSBase * >( activeWeapon ); + if ( csWeapon ) + { + return csWeapon->GetMaxSpeed(); + } + } + } + return ANIM_TOPSPEED_RUN; + } + else if ( currentActivity == ACT_RUN_CROUCH ) + return ANIM_TOPSPEED_RUN_CROUCH; + else + return 0; +} + + +bool CCSPlayerAnimState::HandleJumping() +{ + if ( m_bJumping ) + { + if ( m_bFirstJumpFrame ) + { + +#if !defined(CLIENT_DLL) + //============================================================================= + // HPE_BEGIN: + // [dwenger] Needed for fun-fact implementation + //============================================================================= + + CCS_GameStats.IncrementStat(m_pPlayer, CSSTAT_TOTAL_JUMPS, 1); + + //============================================================================= + // HPE_END + //============================================================================= +#endif + + m_bFirstJumpFrame = false; + RestartMainSequence(); // Reset the animation. + } + + // Don't check if he's on the ground for a sec.. sometimes the client still has the + // on-ground flag set right when the message comes in. + if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f ) + { + if ( m_pOuter->GetFlags() & FL_ONGROUND ) + { + m_bJumping = false; + RestartMainSequence(); // Reset the animation. + } + } + } + + // Are we still jumping? If so, keep playing the jump animation. + return m_bJumping; +} + + +Activity CCSPlayerAnimState::CalcMainActivity() +{ + float flOuterSpeed = GetOuterXYSpeed(); + + if ( HandleJumping() ) + { + return ACT_HOP; + } + else + { + Activity idealActivity = ACT_IDLE; + + if ( m_pOuter->GetFlags() & FL_ANIMDUCKING ) + { + if ( flOuterSpeed > MOVING_MINIMUM_SPEED ) + idealActivity = ACT_RUN_CROUCH; + else + idealActivity = ACT_CROUCHIDLE; + } + else + { + if ( flOuterSpeed > MOVING_MINIMUM_SPEED ) + { + if ( flOuterSpeed > ARBITRARY_RUN_SPEED ) + idealActivity = ACT_RUN; + else + idealActivity = ACT_WALK; + } + else + { + idealActivity = ACT_IDLE; + } + } + + return idealActivity; + } +} + + +void CCSPlayerAnimState::DebugShowAnimState( int iStartLine ) +{ + engine->Con_NPrintf( iStartLine++, "fire : %s, cycle: %.2f\n", m_bFiring ? GetSequenceName( m_pOuter->GetModelPtr(), m_iFireSequence ) : "[not firing]", m_flFireCycle ); + engine->Con_NPrintf( iStartLine++, "reload: %s, cycle: %.2f\n", m_bReloading ? GetSequenceName( m_pOuter->GetModelPtr(), m_iReloadSequence ) : "[not reloading]", m_flReloadCycle ); + BaseClass::DebugShowAnimState( iStartLine ); +} + + +void CCSPlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) +{ + BaseClass::ComputeSequences( pStudioHdr ); + + VPROF( "CCSPlayerAnimState::ComputeSequences" ); + + ComputeFireSequence( pStudioHdr ); + ComputeReloadSequence( pStudioHdr ); + ComputeGrenadeSequence( pStudioHdr ); +} + + +void CCSPlayerAnimState::ClearAnimationLayers() +{ + if ( !m_pOuter ) + return; + + m_pOuter->SetNumAnimOverlays( NUM_LAYERS_WANTED ); + for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) + { + // Client obeys Order of CBaseAnimatingOverlay::MAX_OVERLAYS (15), but server trusts only the ANIM_LAYER_ACTIVE flag. + m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); +#ifndef CLIENT_DLL + m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; +#endif + } +} + + +void CCSPlayerAnimState::ComputeFireSequence( CStudioHdr *pStudioHdr ) +{ + VPROF( "CCSPlayerAnimState::ComputeFireSequence" ); + + if ( m_delayedFire != PLAYERANIMEVENT_COUNT ) + { + DoAnimationEvent( m_delayedFire, 0 ); + m_delayedFire = PLAYERANIMEVENT_COUNT; + } + + UpdateLayerSequenceGeneric( pStudioHdr, FIRESEQUENCE_LAYER, m_bFiring, m_flFireCycle, m_iFireSequence, false ); +} diff --git a/game/shared/cstrike/cs_playeranimstate.h b/game/shared/cstrike/cs_playeranimstate.h new file mode 100644 index 0000000..aad335c --- /dev/null +++ b/game/shared/cstrike/cs_playeranimstate.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TF_PLAYERANIMSTATE_H +#define TF_PLAYERANIMSTATE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "convar.h" +#include "iplayeranimstate.h" +#include "base_playeranimstate.h" + +#ifdef CLIENT_DLL + class C_BaseAnimatingOverlay; + class C_WeaponCSBase; + // Avoid redef warnings + #undef CBaseAnimatingOverlay + #define CBaseAnimatingOverlay C_BaseAnimatingOverlay + #define CWeaponCSBase C_WeaponCSBase + #define CCSPlayer C_CSPlayer +#else + class CBaseAnimatingOverlay; + class CWeaponCSBase; + class CCSPlayer; +#endif + + +// When moving this fast, he plays run anim. +#define ARBITRARY_RUN_SPEED 175.0f + + +enum PlayerAnimEvent_t +{ + PLAYERANIMEVENT_FIRE_GUN_PRIMARY=0, + PLAYERANIMEVENT_FIRE_GUN_SECONDARY, + PLAYERANIMEVENT_THROW_GRENADE, + PLAYERANIMEVENT_JUMP, + PLAYERANIMEVENT_RELOAD, + PLAYERANIMEVENT_RELOAD_START, ///< w_model partial reload for shotguns + PLAYERANIMEVENT_RELOAD_LOOP, ///< w_model partial reload for shotguns + PLAYERANIMEVENT_RELOAD_END, ///< w_model partial reload for shotguns + PLAYERANIMEVENT_CLEAR_FIRING, ///< clear animations on the firing layer + + PLAYERANIMEVENT_COUNT +}; + + +class ICSPlayerAnimState : virtual public IPlayerAnimState +{ +public: + // This is called by both the client and the server in the same way to trigger events for + // players firing, jumping, throwing grenades, etc. + virtual void DoAnimationEvent( PlayerAnimEvent_t event, int nData = 0 ) = 0; + + // Returns true if we're playing the grenade prime or throw animation. + virtual bool IsThrowingGrenade() = 0; +}; + + +// This abstracts the differences between CS players and hostages. +class ICSPlayerAnimStateHelpers +{ +public: + virtual CWeaponCSBase* CSAnim_GetActiveWeapon() = 0; + virtual bool CSAnim_CanMove() = 0; +}; + + +ICSPlayerAnimState* CreatePlayerAnimState( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ); +ICSPlayerAnimState* CreateHostageAnimState( CBaseAnimatingOverlay *pEntity, ICSPlayerAnimStateHelpers *pHelpers, LegAnimType_t legAnimType, bool bUseAimSequences ); + +// If this is set, then the game code needs to make sure to send player animation events +// to the local player if he's the one being watched. +extern ConVar cl_showanimstate; + + +#endif // TF_PLAYERANIMSTATE_H diff --git a/game/shared/cstrike/cs_shareddefs.cpp b/game/shared/cstrike/cs_shareddefs.cpp new file mode 100644 index 0000000..89b8ae3 --- /dev/null +++ b/game/shared/cstrike/cs_shareddefs.cpp @@ -0,0 +1,67 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "cs_shareddefs.h" + +const float CS_PLAYER_SPEED_RUN = 260.0f; +const float CS_PLAYER_SPEED_VIP = 227.0f; +const float CS_PLAYER_SPEED_WALK = 100.0f; +const float CS_PLAYER_SPEED_SHIELD = 160.0f; +const float CS_PLAYER_SPEED_STOPPED = 1.0f; +const float CS_PLAYER_SPEED_OBSERVER = 900.0f; + +const float CS_PLAYER_SPEED_DUCK_MODIFIER = 0.34f; +const float CS_PLAYER_SPEED_WALK_MODIFIER = 0.52f; +const float CS_PLAYER_SPEED_CLIMB_MODIFIER = 0.34f; + + +CCSClassInfo g_ClassInfos[] = +{ + { "None" }, + + { "Phoenix Connection" }, + { "L337 KREW" }, + { "Arctic Avengers" }, + { "Guerilla Warfare" }, + + { "Seal Team 6" }, + { "GSG-9" }, + { "SAS" }, + { "GIGN" } +}; + +const CCSClassInfo* GetCSClassInfo( int i ) +{ + Assert( i >= 0 && i < ARRAYSIZE( g_ClassInfos ) ); + return &g_ClassInfos[i]; +} + +const char *pszWinPanelCategoryHeaders[] = +{ + "", + "#winpanel_topdamage", + "#winpanel_topheadshots", + "#winpanel_kills" +}; + +// Construct some arrays of player model strings, so we can statically initialize CUtlVectors for general usage +const char *CTPlayerModelStrings[] = +{ + "models/player/ct_urban.mdl", + "models/player/ct_gsg9.mdl", + "models/player/ct_sas.mdl", + "models/player/ct_gign.mdl", +}; +const char *TerroristPlayerModelStrings[] = +{ + "models/player/t_phoenix.mdl", + "models/player/t_leet.mdl", + "models/player/t_arctic.mdl", + "models/player/t_guerilla.mdl", +}; +CUtlVectorInitialized< const char * > CTPlayerModels( CTPlayerModelStrings, ARRAYSIZE( CTPlayerModelStrings ) ); +CUtlVectorInitialized< const char * > TerroristPlayerModels( TerroristPlayerModelStrings, ARRAYSIZE( TerroristPlayerModelStrings ) ); diff --git a/game/shared/cstrike/cs_shareddefs.h b/game/shared/cstrike/cs_shareddefs.h new file mode 100644 index 0000000..3e41431 --- /dev/null +++ b/game/shared/cstrike/cs_shareddefs.h @@ -0,0 +1,261 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Shared CS definitions. +// +//=============================================================================// + +#ifndef CS_SHAREDDEFS_H +#define CS_SHAREDDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +/*======================*/ +// Menu stuff // +/*======================*/ + +#include <game/client/iviewport.h> + +//============================================================================= +// HPE_BEGIN: +// Including Achievement ID Definitions +//============================================================================= + +#include "cs_achievementdefs.h" + +//============================================================================= +// HPE_END +//============================================================================= + +// CS-specific viewport panels +#define PANEL_CLASS_CT "class_ct" +#define PANEL_CLASS_TER "class_ter" + +// Buy sub menus +#define MENU_PISTOL "menu_pistol" +#define MENU_SHOTGUN "menu_shotgun" +#define MENU_RIFLE "menu_rifle" +#define MENU_SMG "menu_smg" +#define MENU_MACHINEGUN "menu_mg" +#define MENU_EQUIPMENT "menu_equip" + + +#define MAX_HOSTAGES 12 +#define MAX_HOSTAGE_RESCUES 4 + + + + +#define CSTRIKE_DEFAULT_AVATAR "avatar_default_64" +#define CSTRIKE_DEFAULT_T_AVATAR "avatar_default-t_64" +#define CSTRIKE_DEFAULT_CT_AVATAR "avatar_default_64" + +extern const float CS_PLAYER_SPEED_RUN; +extern const float CS_PLAYER_SPEED_VIP; +extern const float CS_PLAYER_SPEED_WALK; +extern const float CS_PLAYER_SPEED_SHIELD; +extern const float CS_PLAYER_SPEED_STOPPED; +extern const float CS_PLAYER_SPEED_OBSERVER; + +extern const float CS_PLAYER_SPEED_DUCK_MODIFIER; +extern const float CS_PLAYER_SPEED_WALK_MODIFIER; +extern const float CS_PLAYER_SPEED_CLIMB_MODIFIER; + + +template< class T > +class CUtlVectorInitialized : public CUtlVector< T > +{ +public: + CUtlVectorInitialized( T* pMemory, int numElements ) : CUtlVector< T >( pMemory, numElements ) + { + this->SetSize( numElements ); + } +}; + +extern CUtlVectorInitialized< const char * > CTPlayerModels; +extern CUtlVectorInitialized< const char * > TerroristPlayerModels; + + +// These go in CCSPlayer::m_iAddonBits and get sent to the client so it can create +// grenade models hanging off players. +#define ADDON_FLASHBANG_1 0x001 +#define ADDON_FLASHBANG_2 0x002 +#define ADDON_HE_GRENADE 0x004 +#define ADDON_SMOKE_GRENADE 0x008 +#define ADDON_C4 0x010 +#define ADDON_DEFUSEKIT 0x020 +#define ADDON_PRIMARY 0x040 +#define ADDON_PISTOL 0x080 +#define ADDON_PISTOL2 0x100 +#define NUM_ADDON_BITS 9 + + +// Indices of each weapon slot. +#define WEAPON_SLOT_RIFLE 0 // (primary slot) +#define WEAPON_SLOT_PISTOL 1 // (secondary slot) +#define WEAPON_SLOT_KNIFE 2 +#define WEAPON_SLOT_GRENADES 3 +#define WEAPON_SLOT_C4 4 + +#define WEAPON_SLOT_FIRST 0 +#define WEAPON_SLOT_LAST 4 + + +// CS Team IDs. +#define TEAM_TERRORIST 2 +#define TEAM_CT 3 +#define TEAM_MAXCOUNT 4 // update this if we ever add teams (unlikely) + +#define MAX_CLAN_TAG_LENGTH 16 // max for new tags is actually 12, this allows some backward compat. + +//============================================================================= +// HPE_BEGIN: +// [menglish] CS specific death animation time now that freeze cam is implemented +// in order to linger on the players body less +// [tj] The number of times you must kill a given player to be dominating them +// [menglish] Flags to use upon player death +//============================================================================= + +// [menglish] CS specific death animation time now that freeze cam is implemented +// in order to linger on the players body less +#define CS_DEATH_ANIMATION_TIME 0.8 + +// [tj] The number of times you must kill a given player to be dominating them +// Should always be more than 1 +#define CS_KILLS_FOR_DOMINATION 4 + +#define CS_DEATH_DOMINATION 0x0001 // killer is dominating victim +#define CS_DEATH_REVENGE 0x0002 // killer got revenge on victim +//============================================================================= +// HPE_END +//============================================================================= + + +//-------------- +// CSPort Specific damage flags +//-------------- +#define DMG_HEADSHOT (DMG_LASTGENERICFLAG<<1) + + +// The various states the player can be in during the join game process. +enum CSPlayerState +{ + // Happily running around in the game. + // You can't move though if CSGameRules()->IsFreezePeriod() returns true. + // This state can jump to a bunch of other states like STATE_PICKINGCLASS or STATE_DEATH_ANIM. + STATE_ACTIVE=0, + + // This is the state you're in when you first enter the server. + // It's switching between intro cameras every few seconds, and there's a level info + // screen up. + STATE_WELCOME, // Show the level intro screen. + + // During these states, you can either be a new player waiting to join, or + // you can be a live player in the game who wants to change teams. + // Either way, you can't move while choosing team or class (or while any menu is up). + STATE_PICKINGTEAM, // Choosing team. + STATE_PICKINGCLASS, // Choosing class. + + STATE_DEATH_ANIM, // Playing death anim, waiting for that to finish. + STATE_DEATH_WAIT_FOR_KEY, // Done playing death anim. Waiting for keypress to go into observer mode. + STATE_OBSERVER_MODE, // Noclipping around, watching players, etc. + NUM_PLAYER_STATES +}; + + +enum e_RoundEndReason +{ + Invalid_Round_End_Reason = -1, + Target_Bombed, + VIP_Escaped, + VIP_Assassinated, + Terrorists_Escaped, + CTs_PreventEscape, + Escaping_Terrorists_Neutralized, + Bomb_Defused, + CTs_Win, + Terrorists_Win, + Round_Draw, + All_Hostages_Rescued, + Target_Saved, + Hostages_Not_Rescued, + Terrorists_Not_Escaped, + VIP_Not_Escaped, + Game_Commencing, + RoundEndReason_Count +}; + +#define PUSHAWAY_THINK_INTERVAL (1.0f / 20.0f) + +enum +{ + CS_CLASS_NONE=0, + + // Terrorist classes (keep in sync with FIRST_T_CLASS/LAST_T_CLASS). + CS_CLASS_PHOENIX_CONNNECTION, + CS_CLASS_L337_KREW, + CS_CLASS_ARCTIC_AVENGERS, + CS_CLASS_GUERILLA_WARFARE, + + // CT classes (keep in sync with FIRST_CT_CLASS/LAST_CT_CLASS). + CS_CLASS_SEAL_TEAM_6, + CS_CLASS_GSG_9, + CS_CLASS_SAS, + CS_CLASS_GIGN, + + CS_NUM_CLASSES +}; + +//============================================================================= +// HPE_BEGIN: +// [menglish] List of equipment dropped by players with freeze panel callouts +//============================================================================= +enum +{ + DROPPED_C4, + DROPPED_DEFUSE, + DROPPED_WEAPON, + DROPPED_GRENADE, // This could be an HE greande, flashbang, or smoke + DROPPED_COUNT +}; +//============================================================================= +// HPE_END +//============================================================================= + + +//============================================================================= +// +// MVP reasons +// +enum CSMvpReason_t +{ + CSMVP_UNDEFINED = 0, + CSMVP_ELIMINATION, + CSMVP_BOMBPLANT, + CSMVP_BOMBDEFUSE, + CSMVP_HOSTAGERESCUE, +}; + + +// Keep these in sync with CSClasses. +#define FIRST_T_CLASS CS_CLASS_PHOENIX_CONNNECTION +#define LAST_T_CLASS CS_CLASS_GUERILLA_WARFARE + +#define FIRST_CT_CLASS CS_CLASS_SEAL_TEAM_6 +#define LAST_CT_CLASS CS_CLASS_GIGN + +#define CS_MUZZLEFLASH_NONE -1 +#define CS_MUZZLEFLASH_NORM 0 +#define CS_MUZZLEFLASH_X 1 + +class CCSClassInfo +{ +public: + const char *m_pClassName; +}; + +const CCSClassInfo* GetCSClassInfo( int i ); + +extern const char *pszWinPanelCategoryHeaders[]; + +#endif // CS_SHAREDDEFS_H diff --git a/game/shared/cstrike/cs_urlretrieveprices.cpp b/game/shared/cstrike/cs_urlretrieveprices.cpp new file mode 100644 index 0000000..c094263 --- /dev/null +++ b/game/shared/cstrike/cs_urlretrieveprices.cpp @@ -0,0 +1,237 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +//#include "stdafx.h" +#ifdef _WIN32 +#include "winlite.h" +#include "winsock.h" +#elif POSIX +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#define SOCKET int +#define LPSOCKADDR struct sockaddr * +#define SOCKADDR_IN struct sockaddr_in +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#define closesocket close +#endif +#include "tier1/strtools.h" +#include "KeyValues.h" +#include "utlbuffer.h" +#include "tier1/checksum_crc.h" +#include "tier1/convar.h" +#include "cbase.h" +#include "cs_gamestats.h" +#include "cs_gamerules.h" +#include "cs_urlretrieveprices.h" + +#if _DEBUG +#define WEEKLY_PRICE_URL "http://gamestats/weeklyprices.dat" +#else +#define WEEKLY_PRICE_URL "http://www.steampowered.com/stats/csmarket/weeklyprices.dat" +#endif + + +//----------------------------------------------------------------------------- +// Purpose: request a URL from connection +//----------------------------------------------------------------------------- +bool SendHTTPRequest( const char *pchRequestURL, SOCKET socketHTML ) +{ + char szHeader[ MED_BUFFER_SIZE ]; + char szHostName[ SMALL_BUFFER_SIZE ]; + ::gethostname( szHostName, sizeof(szHostName) ); + + Q_snprintf( szHeader, sizeof(szHeader), "GET %s HTTP/1.0\r\n" \ + "Accept: */*\r\n" \ + "Accept-Language: en-us\r\n" \ + "User-Agent: Steam/3.0\r\n" \ + "Host: %s\r\n" \ + "\r\n", + pchRequestURL, szHostName ); + + return ::send( socketHTML, szHeader, Q_strlen(szHeader) + 1, 0 ) != SOCKET_ERROR ; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given a previous HTTP request parse the response into a key values buffer +//----------------------------------------------------------------------------- +bool ParseHTTPResponse( SOCKET socketHTML, uint32 *unPageHash = NULL ) +{ + char szHeaderBuf[ MED_BUFFER_SIZE ]; + char szBodyBuf[ MED_BUFFER_SIZE ]; + + int dwRet = 0; + bool bFinishedHeaderRead = false; + int iRecvPosition = 0; + int cCharsInLine = 0; + + // scan for the end of the header + while ( !bFinishedHeaderRead && iRecvPosition < sizeof(szHeaderBuf) ) + { + dwRet = ::recv( socketHTML, &szHeaderBuf[ iRecvPosition ] , 1, 0); + if ( dwRet < 0 ) + { + bFinishedHeaderRead = true; + } + + switch( szHeaderBuf[ iRecvPosition ] ) + { + case '\r': + break; + case '\n': + if ( cCharsInLine == 0 ) + bFinishedHeaderRead = true; + + cCharsInLine = 0; + break; + default: + cCharsInLine++; + break; + } + + iRecvPosition++; + } + + CUtlBuffer buf; + buf.SetBufferType( false, false ); + while( 1 ) + { + dwRet = ::recv( socketHTML, szBodyBuf, sizeof(szBodyBuf)-1, 0); + if ( dwRet <= 0 ) + break; + + buf.Put( szBodyBuf, sizeof(szBodyBuf)-1 ); + } + + weeklyprice_t weeklyprice; + Q_memset( &weeklyprice, 0, sizeof( weeklyprice_t) ); + + buf.Get( &weeklyprice, sizeof( weeklyprice_t ) ); + + if ( weeklyprice.iVersion != PRICE_BLOB_VERSION ) + { + Msg( "Incorrect price blob version! Update your server!\n" ); + return false; + } + + CSGameRules()->AddPricesToTable( weeklyprice ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given http url crack it into an address and the request part +//----------------------------------------------------------------------------- +bool ProcessURL( const char *pchURL, void *pSockAddrIn, char *pchRequest, int cchRequest ) +{ + char rgchHost[ MAX_DNS_NAME ]; + char rgchRequest[ MED_BUFFER_SIZE ]; + uint16 iPort; + + if ( Q_strnicmp( pchURL, "http://", 7 ) != 0 ) + { + Assert( !"http protocol only supported" ); + return false; + } + + const char *pchColon = strchr( pchURL + 7, ':' ); + if ( pchColon ) + { + Q_strncpy( rgchHost, pchURL + 7, pchColon - ( pchURL + 7 ) + 1 ); + const char *pchForwardSlash = strchr( pchColon + 1, '/' ); + if ( !pchForwardSlash ) + return false; + Q_strncpy( rgchRequest, pchColon + 1, pchForwardSlash - ( pchColon + 1 ) + 1 ); + iPort = atoi( rgchRequest ); + Q_strncpy( rgchRequest, pchForwardSlash, ( pchURL + Q_strlen(pchURL) ) - pchForwardSlash + 1 ); + } + else + { + const char *pchForwardSlash = strchr( pchURL + 7, '/' ); + if ( !pchForwardSlash ) + return false; + + Q_strncpy( rgchHost, pchURL + 7, pchForwardSlash - ( pchURL + 7 ) + 1 ); + iPort = 80; + Q_strncpy( rgchRequest, pchForwardSlash, ( pchURL + Q_strlen(pchURL) ) - pchForwardSlash + 1 ); + } + + struct hostent *hp = NULL; + if ( inet_addr( rgchHost ) == INADDR_NONE ) + { + hp = gethostbyname( rgchHost ); + } + else + { + uint32 addr = inet_addr( rgchHost ); + hp = gethostbyaddr( ( char* )&addr, sizeof( addr ), AF_INET ); + } + + if( hp == NULL ) + { + return false; + } + + sockaddr_in &sockAddrIn = *((sockaddr_in *)pSockAddrIn); + sockAddrIn.sin_addr.s_addr = *( ( unsigned long* )hp->h_addr ); + sockAddrIn.sin_family = AF_INET; + sockAddrIn.sin_port = htons( iPort ); + + Q_strncpy( pchRequest, rgchRequest, cchRequest ); + return true; +} + +//networkstringtable + +bool BlackMarket_DownloadPrices( void ) +{ + char szRequest[ MED_BUFFER_SIZE ]; + sockaddr_in server; + bool bConnected = false; + + if ( ProcessURL( WEEKLY_PRICE_URL, &server, szRequest, sizeof(szRequest) ) ) + { + SOCKET socketHTML = ::socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if ( socketHTML != INVALID_SOCKET) + { + int iRet = ::connect( socketHTML, (LPSOCKADDR)&server, sizeof(SOCKADDR_IN) ); + + if ( !iRet ) + { + bConnected = true; + if ( SendHTTPRequest( szRequest, socketHTML ) ) + { + uint32 unHash = 0; + bool bRet = ParseHTTPResponse( socketHTML, &unHash ); + + closesocket( socketHTML ); + + return bRet; + } + else + { + return false; + } + } + else + { + closesocket( socketHTML ); + return false; + } + } + } + else + { + return false; + } + + return true; +} diff --git a/game/shared/cstrike/cs_urlretrieveprices.h b/game/shared/cstrike/cs_urlretrieveprices.h new file mode 100644 index 0000000..e81ce45 --- /dev/null +++ b/game/shared/cstrike/cs_urlretrieveprices.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef URLRETRIEVETHREAD_H +#define URLRETRIEVETHREAD_H + +#include "KeyValues.h" +#include "cs_weapon_parse.h" + +bool BlackMarket_DownloadPrices( void ); + +#define MED_BUFFER_SIZE 1024 +#define SMALL_BUFFER_SIZE 255 +#define MAX_DNS_NAME 255 + +#define PRICE_BLOB_VERSION 1 +#define PRICE_BLOB_NAME "weeklyprices.dat" + +struct weeklyprice_t +{ + short iVersion; + short iPreviousPrice[WEAPON_MAX]; + short iCurrentPrice[WEAPON_MAX]; +}; + +#endif // URLRETRIEVETHREAD_H diff --git a/game/shared/cstrike/cs_usermessages.cpp b/game/shared/cstrike/cs_usermessages.cpp new file mode 100644 index 0000000..0c03b6d --- /dev/null +++ b/game/shared/cstrike/cs_usermessages.cpp @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "usermessages.h" +#include "shake.h" +#include "voice_gamemgr.h" + +// NVNT include to register in haptic user messages +#include "haptics/haptic_msgs.h" + +void RegisterUserMessages() +{ + usermessages->Register( "Geiger", 1 ); // geiger info data + usermessages->Register( "Train", 1 ); // train control data + usermessages->Register( "HudText", -1 ); + usermessages->Register( "SayText", -1 ); + usermessages->Register( "SayText2", -1 ); + usermessages->Register( "TextMsg", -1 ); + usermessages->Register( "HudMsg", -1 ); + usermessages->Register( "ResetHUD", 1 ); // called every respawn + usermessages->Register( "GameTitle", 0 ); // show game title + usermessages->Register( "ItemPickup", -1 ); // for item history on screen + usermessages->Register( "ShowMenu", -1 ); // show hud menu + usermessages->Register( "Shake", 13 ); // shake view + usermessages->Register( "Fade", 10 ); // fade HUD in/out + usermessages->Register( "VGUIMenu", -1 ); // Show VGUI menu + usermessages->Register( "Rumble", 3 ); // Send a rumble to a controller + usermessages->Register( "CloseCaption", -1 ); // Show a caption (by string id number)(duration in 10th of a second) + + usermessages->Register( "SendAudio", -1 ); // play radio command + usermessages->Register( "RawAudio", -1 ); // play a .wav as a radio command + + usermessages->Register( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 + 1 ); + usermessages->Register( "RequestState", 0 ); + + usermessages->Register( "BarTime", -1 ); // For the C4 progress bar. + usermessages->Register( "Damage", -1 ); // for HUD damage indicators + usermessages->Register( "RadioText", -1 ); // for radio text display + usermessages->Register( "HintText", -1 ); // Displays hint text display + usermessages->Register( "KeyHintText", -1 ); // Displays hint text display + + usermessages->Register( "ReloadEffect", 2 ); // a player reloading.. + usermessages->Register( "PlayerAnimEvent", -1 ); // jumping, firing, reload, etc. + + usermessages->Register( "AmmoDenied", 2 ); + usermessages->Register( "UpdateRadar", -1 ); + usermessages->Register( "KillCam", -1 ); + usermessages->Register( "MarkAchievement", -1 ); + + // Voting + usermessages->Register( "CallVoteFailed", -1 ); + usermessages->Register( "VoteStart", -1 ); + usermessages->Register( "VotePass", -1 ); + usermessages->Register( "VoteFailed", 2 ); + usermessages->Register( "VoteSetup", -1 ); // Initiates client-side voting UI + + // NVNT register haptic user messages + RegisterHapticMessages(); + + //============================================================================= + // HPE_BEGIN: + // [menglish] Registering PlayerStatsUpdate for Stats implementation + // [dwenger] AchievementEvent: Necessary for server-side achievement awarding + // [tj] Added support for absolute current match statistics updates + //============================================================================= + + usermessages->Register( "PlayerStatsUpdate_DEPRECATED", -1 ); // Protocol changed, this message replaced below + usermessages->Register( "AchievementEvent", -1 ); + usermessages->Register( "MatchEndConditions", -1 ); //The end conditions for the match. long frag limit, long max rounds, long rounds needed won, and long time + usermessages->Register( "MatchStatsUpdate", -1 ); + usermessages->Register( "PlayerStatsUpdate", -1 ); //Processes stats update + + //============================================================================= + // HPE_END + //============================================================================= +} diff --git a/game/shared/cstrike/cs_weapon_parse.cpp b/game/shared/cstrike/cs_weapon_parse.cpp new file mode 100644 index 0000000..d23ea72 --- /dev/null +++ b/game/shared/cstrike/cs_weapon_parse.cpp @@ -0,0 +1,455 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include <KeyValues.h> +#include "cs_weapon_parse.h" +#include "cs_shareddefs.h" +#include "weapon_csbase.h" +#include "icvar.h" +#include "cs_gamerules.h" +#include "cs_blackmarket.h" + + +//-------------------------------------------------------------------------------------------------------- +struct WeaponTypeInfo +{ + CSWeaponType type; + const char * name; +}; + + +//-------------------------------------------------------------------------------------------------------- +WeaponTypeInfo s_weaponTypeInfo[] = +{ + { WEAPONTYPE_KNIFE, "Knife" }, + { WEAPONTYPE_PISTOL, "Pistol" }, + { WEAPONTYPE_SUBMACHINEGUN, "Submachine Gun" }, // First match is printable + { WEAPONTYPE_SUBMACHINEGUN, "submachinegun" }, + { WEAPONTYPE_SUBMACHINEGUN, "smg" }, + { WEAPONTYPE_RIFLE, "Rifle" }, + { WEAPONTYPE_SHOTGUN, "Shotgun" }, + { WEAPONTYPE_SNIPER_RIFLE, "Sniper Rifle" }, // First match is printable + { WEAPONTYPE_SNIPER_RIFLE, "SniperRifle" }, + { WEAPONTYPE_MACHINEGUN, "Machine Gun" }, // First match is printable + { WEAPONTYPE_MACHINEGUN, "machinegun" }, + { WEAPONTYPE_MACHINEGUN, "mg" }, + { WEAPONTYPE_C4, "C4" }, + { WEAPONTYPE_GRENADE, "Grenade" }, +}; + + +struct WeaponNameInfo +{ + CSWeaponID id; + const char *name; +}; + +WeaponNameInfo s_weaponNameInfo[] = +{ + { WEAPON_P228, "weapon_p228" }, + { WEAPON_GLOCK, "weapon_glock" }, + { WEAPON_SCOUT, "weapon_scout" }, + { WEAPON_HEGRENADE, "weapon_hegrenade" }, + { WEAPON_XM1014, "weapon_xm1014" }, + { WEAPON_C4, "weapon_c4" }, + { WEAPON_MAC10, "weapon_mac10" }, + { WEAPON_AUG, "weapon_aug" }, + { WEAPON_SMOKEGRENADE, "weapon_smokegrenade" }, + { WEAPON_ELITE, "weapon_elite" }, + { WEAPON_FIVESEVEN, "weapon_fiveseven" }, + { WEAPON_UMP45, "weapon_ump45" }, + { WEAPON_SG550, "weapon_sg550" }, + + { WEAPON_GALIL, "weapon_galil" }, + { WEAPON_FAMAS, "weapon_famas" }, + { WEAPON_USP, "weapon_usp" }, + { WEAPON_AWP, "weapon_awp" }, + { WEAPON_MP5NAVY, "weapon_mp5navy" }, + { WEAPON_M249, "weapon_m249" }, + { WEAPON_M3, "weapon_m3" }, + { WEAPON_M4A1, "weapon_m4a1" }, + { WEAPON_TMP, "weapon_tmp" }, + { WEAPON_G3SG1, "weapon_g3sg1" }, + { WEAPON_FLASHBANG, "weapon_flashbang" }, + { WEAPON_DEAGLE, "weapon_deagle" }, + { WEAPON_SG552, "weapon_sg552" }, + { WEAPON_AK47, "weapon_ak47" }, + { WEAPON_KNIFE, "weapon_knife" }, + { WEAPON_P90, "weapon_p90" }, + + // not sure any of these are needed + { WEAPON_SHIELDGUN, "weapon_shieldgun" }, + { WEAPON_KEVLAR, "weapon_kevlar" }, + { WEAPON_ASSAULTSUIT, "weapon_assaultsuit" }, + { WEAPON_NVG, "weapon_nvg" }, + + { WEAPON_NONE, "weapon_none" }, +}; + + + +//-------------------------------------------------------------------------------------------------------------- + + +CCSWeaponInfo g_EquipmentInfo[MAX_EQUIPMENT]; + +void PrepareEquipmentInfo( void ) +{ + memset( g_EquipmentInfo, 0, ARRAYSIZE( g_EquipmentInfo ) ); + + g_EquipmentInfo[2].SetWeaponPrice( CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_KEVLAR ) ); + g_EquipmentInfo[2].SetDefaultPrice( KEVLAR_PRICE ); + g_EquipmentInfo[2].SetPreviousPrice( CSGameRules()->GetBlackMarketPreviousPriceForWeapon( WEAPON_KEVLAR ) ); + g_EquipmentInfo[2].m_iTeam = TEAM_UNASSIGNED; + Q_strcpy( g_EquipmentInfo[2].szClassName, "weapon_vest" ); + +#ifdef CLIENT_DLL + g_EquipmentInfo[2].iconActive = new CHudTexture; + g_EquipmentInfo[2].iconActive->cCharacterInFont = 't'; +#endif + + g_EquipmentInfo[1].SetWeaponPrice( CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_ASSAULTSUIT ) ); + g_EquipmentInfo[1].SetDefaultPrice( ASSAULTSUIT_PRICE ); + g_EquipmentInfo[1].SetPreviousPrice( CSGameRules()->GetBlackMarketPreviousPriceForWeapon( WEAPON_ASSAULTSUIT ) ); + g_EquipmentInfo[1].m_iTeam = TEAM_UNASSIGNED; + Q_strcpy( g_EquipmentInfo[1].szClassName, "weapon_vesthelm" ); + +#ifdef CLIENT_DLL + g_EquipmentInfo[1].iconActive = new CHudTexture; + g_EquipmentInfo[1].iconActive->cCharacterInFont = 'u'; +#endif + + g_EquipmentInfo[0].SetWeaponPrice( CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_NVG ) ); + g_EquipmentInfo[0].SetPreviousPrice( CSGameRules()->GetBlackMarketPreviousPriceForWeapon( WEAPON_NVG ) ); + g_EquipmentInfo[0].SetDefaultPrice( NVG_PRICE ); + g_EquipmentInfo[0].m_iTeam = TEAM_UNASSIGNED; + Q_strcpy( g_EquipmentInfo[0].szClassName, "weapon_nvgs" ); + +#ifdef CLIENT_DLL + g_EquipmentInfo[0].iconActive = new CHudTexture; + g_EquipmentInfo[0].iconActive->cCharacterInFont = 's'; +#endif + +} + +//-------------------------------------------------------------------------------------------------------------- +CCSWeaponInfo * GetWeaponInfo( CSWeaponID weaponID ) +{ + if ( weaponID == WEAPON_NONE ) + return NULL; + + if ( weaponID >= WEAPON_KEVLAR ) + { + int iIndex = (WEAPON_MAX - weaponID) - 1; + + return &g_EquipmentInfo[iIndex]; + + } + + const char *weaponName = WeaponIdAsString(weaponID); + WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( weaponName ); + if ( hWpnInfo == GetInvalidWeaponInfoHandle() ) + { + return NULL; + } + + CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); + + return pWeaponInfo; +} + +//-------------------------------------------------------------------------------------------------------- +const char* WeaponClassAsString( CSWeaponType weaponType ) +{ + for ( int i = 0; i < ARRAYSIZE(s_weaponTypeInfo); ++i ) + { + if ( s_weaponTypeInfo[i].type == weaponType ) + { + return s_weaponTypeInfo[i].name; + } + } + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------- +CSWeaponType WeaponClassFromString( const char* weaponType ) +{ + for ( int i = 0; i < ARRAYSIZE(s_weaponTypeInfo); ++i ) + { + if ( !Q_stricmp( s_weaponTypeInfo[i].name, weaponType ) ) + { + return s_weaponTypeInfo[i].type; + } + } + + return WEAPONTYPE_UNKNOWN; +} + + +//-------------------------------------------------------------------------------------------------------- +CSWeaponType WeaponClassFromWeaponID( CSWeaponID weaponID ) +{ + const char *weaponStr = WeaponIDToAlias( weaponID ); + const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr ); + + char wpnName[128]; + Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias ); + WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName ); + if ( hWpnInfo != GetInvalidWeaponInfoHandle() ) + { + CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); + if ( pWeaponInfo ) + { + return pWeaponInfo->m_WeaponType; + } + } + + return WEAPONTYPE_UNKNOWN; +} + + +//-------------------------------------------------------------------------------------------------------- +const char * WeaponIdAsString( CSWeaponID weaponID ) +{ + for ( int i = 0; i < ARRAYSIZE(s_weaponNameInfo); ++i ) + { + if (s_weaponNameInfo[i].id == weaponID ) + return s_weaponNameInfo[i].name; + } + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------- +CSWeaponID WeaponIdFromString( const char *szWeaponName ) +{ + for ( int i = 0; i < ARRAYSIZE(s_weaponNameInfo); ++i ) + { + if ( Q_stricmp(s_weaponNameInfo[i].name, szWeaponName) == 0 ) + return s_weaponNameInfo[i].id; + } + + return WEAPON_NONE; +} + + +//-------------------------------------------------------------------------------------------------------- +void ParseVector( KeyValues *keyValues, const char *keyName, Vector& vec ) +{ + vec.x = vec.y = vec.z = 0.0f; + + if ( !keyValues || !keyName ) + return; + + const char *vecString = keyValues->GetString( keyName, "0 0 0" ); + if ( vecString && *vecString ) + { + float x = 0.0f, y = 0.0f, z = 0.0f; + if ( 3 == sscanf( vecString, "%f %f %f", &x, &y, &z ) ) + { + vec.x = x; + vec.y = y; + vec.z = z; + } + } +} + + +FileWeaponInfo_t* CreateWeaponInfo() +{ + return new CCSWeaponInfo; +} + + +CCSWeaponInfo::CCSWeaponInfo() +{ + m_flMaxSpeed = 1; // This should always be set in the script. + m_szAddonModel[0] = 0; +} + +int CCSWeaponInfo::GetWeaponPrice( void ) const +{ + return m_iWeaponPrice; +} + +int CCSWeaponInfo::GetDefaultPrice( void ) +{ + return m_iDefaultPrice; +} + +int CCSWeaponInfo::GetPrevousPrice( void ) +{ + return m_iPreviousPrice; +} + + +void CCSWeaponInfo::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) +{ + BaseClass::Parse( pKeyValuesData, szWeaponName ); + + m_flMaxSpeed = (float)pKeyValuesData->GetInt( "MaxPlayerSpeed", 1 ); + + m_iDefaultPrice = m_iWeaponPrice = pKeyValuesData->GetInt( "WeaponPrice", -1 ); + if ( m_iWeaponPrice == -1 ) + { + // This weapon should have the price in its script. + Assert( false ); + } + + if ( CSGameRules()->IsBlackMarket() ) + { + CSWeaponID iWeaponID = AliasToWeaponID( GetTranslatedWeaponAlias ( szWeaponName ) ); + + m_iDefaultPrice = m_iWeaponPrice; + m_iPreviousPrice = CSGameRules()->GetBlackMarketPreviousPriceForWeapon( iWeaponID ); + m_iWeaponPrice = CSGameRules()->GetBlackMarketPriceForWeapon( iWeaponID ); + } + + m_flArmorRatio = pKeyValuesData->GetFloat( "WeaponArmorRatio", 1 ); + m_iCrosshairMinDistance = pKeyValuesData->GetInt( "CrosshairMinDistance", 4 ); + m_iCrosshairDeltaDistance = pKeyValuesData->GetInt( "CrosshairDeltaDistance", 3 ); + m_bCanUseWithShield = !!pKeyValuesData->GetInt( "CanEquipWithShield", false ); + m_flMuzzleScale = pKeyValuesData->GetFloat( "MuzzleFlashScale", 1 ); + + const char *pMuzzleFlashStyle = pKeyValuesData->GetString( "MuzzleFlashStyle", "CS_MUZZLEFLASH_NORM" ); + + if( pMuzzleFlashStyle ) + { + if ( Q_stricmp( pMuzzleFlashStyle, "CS_MUZZLEFLASH_X" ) == 0 ) + { + m_iMuzzleFlashStyle = CS_MUZZLEFLASH_X; + } + else if ( Q_stricmp( pMuzzleFlashStyle, "CS_MUZZLEFLASH_NONE" ) == 0 ) + { + m_iMuzzleFlashStyle = CS_MUZZLEFLASH_NONE; + } + else + { + m_iMuzzleFlashStyle = CS_MUZZLEFLASH_NORM; + } + } + else + { + Assert( false ); + } + + m_iPenetration = pKeyValuesData->GetInt( "Penetration", 1 ); + m_iDamage = pKeyValuesData->GetInt( "Damage", 42 ); // Douglas Adams 1952 - 2001 + m_flRange = pKeyValuesData->GetFloat( "Range", 8192.0f ); + m_flRangeModifier = pKeyValuesData->GetFloat( "RangeModifier", 0.98f ); + m_iBullets = pKeyValuesData->GetInt( "Bullets", 1 ); + m_flCycleTime = pKeyValuesData->GetFloat( "CycleTime", 0.15 ); + m_bAccuracyQuadratic= pKeyValuesData->GetInt( "AccuracyQuadratic", 0 ); + m_flAccuracyDivisor = pKeyValuesData->GetFloat( "AccuracyDivisor", -1 ); // -1 = off + m_flAccuracyOffset = pKeyValuesData->GetFloat( "AccuracyOffset", 0 ); + m_flMaxInaccuracy = pKeyValuesData->GetFloat( "MaxInaccuracy", 0 ); + + // new accuracy model parameters + m_fSpread[0] = pKeyValuesData->GetFloat("Spread", 0.0f); + m_fInaccuracyCrouch[0] = pKeyValuesData->GetFloat("InaccuracyCrouch", 0.0f); + m_fInaccuracyStand[0] = pKeyValuesData->GetFloat("InaccuracyStand", 0.0f); + m_fInaccuracyJump[0] = pKeyValuesData->GetFloat("InaccuracyJump", 0.0f); + m_fInaccuracyLand[0] = pKeyValuesData->GetFloat("InaccuracyLand", 0.0f); + m_fInaccuracyLadder[0] = pKeyValuesData->GetFloat("InaccuracyLadder", 0.0f); + m_fInaccuracyImpulseFire[0] = pKeyValuesData->GetFloat("InaccuracyFire", 0.0f); + m_fInaccuracyMove[0] = pKeyValuesData->GetFloat("InaccuracyMove", 0.0f); + + m_fSpread[1] = pKeyValuesData->GetFloat("SpreadAlt", 0.0f); + m_fInaccuracyCrouch[1] = pKeyValuesData->GetFloat("InaccuracyCrouchAlt", 0.0f); + m_fInaccuracyStand[1] = pKeyValuesData->GetFloat("InaccuracyStandAlt", 0.0f); + m_fInaccuracyJump[1] = pKeyValuesData->GetFloat("InaccuracyJumpAlt", 0.0f); + m_fInaccuracyLand[1] = pKeyValuesData->GetFloat("InaccuracyLandAlt", 0.0f); + m_fInaccuracyLadder[1] = pKeyValuesData->GetFloat("InaccuracyLadderAlt", 0.0f); + m_fInaccuracyImpulseFire[1] = pKeyValuesData->GetFloat("InaccuracyFireAlt", 0.0f); + m_fInaccuracyMove[1] = pKeyValuesData->GetFloat("InaccuracyMoveAlt", 0.0f); + + m_fInaccuracyReload = pKeyValuesData->GetFloat("InaccuracyReload", 0.0f); + m_fInaccuracyAltSwitch = pKeyValuesData->GetFloat("InaccuracyAltSwitch", 0.0f); + + m_fRecoveryTimeCrouch = pKeyValuesData->GetFloat("RecoveryTimeCrouch", 1.0f); + m_fRecoveryTimeStand = pKeyValuesData->GetFloat("RecoveryTimeStand", 1.0f); + + m_flTimeToIdleAfterFire = pKeyValuesData->GetFloat( "TimeToIdle", 2 ); + m_flIdleInterval = pKeyValuesData->GetFloat( "IdleInterval", 20 ); + + // Figure out what team can have this weapon. + m_iTeam = TEAM_UNASSIGNED; + const char *pTeam = pKeyValuesData->GetString( "Team", NULL ); + if ( pTeam ) + { + if ( Q_stricmp( pTeam, "CT" ) == 0 ) + { + m_iTeam = TEAM_CT; + } + else if ( Q_stricmp( pTeam, "TERRORIST" ) == 0 ) + { + m_iTeam = TEAM_TERRORIST; + } + else if ( Q_stricmp( pTeam, "ANY" ) == 0 ) + { + m_iTeam = TEAM_UNASSIGNED; + } + else + { + Assert( false ); + } + } + else + { + Assert( false ); + } + + + const char *pWrongTeamMsg = pKeyValuesData->GetString( "WrongTeamMsg", "" ); + Q_strncpy( m_WrongTeamMsg, pWrongTeamMsg, sizeof( m_WrongTeamMsg ) ); + + const char *pShieldViewModel = pKeyValuesData->GetString( "shieldviewmodel", "" ); + Q_strncpy( m_szShieldViewModel, pShieldViewModel, sizeof( m_szShieldViewModel ) ); + + const char *pAnimEx = pKeyValuesData->GetString( "PlayerAnimationExtension", "m4" ); + Q_strncpy( m_szAnimExtension, pAnimEx, sizeof( m_szAnimExtension ) ); + + // Default is 2000. + m_flBotAudibleRange = pKeyValuesData->GetFloat( "BotAudibleRange", 2000.0f ); + + const char *pTypeString = pKeyValuesData->GetString( "WeaponType", "" ); + m_WeaponType = WeaponClassFromString(pTypeString); + + m_bFullAuto = pKeyValuesData->GetBool("FullAuto"); + + // Read the addon model. + Q_strncpy( m_szAddonModel, pKeyValuesData->GetString( "AddonModel" ), sizeof( m_szAddonModel ) ); + + // Read the dropped model. + Q_strncpy( m_szDroppedModel, pKeyValuesData->GetString( "DroppedModel" ), sizeof( m_szDroppedModel ) ); + + // Read the silencer model. + Q_strncpy( m_szSilencerModel, pKeyValuesData->GetString( "SilencerModel" ), sizeof( m_szSilencerModel ) ); + +#ifndef CLIENT_DLL + // Enforce consistency for the weapon here, since that way we don't need to save off the model bounds + // for all time. + // Moved to pure_server_minimal.txt +// engine->ForceExactFile( UTIL_VarArgs("scripts/%s.ctx", szWeaponName ) ); + + // Model bounds are rounded to the nearest integer, then extended by 1 + engine->ForceModelBounds( szWorldModel, Vector( -15, -12, -18 ), Vector( 44, 16, 19 ) ); + if ( m_szAddonModel[0] ) + { + engine->ForceModelBounds( m_szAddonModel, Vector( -5, -5, -6 ), Vector( 13, 5, 7 ) ); + } + if ( m_szSilencerModel[0] ) + { + engine->ForceModelBounds( m_szSilencerModel, Vector( -15, -12, -18 ), Vector( 44, 16, 19 ) ); + } +#endif // !CLIENT_DLL +} + + diff --git a/game/shared/cstrike/cs_weapon_parse.h b/game/shared/cstrike/cs_weapon_parse.h new file mode 100644 index 0000000..33bf0e4 --- /dev/null +++ b/game/shared/cstrike/cs_weapon_parse.h @@ -0,0 +1,192 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CS_WEAPON_PARSE_H +#define CS_WEAPON_PARSE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_parse.h" +#include "networkvar.h" + + +//-------------------------------------------------------------------------------------------------------- +enum CSWeaponType +{ + WEAPONTYPE_KNIFE=0, + WEAPONTYPE_PISTOL, + WEAPONTYPE_SUBMACHINEGUN, + WEAPONTYPE_RIFLE, + WEAPONTYPE_SHOTGUN, + WEAPONTYPE_SNIPER_RIFLE, + WEAPONTYPE_MACHINEGUN, + WEAPONTYPE_C4, + WEAPONTYPE_GRENADE, + WEAPONTYPE_UNKNOWN + +}; + + +//-------------------------------------------------------------------------------------------------------- +enum CSWeaponID +{ + WEAPON_NONE = 0, + + WEAPON_P228, + WEAPON_GLOCK, + WEAPON_SCOUT, + WEAPON_HEGRENADE, + WEAPON_XM1014, + WEAPON_C4, + WEAPON_MAC10, + WEAPON_AUG, + WEAPON_SMOKEGRENADE, + WEAPON_ELITE, + WEAPON_FIVESEVEN, + WEAPON_UMP45, + WEAPON_SG550, + + WEAPON_GALIL, + WEAPON_FAMAS, + WEAPON_USP, + WEAPON_AWP, + WEAPON_MP5NAVY, + WEAPON_M249, + WEAPON_M3, + WEAPON_M4A1, + WEAPON_TMP, + WEAPON_G3SG1, + WEAPON_FLASHBANG, + WEAPON_DEAGLE, + WEAPON_SG552, + WEAPON_AK47, + WEAPON_KNIFE, + WEAPON_P90, + + WEAPON_SHIELDGUN, // BOTPORT: Is this still needed? + + WEAPON_KEVLAR, + WEAPON_ASSAULTSUIT, + WEAPON_NVG, + + WEAPON_MAX, // number of weapons weapon index +}; + +#define MAX_EQUIPMENT (WEAPON_MAX - WEAPON_KEVLAR) + +void PrepareEquipmentInfo( void ); + +//-------------------------------------------------------------------------------------------------------- +const char * WeaponClassAsString( CSWeaponType weaponType ); + +//-------------------------------------------------------------------------------------------------------- +CSWeaponType WeaponClassFromString( const char* weaponType ); + +//-------------------------------------------------------------------------------------------------------- +CSWeaponType WeaponClassFromWeaponID( CSWeaponID weaponID ); + +//-------------------------------------------------------------------------------------------------------- +const char * WeaponIdAsString( CSWeaponID weaponID ); + +//-------------------------------------------------------------------------------------------------------- +CSWeaponID WeaponIdFromString( const char *szWeaponName ); + + +//-------------------------------------------------------------------------------------------------------- +class CCSWeaponInfo : public FileWeaponInfo_t +{ +public: + DECLARE_CLASS_GAMEROOT( CCSWeaponInfo, FileWeaponInfo_t ); + + CCSWeaponInfo(); + + virtual void Parse( ::KeyValues *pKeyValuesData, const char *szWeaponName ); + + int GetRealWeaponPrice( void ) { return m_iWeaponPrice; } + + +public: + + float m_flMaxSpeed; // How fast the player can run while this is his primary weapon. + + CSWeaponType m_WeaponType; + + bool m_bFullAuto; // is this a fully automatic weapon? + + int m_iTeam; // Which team can have this weapon. TEAM_UNASSIGNED if both can have it. + float m_flBotAudibleRange; // How far away a bot can hear this weapon. + float m_flArmorRatio; + + int m_iCrosshairMinDistance; + int m_iCrosshairDeltaDistance; + + bool m_bCanUseWithShield; + + char m_WrongTeamMsg[32]; // Reference to a string describing the error if someone tries to buy + // this weapon but they're on the wrong team to have it. + // Zero-length if no specific message for this weapon. + + char m_szAnimExtension[16]; + char m_szShieldViewModel[64]; + + char m_szAddonModel[MAX_WEAPON_STRING]; // If this is set, it is used as the addon model. Otherwise, szWorldModel is used. + char m_szDroppedModel[MAX_WEAPON_STRING]; // Alternate dropped model, if different from the szWorldModel the player holds + char m_szSilencerModel[MAX_WEAPON_STRING]; // Alternate model with silencer attached + + int m_iMuzzleFlashStyle; + float m_flMuzzleScale; + + // Parameters for FX_FireBullets: + int m_iPenetration; + int m_iDamage; + float m_flRange; + float m_flRangeModifier; + int m_iBullets; + float m_flCycleTime; + + // Variables that control how fast the weapon's accuracy changes as it is fired. + bool m_bAccuracyQuadratic; + float m_flAccuracyDivisor; + float m_flAccuracyOffset; + float m_flMaxInaccuracy; + + // variables for new accuracy model + float m_fSpread[2]; + float m_fInaccuracyCrouch[2]; + float m_fInaccuracyStand[2]; + float m_fInaccuracyJump[2]; + float m_fInaccuracyLand[2]; + float m_fInaccuracyLadder[2]; + float m_fInaccuracyImpulseFire[2]; + float m_fInaccuracyMove[2]; + float m_fRecoveryTimeStand; + float m_fRecoveryTimeCrouch; + float m_fInaccuracyReload; + float m_fInaccuracyAltSwitch; + + // Delay until the next idle animation after shooting. + float m_flTimeToIdleAfterFire; + float m_flIdleInterval; + + int GetWeaponPrice( void ) const; + int GetDefaultPrice( void ); + int GetPrevousPrice( void ); + void SetWeaponPrice( int iPrice ) { m_iWeaponPrice = iPrice; } + void SetDefaultPrice( int iPrice ) { m_iDefaultPrice = iPrice; } + void SetPreviousPrice( int iPrice ) { m_iPreviousPrice = iPrice; } + +private: + + int m_iWeaponPrice; + int m_iDefaultPrice; + int m_iPreviousPrice; + +}; + + +#endif // CS_WEAPON_PARSE_H diff --git a/game/shared/cstrike/flashbang_projectile.cpp b/game/shared/cstrike/flashbang_projectile.cpp new file mode 100644 index 0000000..aeb9830 --- /dev/null +++ b/game/shared/cstrike/flashbang_projectile.cpp @@ -0,0 +1,316 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "flashbang_projectile.h" +#include "shake.h" +#include "engine/IEngineSound.h" +#include "cs_player.h" +#include "dlight.h" +#include "KeyValues.h" +#include "weapon_csbase.h" +#include "collisionutils.h" +#include "particle_smokegrenade.h" +#include "smoke_fog_overlay_shared.h" + +#define GRENADE_MODEL "models/Weapons/w_eq_flashbang_thrown.mdl" + + +LINK_ENTITY_TO_CLASS( flashbang_projectile, CFlashbangProjectile ); +PRECACHE_WEAPON_REGISTER( flashbang_projectile ); + +float PercentageOfFlashForPlayer(CBaseEntity *player, Vector flashPos, CBaseEntity *pevInflictor) +{ + float retval = 0.0f; + + trace_t tr; + + Vector pos = player->EyePosition(); + Vector vecRight, vecUp, vecForward; + AngleVectors( player->EyeAngles(), &vecForward ); + + QAngle tempAngle; + VectorAngles(player->EyePosition() - flashPos, tempAngle); + AngleVectors(tempAngle, NULL, &vecRight, &vecUp); + + vecRight.NormalizeInPlace(); + vecUp.NormalizeInPlace(); + + UTIL_TraceLine( flashPos, pos, + (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_DEBRIS|CONTENTS_MONSTER), + pevInflictor, COLLISION_GROUP_NONE, &tr ); + + if ((tr.fraction == 1.0) || (tr.m_pEnt == player)) + { + retval = 1.0; + } + else + { + return 0.0; + } + + CBaseEntity *pSGren; + + for( pSGren = gEntList.FindEntityByClassname( NULL, "env_particlesmokegrenade" ); + pSGren; + pSGren = gEntList.FindEntityByClassname( pSGren, "env_particlesmokegrenade" ) ) + { + ParticleSmokeGrenade *pPSG =( ParticleSmokeGrenade* ) pSGren; + + if ( gpGlobals->curtime > pPSG->m_flSpawnTime + pPSG->m_FadeStartTime ) // ignore the smoke grenade if it's fading. + continue; + + float flHit1, flHit2; + + float flInnerRadius = SMOKEGRENADE_PARTICLERADIUS; +// float flOutterRadius = flInnerRadius + ( 0.5 * SMOKEPARTICLE_SIZE ); + + Vector vPos = pSGren->GetAbsOrigin(); + + /*debugoverlay->AddBoxOverlay( pSGren->GetAbsOrigin(), Vector( flInnerRadius, flInnerRadius, flInnerRadius ), + Vector( -flInnerRadius, -flInnerRadius, -flInnerRadius ), QAngle( 0, 0, 0 ), 0, 255, 0, 30, 10 ); + debugoverlay->AddBoxOverlay( pSGren->GetAbsOrigin(), Vector( flOutterRadius, flOutterRadius, flOutterRadius ), + Vector( -flOutterRadius, -flOutterRadius, -flOutterRadius ), QAngle( 0, 0, 0 ), 255, 0, 0, 30, 10 ); */ + + if ( IntersectInfiniteRayWithSphere( pos, vecForward, vPos, flInnerRadius, &flHit1, &flHit2 ) ) + { + retval *= 0.8; + } +/* else if ( IntersectInfiniteRayWithSphere( pos, vecForward, vPos, flOutterRadius, &flHit1, &flHit2 ) ) + { + retval *= 0.9; + } +*/ + } + + return retval; + +} + +// --------------------------------------------------------------------------------------------------- // +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! +// --------------------------------------------------------------------------------------------------- // + +void RadiusFlash( + Vector vecSrc, + CBaseEntity *pevInflictor, + CBaseEntity *pevAttacker, + float flDamage, + int iClassIgnore, + int bitsDamageType ) +{ + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + trace_t tr; + float flAdjustedDamage; + variant_t var; + Vector vecEyePos; + float fadeTime, fadeHold; + Vector vForward; + Vector vecLOS; + float flDot; + + CBaseEntity *pEntity = NULL; + static float flRadius = 1500; + float falloff = flDamage / flRadius; + + bool bInWater = (UTIL_PointContents( vecSrc ) == CONTENTS_WATER); + + // iterate on all entities in the vicinity. + while ((pEntity = gEntList.FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + bool bPlayer = pEntity->IsPlayer(); + bool bHostage = ( Q_stricmp( pEntity->GetClassname(), "hostage_entity" ) == 0 ); + + if( !bPlayer && !bHostage ) + continue; + + vecEyePos = pEntity->EyePosition(); + + // blasts don't travel into or out of water + if ( bInWater && pEntity->GetWaterLevel() == 0) + continue; + if (!bInWater && pEntity->GetWaterLevel() == 3) + continue; + + float percentageOfFlash = PercentageOfFlashForPlayer(pEntity, vecSrc, pevInflictor); + + if ( percentageOfFlash > 0.0 ) + { + // decrease damage for an ent that's farther from the grenade + flAdjustedDamage = flDamage - ( vecSrc - pEntity->EyePosition() ).Length() * falloff; + + if ( flAdjustedDamage > 0 ) + { + // See if we were facing the flash + AngleVectors( pEntity->EyeAngles(), &vForward ); + + vecLOS = ( vecSrc - vecEyePos ); + + float flDistance = vecLOS.Length(); + + // Normalize both vectors so the dotproduct is in the range -1.0 <= x <= 1.0 + vecLOS.NormalizeInPlace(); + + flDot = DotProduct (vecLOS, vForward); + + float startingAlpha = 255; + + // if target is facing the bomb, the effect lasts longer + if( flDot >= 0.5 ) + { + // looking at the flashbang + fadeTime = flAdjustedDamage * 2.5f; + fadeHold = flAdjustedDamage * 1.25f; + } + else if( flDot >= -0.5 ) + { + // looking to the side + fadeTime = flAdjustedDamage * 1.75f; + fadeHold = flAdjustedDamage * 0.8f; + } + else + { + // facing away + fadeTime = flAdjustedDamage * 1.0f; + fadeHold = flAdjustedDamage * 0.75f; + startingAlpha = 200; + } + + fadeTime *= percentageOfFlash; + fadeHold *= percentageOfFlash; + + if ( bPlayer ) + { + // blind players and bots + CCSPlayer *player = static_cast< CCSPlayer * >( pEntity ); + + //============================================================================= + // HPE_BEGIN: + // [tj] Store who was responsible for the most recent flashbang blinding. + //============================================================================= + + CCSPlayer *attacker = ToCSPlayer (pevAttacker); + if (attacker && player) + { + player->SetLastFlashbangAttacker(attacker); + } + + //============================================================================= + // HPE_END + //============================================================================= + + + + player->Blind( fadeHold, fadeTime, startingAlpha ); + + // deafen players and bots + player->Deafen( flDistance ); + } + else if ( bHostage ) + { + variant_t val; + val.SetFloat( fadeTime ); + pEntity->AcceptInput( "flashbang", pevInflictor, pevAttacker, val, 0 ); + } + } + } + } + + CPVSFilter filter(vecSrc); + te->DynamicLight( filter, 0.0, &vecSrc, 255, 255, 255, 2, 400, 0.1, 768 ); +} + +// --------------------------------------------------------------------------------------------------- // +// CFlashbangProjectile implementation. +// --------------------------------------------------------------------------------------------------- // + +CFlashbangProjectile* CFlashbangProjectile::Create( + const Vector &position, + const QAngle &angles, + const Vector &velocity, + const AngularImpulse &angVelocity, + CBaseCombatCharacter *pOwner ) +{ + CFlashbangProjectile *pGrenade = (CFlashbangProjectile*)CBaseEntity::Create( "flashbang_projectile", position, angles, pOwner ); + + // Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER + // one second before detonation. + pGrenade->SetAbsVelocity( velocity ); + pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity ); + pGrenade->SetThrower( pOwner ); + pGrenade->m_flDamage = 100; + pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); + + pGrenade->SetTouch( &CBaseGrenade::BounceTouch ); + + pGrenade->SetThink( &CBaseCSGrenadeProjectile::DangerSoundThink ); + pGrenade->SetNextThink( gpGlobals->curtime ); + + pGrenade->SetDetonateTimerLength( 1.5 ); + + pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); + + pGrenade->SetGravity( BaseClass::GetGrenadeGravity() ); + pGrenade->SetFriction( BaseClass::GetGrenadeFriction() ); + pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() ); + + pGrenade->m_pWeaponInfo = GetWeaponInfo( WEAPON_FLASHBANG ); + + + return pGrenade; +} + +void CFlashbangProjectile::Spawn() +{ + SetModel( GRENADE_MODEL ); + BaseClass::Spawn(); +} + +void CFlashbangProjectile::Precache() +{ + PrecacheModel( GRENADE_MODEL ); + + PrecacheScriptSound( "Flashbang.Explode" ); + PrecacheScriptSound( "Flashbang.Bounce" ); + + BaseClass::Precache(); +} + +void CFlashbangProjectile::Detonate() +{ + RadiusFlash ( GetAbsOrigin(), this, GetThrower(), 4, CLASS_NONE, DMG_BLAST ); + EmitSound( "Flashbang.Explode" ); + + // tell the bots a flashbang grenade has exploded + CCSPlayer *player = ToCSPlayer(GetThrower()); + if ( player ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "flashbang_detonate" ); + if ( event ) + { + event->SetInt( "userid", player->GetUserID() ); + event->SetFloat( "x", GetAbsOrigin().x ); + event->SetFloat( "y", GetAbsOrigin().y ); + event->SetFloat( "z", GetAbsOrigin().z ); + gameeventmanager->FireEvent( event ); + } + } + + UTIL_Remove( this ); +} + +//TODO: Let physics handle the sound! +void CFlashbangProjectile::BounceSound( void ) +{ + EmitSound( "Flashbang.Bounce" ); +} diff --git a/game/shared/cstrike/flashbang_projectile.h b/game/shared/cstrike/flashbang_projectile.h new file mode 100644 index 0000000..454ab1a --- /dev/null +++ b/game/shared/cstrike/flashbang_projectile.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef HEGRENADE_PROJECTILE_H +#define HEGRENADE_PROJECTILE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "basecsgrenade_projectile.h" + + +class CFlashbangProjectile : public CBaseCSGrenadeProjectile +{ +public: + DECLARE_CLASS( CFlashbangProjectile, CBaseCSGrenadeProjectile ); + +// Overrides. +public: + virtual void Spawn(); + virtual void Precache(); + virtual void BounceSound( void ); + virtual void Detonate(); + +// Grenade stuff. + static CFlashbangProjectile* Create( + const Vector &position, + const QAngle &angles, + const Vector &velocity, + const AngularImpulse &angVelocity, + CBaseCombatCharacter *pOwner ); +}; + + +#endif // HEGRENADE_PROJECTILE_H diff --git a/game/shared/cstrike/fx_cs_shared.cpp b/game/shared/cstrike/fx_cs_shared.cpp new file mode 100644 index 0000000..446d2b5 --- /dev/null +++ b/game/shared/cstrike/fx_cs_shared.cpp @@ -0,0 +1,345 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "fx_cs_shared.h" +#include "weapon_csbase.h" + +#ifndef CLIENT_DLL + #include "ilagcompensationmanager.h" +#endif + +ConVar weapon_accuracy_logging( "weapon_accuracy_logging", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE ); + +#ifdef CLIENT_DLL + +#include "fx_impact.h" + + // this is a cheap ripoff from CBaseCombatWeapon::WeaponSound(): + void FX_WeaponSound( + int iPlayerIndex, + WeaponSound_t sound_type, + const Vector &vOrigin, + CCSWeaponInfo *pWeaponInfo, float flSoundTime ) + { + + // If we have some sounds from the weapon classname.txt file, play a random one of them + const char *shootsound = pWeaponInfo->aShootSounds[ sound_type ]; + if ( !shootsound || !shootsound[0] ) + return; + + CBroadcastRecipientFilter filter; // this is client side only + if ( !te->CanPredict() ) + return; + + CBaseEntity::EmitSound( filter, iPlayerIndex, shootsound, &vOrigin, flSoundTime ); + } + + class CGroupedSound + { + public: + string_t m_SoundName; + Vector m_vPos; + }; + + CUtlVector<CGroupedSound> g_GroupedSounds; + + + // Called by the ImpactSound function. + void ShotgunImpactSoundGroup( const char *pSoundName, const Vector &vEndPos ) + { + int i; + // Don't play the sound if it's too close to another impact sound. + for ( i=0; i < g_GroupedSounds.Count(); i++ ) + { + CGroupedSound *pSound = &g_GroupedSounds[i]; + + if ( vEndPos.DistToSqr( pSound->m_vPos ) < 300*300 ) + { + if ( Q_stricmp( pSound->m_SoundName, pSoundName ) == 0 ) + return; + } + } + + // Ok, play the sound and add it to the list. + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, NULL, pSoundName, &vEndPos ); + + i = g_GroupedSounds.AddToTail(); + g_GroupedSounds[i].m_SoundName = pSoundName; + g_GroupedSounds[i].m_vPos = vEndPos; + } + + + void StartGroupingSounds() + { + Assert( g_GroupedSounds.Count() == 0 ); + SetImpactSoundRoute( ShotgunImpactSoundGroup ); + } + + + void EndGroupingSounds() + { + g_GroupedSounds.Purge(); + SetImpactSoundRoute( NULL ); + } + +#else + + #include "te_shotgun_shot.h" + + // Server doesn't play sounds anyway. + void StartGroupingSounds() {} + void EndGroupingSounds() {} + void FX_WeaponSound ( int iPlayerIndex, + WeaponSound_t sound_type, + const Vector &vOrigin, + CCSWeaponInfo *pWeaponInfo, float flSoundTime ) {}; + +#endif + + +// This runs on both the client and the server. +// On the server, it only does the damage calculations. +// On the client, it does all the effects. +void FX_FireBullets( + int iPlayerIndex, + const Vector &vOrigin, + const QAngle &vAngles, + int iWeaponID, + int iMode, + int iSeed, + float fInaccuracy, + float fSpread, + float flSoundTime + ) +{ + bool bDoEffects = true; + +#ifdef CLIENT_DLL + C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) ); +#else + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) ); +#endif + + const char * weaponAlias = WeaponIDToAlias( iWeaponID ); + + if ( !weaponAlias ) + { + DevMsg("FX_FireBullets: weapon alias for ID %i not found\n", iWeaponID ); + return; + } + +#if !defined(CLIENT_DLL) + if ( weapon_accuracy_logging.GetBool() ) + { + char szFlags[256]; + + V_strcpy(szFlags, " "); + +// #if defined(CLIENT_DLL) +// V_strcat(szFlags, "CLIENT ", sizeof(szFlags)); +// #else +// V_strcat(szFlags, "SERVER ", sizeof(szFlags)); +// #endif +// + if ( pPlayer->GetMoveType() == MOVETYPE_LADDER ) + V_strcat(szFlags, "LADDER ", sizeof(szFlags)); + + if ( FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + V_strcat(szFlags, "GROUND ", sizeof(szFlags)); + + if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING) ) + V_strcat(szFlags, "DUCKING ", sizeof(szFlags)); + + float fVelocity = pPlayer->GetAbsVelocity().Length2D(); + + Msg("FireBullets @ %10f [ %s ]: inaccuracy=%f spread=%f max dispersion=%f mode=%2i vel=%10f seed=%3i %s\n", + gpGlobals->curtime, weaponAlias, fInaccuracy, fSpread, fInaccuracy + fSpread, iMode, fVelocity, iSeed, szFlags); + } +#endif + + char wpnName[128]; + Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", weaponAlias ); + WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName ); + + if ( hWpnInfo == GetInvalidWeaponInfoHandle() ) + { + DevMsg("FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", wpnName ); + return; + } + + CCSWeaponInfo *pWeaponInfo = static_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); + + // Do the firing animation event. + if ( pPlayer && !pPlayer->IsDormant() ) + { + if ( iMode == Primary_Mode ) + pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY ); + else + pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_SECONDARY ); + } + +#ifndef CLIENT_DLL + // if this is server code, send the effect over to client as temp entity + // Dispatch one message for all the bullet impacts and sounds. + TE_FireBullets( + iPlayerIndex, + vOrigin, + vAngles, + iWeaponID, + iMode, + iSeed, + fInaccuracy, + fSpread + ); + + + // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation. + pPlayer->NoteWeaponFired(); + + bDoEffects = false; // no effects on server +#endif + + iSeed++; + + int iDamage = pWeaponInfo->m_iDamage; + float flRange = pWeaponInfo->m_flRange; + int iPenetration = pWeaponInfo->m_iPenetration; + float flRangeModifier = pWeaponInfo->m_flRangeModifier; + int iAmmoType = pWeaponInfo->iAmmoType; + + WeaponSound_t sound_type = SINGLE; + + // CS HACK, tweak some weapon values based on primary/secondary mode + + if ( iWeaponID == WEAPON_GLOCK ) + { + if ( iMode == Secondary_Mode ) + { + iDamage = 18; // reduced power for burst shots + flRangeModifier = 0.9f; + } + } + else if ( iWeaponID == WEAPON_M4A1 ) + { + if ( iMode == Secondary_Mode ) + { + flRangeModifier = 0.95f; // slower bullets in silenced mode + sound_type = SPECIAL1; + } + } + else if ( iWeaponID == WEAPON_USP ) + { + if ( iMode == Secondary_Mode ) + { + iDamage = 30; // reduced damage in silenced mode + sound_type = SPECIAL1; + } + } + + if ( bDoEffects) + { + FX_WeaponSound( iPlayerIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime ); + } + + + // Fire bullets, calculate impacts & effects + + if ( !pPlayer ) + return; + + StartGroupingSounds(); + +#ifdef GAME_DLL + pPlayer->StartNewBulletGroup(); +#endif + +#if !defined (CLIENT_DLL) + // Move other players back to history positions based on local player's lag + lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() ); +#endif + + RandomSeed( iSeed ); // init random system with this seed + + // Get accuracy displacement + float fTheta0 = RandomFloat(0.0f, 2.0f * M_PI); + float fRadius0 = RandomFloat(0.0f, fInaccuracy); + float x0 = fRadius0 * cosf(fTheta0); + float y0 = fRadius0 * sinf(fTheta0); + + const int kMaxBullets = 16; + float x1[kMaxBullets], y1[kMaxBullets]; + Assert(pWeaponInfo->m_iBullets <= kMaxBullets); + + // the RNG can be desynchronized by FireBullet(), so pre-generate all spread offsets + for ( int iBullet=0; iBullet < pWeaponInfo->m_iBullets; iBullet++ ) + { + float fTheta1 = RandomFloat(0.0f, 2.0f * M_PI); + float fRadius1 = RandomFloat(0.0f, fSpread); + x1[iBullet] = fRadius1 * cosf(fTheta1); + y1[iBullet] = fRadius1 * sinf(fTheta1); + } + + for ( int iBullet=0; iBullet < pWeaponInfo->m_iBullets; iBullet++ ) + { + pPlayer->FireBullet( + vOrigin, + vAngles, + flRange, + iPenetration, + iAmmoType, + iDamage, + flRangeModifier, + pPlayer, + bDoEffects, + x0 + x1[iBullet], y0 + y1[iBullet] ); + } + +#if !defined (CLIENT_DLL) + lagcompensation->FinishLagCompensation( pPlayer ); +#endif + + EndGroupingSounds(); +} + +// This runs on both the client and the server. +// On the server, it dispatches a TE_PlantBomb to visible clients. +// On the client, it plays the planting animation. +void FX_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option ) +{ +#ifdef CLIENT_DLL + C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) ); +#else + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) ); +#endif + + // Do the firing animation event. + if ( pPlayer && !pPlayer->IsDormant() ) + { + switch ( option ) + { + case PLANTBOMB_PLANT: + { + pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY ); + } + break; + + case PLANTBOMB_ABORT: + { + pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_CLEAR_FIRING ); + } + break; + } + } + +#ifndef CLIENT_DLL + // if this is server code, send the effect over to client as temp entity + // Dispatch one message for all the bullet impacts and sounds. + TE_PlantBomb( iPlayerIndex, vOrigin, option ); +#endif +} + diff --git a/game/shared/cstrike/fx_cs_shared.h b/game/shared/cstrike/fx_cs_shared.h new file mode 100644 index 0000000..19008e8 --- /dev/null +++ b/game/shared/cstrike/fx_cs_shared.h @@ -0,0 +1,47 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FX_CS_SHARED_H +#define FX_CS_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + + +#ifdef CLIENT_DLL + #include "c_cs_player.h" +#else + #include "cs_player.h" +#endif + + +// This runs on both the client and the server. +// On the server, it only does the damage calculations. +// On the client, it does all the effects. +void FX_FireBullets( + int iPlayer, + const Vector &vOrigin, + const QAngle &vAngles, + int iWeaponID, + int iMode, + int iSeed, + float fInaccuracy, + float fSpread, + float flSoundTime = 0.0f + ); + +// This runs on both the client and the server. +// On the server, it dispatches a TE_PlantBomb to visible clients. +// On the client, it plays the planting animation. +enum PlantBombOption_t +{ + PLANTBOMB_PLANT, // play the planting animation + PLANTBOMB_ABORT, // abort the planting animation + // NOTE: If you add additional items to this enum then m_option in CTEPlantBomb will need to have its SendPropInt setting changed to have more than one bit. +}; +void FX_PlantBomb( int iPlayer, const Vector &vOrigin, PlantBombOption_t option ); + +#endif // FX_CS_SHARED_H diff --git a/game/shared/cstrike/hegrenade_projectile.cpp b/game/shared/cstrike/hegrenade_projectile.cpp new file mode 100644 index 0000000..bd80239 --- /dev/null +++ b/game/shared/cstrike/hegrenade_projectile.cpp @@ -0,0 +1,94 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "hegrenade_projectile.h" +#include "soundent.h" +#include "cs_player.h" +#include "KeyValues.h" +#include "weapon_csbase.h" + +#define GRENADE_MODEL "models/Weapons/w_eq_fraggrenade_thrown.mdl" + + +LINK_ENTITY_TO_CLASS( hegrenade_projectile, CHEGrenadeProjectile ); +PRECACHE_WEAPON_REGISTER( hegrenade_projectile ); + +CHEGrenadeProjectile* CHEGrenadeProjectile::Create( + const Vector &position, + const QAngle &angles, + const Vector &velocity, + const AngularImpulse &angVelocity, + CBaseCombatCharacter *pOwner, + float timer ) +{ + CHEGrenadeProjectile *pGrenade = (CHEGrenadeProjectile*)CBaseEntity::Create( "hegrenade_projectile", position, angles, pOwner ); + + // Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER + // one second before detonation. + + pGrenade->SetDetonateTimerLength( 1.5 ); + pGrenade->SetAbsVelocity( velocity ); + pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity ); + pGrenade->SetThrower( pOwner ); + + pGrenade->SetGravity( BaseClass::GetGrenadeGravity() ); + pGrenade->SetFriction( BaseClass::GetGrenadeFriction() ); + pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() ); + + pGrenade->m_flDamage = 100; + pGrenade->m_DmgRadius = pGrenade->m_flDamage * 3.5f; + pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); + pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); + + // make NPCs afaid of it while in the air + pGrenade->SetThink( &CHEGrenadeProjectile::DangerSoundThink ); + pGrenade->SetNextThink( gpGlobals->curtime ); + + pGrenade->m_pWeaponInfo = GetWeaponInfo( WEAPON_HEGRENADE ); + + return pGrenade; +} + +void CHEGrenadeProjectile::Spawn() +{ + SetModel( GRENADE_MODEL ); + BaseClass::Spawn(); +} + +void CHEGrenadeProjectile::Precache() +{ + PrecacheModel( GRENADE_MODEL ); + + PrecacheScriptSound( "HEGrenade.Bounce" ); + + BaseClass::Precache(); +} + +void CHEGrenadeProjectile::BounceSound( void ) +{ + EmitSound( "HEGrenade.Bounce" ); +} + +void CHEGrenadeProjectile::Detonate() +{ + BaseClass::Detonate(); + + // tell the bots an HE grenade has exploded + CCSPlayer *player = ToCSPlayer(GetThrower()); + if ( player ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "hegrenade_detonate" ); + if ( event ) + { + event->SetInt( "userid", player->GetUserID() ); + event->SetFloat( "x", GetAbsOrigin().x ); + event->SetFloat( "y", GetAbsOrigin().y ); + event->SetFloat( "z", GetAbsOrigin().z ); + gameeventmanager->FireEvent( event ); + } + } +} diff --git a/game/shared/cstrike/hegrenade_projectile.h b/game/shared/cstrike/hegrenade_projectile.h new file mode 100644 index 0000000..50df243 --- /dev/null +++ b/game/shared/cstrike/hegrenade_projectile.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef HEGRENADE_PROJECTILE_H +#define HEGRENADE_PROJECTILE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basecsgrenade_projectile.h" + +class CHEGrenadeProjectile : public CBaseCSGrenadeProjectile +{ +public: + DECLARE_CLASS( CHEGrenadeProjectile, CBaseCSGrenadeProjectile ); + + +// Overrides. +public: + virtual void Spawn(); + virtual void Precache(); + virtual void BounceSound( void ); + virtual void Detonate(); + +// Grenade stuff. +public: + + static CHEGrenadeProjectile* Create( + const Vector &position, + const QAngle &angles, + const Vector &velocity, + const AngularImpulse &angVelocity, + CBaseCombatCharacter *pOwner, + float timer ); + + void SetTimer( float timer ); + +private: + float m_flDetonateTime; +}; + + +#endif // HEGRENADE_PROJECTILE_H diff --git a/game/shared/cstrike/weapon_ak47.cpp b/game/shared/cstrike/weapon_ak47.cpp new file mode 100644 index 0000000..79b54b6 --- /dev/null +++ b/game/shared/cstrike/weapon_ak47.cpp @@ -0,0 +1,103 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + +#if defined( CLIENT_DLL ) + + #define CAK47 C_AK47 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CAK47 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CAK47, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CAK47(); + + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_AK47; } + +private: + CAK47( const CAK47 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( AK47, DT_WeaponAK47 ) + +BEGIN_NETWORK_TABLE( CAK47, DT_WeaponAK47 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CAK47 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_ak47, CAK47 ); +PRECACHE_WEAPON_REGISTER( weapon_ak47 ); + +// ---------------------------------------------------------------------------- // +// CAK47 implementation. +// ---------------------------------------------------------------------------- // + +CAK47::CAK47() +{ +} + + +float CAK47::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.04f + 0.4f * m_flAccuracy; + else if (pPlayer->GetAbsVelocity().Length2D() > 140) + return 0.04f + 0.07f * m_flAccuracy; + else + return 0.0275f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + + +void CAK47::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (pPlayer->GetAbsVelocity().Length2D() > 5 ) + pPlayer->KickBack ( 1.5, 0.45, 0.225, 0.05, 6.5, 2.5, 7 ); + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack ( 2, 1.0, 0.5, 0.35, 9, 6, 5 ); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack ( 0.9, 0.35, 0.15, 0.025, 5.5, 1.5, 9 ); + else + pPlayer->KickBack ( 1, 0.375, 0.175, 0.0375, 5.75, 1.75, 8 ); +} diff --git a/game/shared/cstrike/weapon_aug.cpp b/game/shared/cstrike/weapon_aug.cpp new file mode 100644 index 0000000..319a9fc --- /dev/null +++ b/game/shared/cstrike/weapon_aug.cpp @@ -0,0 +1,160 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponAug C_WeaponAug + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponAug : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponAug, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponAug(); + + virtual void SecondaryAttack(); + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + virtual bool Reload(); + virtual bool Deploy(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_AUG; } + +#ifdef CLIENT_DLL + virtual bool HideViewModelWhenZoomed( void ) { return false; } +#endif + +private: + + void AUGFire( float flSpread, bool bZoomed ); + + CWeaponAug( const CWeaponAug & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAug, DT_WeaponAug ) + +BEGIN_NETWORK_TABLE( CWeaponAug, DT_WeaponAug ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponAug ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_aug, CWeaponAug ); +PRECACHE_WEAPON_REGISTER( weapon_aug ); + + + +CWeaponAug::CWeaponAug() +{ +} + +void CWeaponAug::SecondaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + { + pPlayer->SetFOV( pPlayer, 55, 0.2f ); + m_weaponMode = Secondary_Mode; + } + else if ( pPlayer->GetFOV() == 55 ) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), 0.15f ); + m_weaponMode = Primary_Mode; + } + else + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() ); + m_weaponMode = Primary_Mode; + } + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3; +} + + +float CWeaponAug::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.035f + 0.4f * m_flAccuracy; + + else if ( pPlayer->GetAbsVelocity().Length2D() > 140 ) + return 0.035f + 0.07f * m_flAccuracy; + else + return 0.02f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponAug::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + bool bZoomed = pPlayer->GetFOV() < pPlayer->GetDefaultFOV(); + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + if ( bZoomed ) + flCycleTime = 0.135f; + + if ( !CSBaseGunFire( flCycleTime, m_weaponMode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( pPlayer->GetAbsVelocity().Length2D() > 5 ) + pPlayer->KickBack ( 1, 0.45, 0.275, 0.05, 4, 2.5, 7 ); + + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack ( 1.25, 0.45, 0.22, 0.18, 5.5, 4, 5 ); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack ( 0.575, 0.325, 0.2, 0.011, 3.25, 2, 8 ); + + else + pPlayer->KickBack ( 0.625, 0.375, 0.25, 0.0125, 3.5, 2.25, 8 ); +} + + +bool CWeaponAug::Reload() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Reload(); +} + +bool CWeaponAug::Deploy() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Deploy(); +} diff --git a/game/shared/cstrike/weapon_awp.cpp b/game/shared/cstrike/weapon_awp.cpp new file mode 100644 index 0000000..bcd4113 --- /dev/null +++ b/game/shared/cstrike/weapon_awp.cpp @@ -0,0 +1,306 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponAWP C_WeaponAWP + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "KeyValues.h" + +#endif + +#define SNIPER_ZOOM_CONTEXT "SniperRifleThink" + +const int cAWPMidZoomFOV = 40; +const int cAWPMaxZoomFOV = 10; + +#ifdef AWP_UNZOOM + ConVar sv_awpunzoomdelay( + "sv_awpunzoomdelay", + "1.0", + 0, + "how many seconds to zoom the zoom up after firing", + true, 0, // min value + false, 0 // max value + ); +#endif + + +class CWeaponAWP : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponAWP, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_DATADESC(); +#endif + + CWeaponAWP(); + + virtual void Spawn(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + + virtual float GetInaccuracy() const; + virtual float GetMaxSpeed() const; + virtual bool IsAwp() const; + virtual bool Reload(); + virtual bool Deploy(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_AWP; } + +private: + +#ifdef AWP_UNZOOM + void UnzoomThink( void ); +#endif + + CWeaponAWP( const CWeaponAWP & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAWP, DT_WeaponAWP ) + +BEGIN_NETWORK_TABLE( CWeaponAWP, DT_WeaponAWP ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponAWP ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_awp, CWeaponAWP ); +PRECACHE_WEAPON_REGISTER( weapon_awp ); + +#ifndef CLIENT_DLL + + BEGIN_DATADESC( CWeaponAWP ) +#ifdef AWP_UNZOOM + DEFINE_THINKFUNC( UnzoomThink ), +#endif + END_DATADESC() + +#endif + +CWeaponAWP::CWeaponAWP() +{ +} + +void CWeaponAWP::Spawn() +{ + Precache(); + + BaseClass::Spawn(); +} + + +void CWeaponAWP::SecondaryAttack() +{ + const float kZoomTime = 0.10f; + + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer == NULL ) + { + Assert( pPlayer != NULL ); + return; + } + + if ( pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + { + pPlayer->SetFOV( pPlayer, cAWPMidZoomFOV, kZoomTime ); + m_weaponMode = Secondary_Mode; + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyAltSwitch; + } + else if ( pPlayer->GetFOV() == cAWPMidZoomFOV ) + { + pPlayer->SetFOV( pPlayer, cAWPMaxZoomFOV, kZoomTime ); + m_weaponMode = Secondary_Mode; + } + else + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), kZoomTime ); + m_weaponMode = Primary_Mode; + } + + +#ifndef CLIENT_DLL + // If this isn't guarded, the sound will be emitted twice, once by the server and once by the client. + // Let the server play it since if only the client plays it, it's liable to get played twice cause of + // a prediction error. joy. + + //============================================================================= + // HPE_BEGIN: + // [tj] Playing this from the player so that we don't try to play the sound outside the level. + //============================================================================= + if ( GetPlayerOwner() ) + { + GetPlayerOwner()->EmitSound( "Default.Zoom" ); + } + //============================================================================= + // HPE_END + //============================================================================= + // let the bots hear the rifle zoom + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_zoom" ); + if ( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3f; + m_zoomFullyActiveTime = gpGlobals->curtime + 0.15; // The worst zoom time from above. + +} + +float CWeaponAWP::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + float fSpread = 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + fSpread = 0.85f; + + else if ( pPlayer->GetAbsVelocity().Length2D() > 140 ) + fSpread = 0.25f; + + else if ( pPlayer->GetAbsVelocity().Length2D() > 10 ) + fSpread = 0.10f; + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + fSpread = 0.0f; + + else + fSpread = 0.001f; + + // If we are not zoomed in, or we have very recently zoomed and are still transitioning, the bullet diverts more. + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV() || (gpGlobals->curtime < m_zoomFullyActiveTime)) + { + fSpread += 0.08f; + } + + return fSpread; + } + else + { + return BaseClass::GetInaccuracy(); + } +} + +void CWeaponAWP::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, m_weaponMode ) ) + return; + + if ( m_weaponMode == Secondary_Mode ) + { + float midFOVdistance = fabs( pPlayer->GetFOV() - (float)cAWPMidZoomFOV ); + float farFOVdistance = fabs( pPlayer->GetFOV() - (float)cAWPMaxZoomFOV ); + if ( midFOVdistance < farFOVdistance ) + { + pPlayer->m_iLastZoom = cAWPMidZoomFOV; + } + else + { + pPlayer->m_iLastZoom = cAWPMaxZoomFOV; + } + + #ifdef AWP_UNZOOM + SetContextThink( &CWeaponAWP::UnzoomThink, gpGlobals->curtime + sv_awpunzoomdelay.GetFloat(), SNIPER_ZOOM_CONTEXT ); + #else + pPlayer->m_bResumeZoom = true; + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), 0.1f ); + m_weaponMode = Primary_Mode; + #endif + } + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= 2; + pPlayer->SetPunchAngle( angle ); +} + +#ifdef AWP_UNZOOM +void CWeaponAWP::UnzoomThink( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if (pPlayer == NULL) + { + Assert(pPlayer != NULL); + return; + } + + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), 0.1f ); +} +#endif + + +float CWeaponAWP::GetMaxSpeed() const +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if (pPlayer == NULL) + { + Assert(pPlayer != NULL); + return BaseClass::GetMaxSpeed(); + } + + if ( pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + { + return BaseClass::GetMaxSpeed(); + } + else + { + // Slower speed when zoomed in. + return 150; + } +} + + +bool CWeaponAWP::IsAwp() const +{ + return true; +} + + +bool CWeaponAWP::Reload() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Reload(); +} + +bool CWeaponAWP::Deploy() +{ + // don't allow weapon switching to shortcut cycle time (quickswitch exploit) + float fOldNextPrimaryAttack = m_flNextPrimaryAttack; + float fOldNextSecondaryAttack = m_flNextSecondaryAttack; + + if ( !BaseClass::Deploy() ) + return false; + + m_weaponMode = Primary_Mode; + m_flNextPrimaryAttack = MAX( m_flNextPrimaryAttack, fOldNextPrimaryAttack ); + m_flNextSecondaryAttack = MAX( m_flNextSecondaryAttack, fOldNextSecondaryAttack ); + return true; +} diff --git a/game/shared/cstrike/weapon_basecsgrenade.cpp b/game/shared/cstrike/weapon_basecsgrenade.cpp new file mode 100644 index 0000000..618a96b --- /dev/null +++ b/game/shared/cstrike/weapon_basecsgrenade.cpp @@ -0,0 +1,438 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "gamerules.h" +#include "npcevent.h" +#include "engine/IEngineSound.h" +#include "weapon_basecsgrenade.h" +#include "in_buttons.h" +#include "datacache/imdlcache.h" + + + +#ifdef CLIENT_DLL + + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "items.h" + #include "../../server/cstrike/cs_gamestats.h" + +#endif + + +#define GRENADE_TIMER 1.5f //Seconds + + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseCSGrenade, DT_BaseCSGrenade ) + +BEGIN_NETWORK_TABLE(CBaseCSGrenade, DT_BaseCSGrenade) + +#ifndef CLIENT_DLL + SendPropBool( SENDINFO(m_bRedraw) ), + SendPropBool( SENDINFO(m_bPinPulled) ), + SendPropFloat( SENDINFO(m_fThrowTime), 0, SPROP_NOSCALE ), +#else + RecvPropBool( RECVINFO(m_bRedraw) ), + RecvPropBool( RECVINFO(m_bPinPulled) ), + RecvPropFloat( RECVINFO(m_fThrowTime) ), +#endif + +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CBaseCSGrenade ) + DEFINE_PRED_FIELD( m_bRedraw, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bRedraw, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_basecsgrenade, CBaseCSGrenade ); + +#ifndef CLIENT_DLL +ConVar sv_ignoregrenaderadio( "sv_ignoregrenaderadio", "0", 0, "Turn off Fire in the hole messages" ); +#endif + +CBaseCSGrenade::CBaseCSGrenade() +{ + m_bRedraw = false; + m_bPinPulled = false; + m_fThrowTime = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCSGrenade::Precache() +{ + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCSGrenade::Deploy() +{ + m_bRedraw = false; + m_bPinPulled = false; + m_fThrowTime = 0; + +#ifndef CLIENT_DLL + // if we're officially out of grenades, ditch this weapon + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + { + pPlayer->Weapon_Drop( this, NULL, NULL ); + UTIL_Remove(this); + return false; + } +#endif + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseCSGrenade::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + m_bRedraw = false; + m_bPinPulled = false; // when this is holstered make sure the pin isn�t pulled. + m_fThrowTime = 0; + +#ifndef CLIENT_DLL + // If they attempt to switch weapons before the throw animation is done, + // allow it, but kill the weapon if we have to. + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + { + CBaseCombatCharacter *pOwner = (CBaseCombatCharacter *)pPlayer; + pOwner->Weapon_Drop( this ); + UTIL_Remove(this); + } +#endif + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCSGrenade::PrimaryAttack() +{ + if ( m_bRedraw || m_bPinPulled || m_fThrowTime > 0.0f ) + return; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer || pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + return; + + // The pull pin animation has to finish, then we wait until they aren't holding the primary + // attack button, then throw the grenade. + SendWeaponAnim( ACT_VM_PULLPIN ); + m_bPinPulled = true; + + // Don't let weapon idle interfere in the middle of a throw! + MDLCACHE_CRITICAL_SECTION(); + SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCSGrenade::SecondaryAttack() +{ + if ( m_bRedraw ) + return; + + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer == NULL ) + return; + + //See if we're ducking + if ( pPlayer->GetFlags() & FL_DUCKING ) + { + //Send the weapon animation + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + } + else + { + //Send the weapon animation + SendWeaponAnim( ACT_VM_HAULBACK ); + } + + // Don't let weapon idle interfere in the middle of a throw! + SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); + + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseCSGrenade::Reload() +{ + if ( ( m_bRedraw ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) ) + { + //Redraw the weapon + SendWeaponAnim( ACT_VM_DRAW ); + + //Update our times + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); + + SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); + + //Mark this as done + // m_bRedraw = false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCSGrenade::ItemPostFrame() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + CBaseViewModel *vm = pPlayer->GetViewModel( m_nViewModelIndex ); + if ( !vm ) + return; + + // If they let go of the fire button, they want to throw the grenade. + if ( m_bPinPulled && !(pPlayer->m_nButtons & IN_ATTACK) ) + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_THROW_GRENADE ); + + StartGrenadeThrow(); + + MDLCACHE_CRITICAL_SECTION(); + m_bPinPulled = false; + SendWeaponAnim( ACT_VM_THROW ); + SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); // we're still throwing, so reset our next primary attack + +#ifndef CLIENT_DLL + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_fire" ); + if( event ) + { + const char *weaponName = STRING( m_iClassname ); + if ( strncmp( weaponName, "weapon_", 7 ) == 0 ) + { + weaponName += 7; + } + + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetString( "weapon", weaponName ); + gameeventmanager->FireEvent( event ); + } +#endif + } + else if ((m_fThrowTime > 0) && (m_fThrowTime < gpGlobals->curtime)) + { + // only decrement our ammo when we actually create the projectile + DecrementAmmo( pPlayer ); + + ThrowGrenade(); + } + else if( m_bRedraw ) + { + // Has the throw animation finished playing + if( m_flTimeWeaponIdle < gpGlobals->curtime ) + { +#ifdef GAME_DLL + // if we're officially out of grenades, ditch this weapon + if( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + { + pPlayer->Weapon_Drop( this, NULL, NULL ); + UTIL_Remove(this); + } + else + { + pPlayer->SwitchToNextBestWeapon( this ); + } +#endif + return; //don't animate this grenade any more! + } + } + else if( !m_bRedraw ) + { + BaseClass::ItemPostFrame(); + } +} + + + +#ifdef CLIENT_DLL + + void CBaseCSGrenade::DecrementAmmo( CBaseCombatCharacter *pOwner ) + { + } + + void CBaseCSGrenade::DropGrenade() + { + m_bRedraw = true; + m_fThrowTime = 0.0f; + } + + void CBaseCSGrenade::ThrowGrenade() + { + m_bRedraw = true; + m_fThrowTime = 0.0f; + } + + void CBaseCSGrenade::StartGrenadeThrow() + { + m_fThrowTime = gpGlobals->curtime + 0.1f; + } + +#else + + BEGIN_DATADESC( CBaseCSGrenade ) + DEFINE_FIELD( m_bRedraw, FIELD_BOOLEAN ), + END_DATADESC() + + int CBaseCSGrenade::CapabilitiesGet() + { + return bits_CAP_WEAPON_RANGE_ATTACK1; + } + + //----------------------------------------------------------------------------- + // Purpose: + // Input : *pOwner - + //----------------------------------------------------------------------------- + void CBaseCSGrenade::DecrementAmmo( CBaseCombatCharacter *pOwner ) + { + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); + } + + void CBaseCSGrenade::StartGrenadeThrow() + { + m_fThrowTime = gpGlobals->curtime + 0.1f; + } + + void CBaseCSGrenade::ThrowGrenade() + { + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( !pPlayer ) + { + Assert( false ); + return; + } + + QAngle angThrow = pPlayer->LocalEyeAngles(); + + Vector vForward, vRight, vUp; + + if ( angThrow.x < 0 ) + { + angThrow.x += 360; // make sure we have a positive angle from LocalEyeAngles() + } + + if ( angThrow.x < 90 ) + angThrow.x = -10 + angThrow.x * ((90 + 10) / 90.0); + else + { + angThrow.x = 360.0f - angThrow.x; + angThrow.x = -10 + angThrow.x * -((90 - 10) / 90.0); + } + + float flVel = (90 - angThrow.x) * 6; + + if (flVel > 750) + flVel = 750; + + AngleVectors( angThrow, &vForward, &vRight, &vUp ); + + Vector vecSrc = pPlayer->GetAbsOrigin() + pPlayer->GetViewOffset(); + + // We want to throw the grenade from 16 units out. But that can cause problems if we're facing + // a thin wall. Do a hull trace to be safe. + trace_t trace; + Vector mins( -2, -2, -2 ); + Vector maxs( 2, 2, 2 ); + UTIL_TraceHull( vecSrc, vecSrc + vForward * 16, mins, maxs, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); + vecSrc = trace.endpos; + + Vector vecThrow = vForward * flVel + pPlayer->GetAbsVelocity(); + + EmitGrenade( vecSrc, vec3_angle, vecThrow, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer ); + + m_bRedraw = true; + m_fThrowTime = 0.0f; + + CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer ); + + if( pCSPlayer ) + { + if ( !sv_ignoregrenaderadio.GetBool() ) + { + pCSPlayer->Radio( "Radio.FireInTheHole", "#Cstrike_TitlesTXT_Fire_in_the_hole" ); + } + CCS_GameStats.IncrementStat(pCSPlayer, CSSTAT_GRENADES_THROWN, 1); + } + } + + void CBaseCSGrenade::DropGrenade() + { + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( !pPlayer ) + { + Assert( false ); + return; + } + + Vector vForward; + pPlayer->EyeVectors( &vForward ); + Vector vecSrc = pPlayer->GetAbsOrigin() + pPlayer->GetViewOffset() + vForward * 16; + + Vector vecVel = pPlayer->GetAbsVelocity(); + + EmitGrenade( vecSrc, vec3_angle, vecVel, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer ); + + CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer ); + + if( pCSPlayer ) + { + CCS_GameStats.IncrementStat(pCSPlayer, CSSTAT_GRENADES_THROWN, 1); + } + + m_bRedraw = true; + m_fThrowTime = 0.0f; + } + + void CBaseCSGrenade::EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ) + { + Assert( 0 && "CBaseCSGrenade::EmitGrenade should not be called. Make sure to implement this in your subclass!\n" ); + } + + bool CBaseCSGrenade::AllowsAutoSwitchFrom( void ) const + { + return !m_bPinPulled; + } + +#endif + diff --git a/game/shared/cstrike/weapon_basecsgrenade.h b/game/shared/cstrike/weapon_basecsgrenade.h new file mode 100644 index 0000000..334e8ab --- /dev/null +++ b/game/shared/cstrike/weapon_basecsgrenade.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_BASECSGRENADE_H +#define WEAPON_BASECSGRENADE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_csbase.h" + + +#ifdef CLIENT_DLL + + #define CBaseCSGrenade C_BaseCSGrenade + +#endif + + +class CBaseCSGrenade : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CBaseCSGrenade, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CBaseCSGrenade(); + + virtual void Precache(); + + bool Deploy(); + bool Holster( CBaseCombatWeapon *pSwitchingTo ); + + void PrimaryAttack(); + void SecondaryAttack(); + +// virtual float GetSpread() const; + + bool Reload(); + + virtual void ItemPostFrame(); + + void DecrementAmmo( CBaseCombatCharacter *pOwner ); + virtual void StartGrenadeThrow(); + virtual void ThrowGrenade(); + virtual void DropGrenade(); + + bool IsPinPulled() const; + bool IsBeingThrown() const { return m_fThrowTime > 0; } + +#ifndef CLIENT_DLL + DECLARE_DATADESC(); + + virtual bool AllowsAutoSwitchFrom( void ) const; + + int CapabilitiesGet(); + + // Each derived grenade class implements this. + virtual void EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ); +#endif + +protected: + CNetworkVar( bool, m_bRedraw ); // Draw the weapon again after throwing a grenade + CNetworkVar( bool, m_bPinPulled ); // Set to true when the pin has been pulled but the grenade hasn't been thrown yet. + CNetworkVar( float, m_fThrowTime ); // the time at which the grenade will be thrown. If this value is 0 then the time hasn't been set yet. + +private: + CBaseCSGrenade( const CBaseCSGrenade & ) {} +}; + + +inline bool CBaseCSGrenade::IsPinPulled() const +{ + return m_bPinPulled; +} + + +#endif // WEAPON_BASECSGRENADE_H diff --git a/game/shared/cstrike/weapon_c4.cpp b/game/shared/cstrike/weapon_c4.cpp new file mode 100644 index 0000000..e75d6a3 --- /dev/null +++ b/game/shared/cstrike/weapon_c4.cpp @@ -0,0 +1,1353 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_c4.h" +#include "in_buttons.h" +#include "cs_gamerules.h" +#include "decals.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "KeyValues.h" +#include "fx_cs_shared.h" +#include "obstacle_pushaway.h" + +#if defined( CLIENT_DLL ) + #include "c_cs_player.h" +#else + #include "cs_player.h" + #include "explode.h" + #include "mapinfo.h" + #include "team.h" + #include "func_bomb_target.h" + #include "vguiscreen.h" + #include "bot.h" + #include "cs_player.h" + #include <KeyValues.h> + +//============================================================================= +// HPE_BEGIN +// [dwenger] Necessary for stats tracking +//============================================================================= +#include "cs_gamestats.h" +#include "cs_achievement_constants.h" +//============================================================================= +// HPE_END +//============================================================================= +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#define BLINK_INTERVAL 2.0 +#define PLANTED_C4_MODEL "models/weapons/w_c4_planted.mdl" +#define HEIST_MODE_C4_TIME 25 + +int g_sModelIndexC4Glow = -1; + +#define WEAPON_C4_ARM_TIME 3.0 + + +#ifdef CLIENT_DLL + +#else + + + LINK_ENTITY_TO_CLASS( planted_c4, CPlantedC4 ); + PRECACHE_REGISTER( planted_c4 ); + + BEGIN_DATADESC( CPlantedC4 ) + DEFINE_FUNCTION( C4Think ) + END_DATADESC() + + + IMPLEMENT_SERVERCLASS_ST( CPlantedC4, DT_PlantedC4 ) + SendPropBool( SENDINFO(m_bBombTicking) ), + SendPropFloat( SENDINFO(m_flC4Blow), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flTimerLength), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flDefuseLength), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flDefuseCountDown), 0, SPROP_NOSCALE ), + END_SEND_TABLE() + + +BEGIN_PREDICTION_DATA( CPlantedC4 ) +END_PREDICTION_DATA() + + + + CUtlVector< CPlantedC4* > g_PlantedC4s; + + + CPlantedC4::CPlantedC4() + { + g_PlantedC4s.AddToTail( this ); + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] No planter initially + m_pPlanter = NULL; + + // [tj] Assume this is the original owner + m_bPlantedAfterPickup = false; + + //============================================================================= + // HPE_END + //============================================================================= + + } + + CPlantedC4::~CPlantedC4() + { + g_PlantedC4s.FindAndRemove( this ); + + int i; + // Kill the control panels + for ( i = m_hScreens.Count(); --i >= 0; ) + { + DestroyVGuiScreen( m_hScreens[i].Get() ); + } + m_hScreens.RemoveAll(); + } + + int CPlantedC4::UpdateTransmitState() + { + return SetTransmitState( FL_EDICT_FULLCHECK ); + } + + int CPlantedC4::ShouldTransmit( const CCheckTransmitInfo *pInfo ) + { + // Terrorists always need this object for the radar + // Everybody needs it for hiding the round timer and showing the planted C4 scenario icon + return FL_EDICT_ALWAYS; + } + + void CPlantedC4::Precache() + { + g_sModelIndexC4Glow = PrecacheModel( "sprites/ledglow.vmt" ); + PrecacheModel( PLANTED_C4_MODEL ); + PrecacheVGuiScreen( "c4_panel" ); + + engine->ForceModelBounds( PLANTED_C4_MODEL, Vector( -7, -13, -3 ), Vector( 9, 12, 11 ) ); + + PrecacheParticleSystem( "bomb_explosion_huge" ); + } + + void CPlantedC4::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) + { + pPanelName = "c4_panel"; + } + + void CPlantedC4::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ) + { + pPanelName = "vgui_screen"; + } + + //----------------------------------------------------------------------------- + // This is called by the base object when it's time to spawn the control panels + //----------------------------------------------------------------------------- + void CPlantedC4::SpawnControlPanels() + { + char buf[64]; + + // FIXME: Deal with dynamically resizing control panels? + + // If we're attached to an entity, spawn control panels on it instead of use + CBaseAnimating *pEntityToSpawnOn = this; + const char *pOrgLL = "controlpanel%d_ll"; + const char *pOrgUR = "controlpanel%d_ur"; + const char *pAttachmentNameLL = pOrgLL; + const char *pAttachmentNameUR = pOrgUR; + + Assert( pEntityToSpawnOn ); + + // Lookup the attachment point... + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel ); + int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nLLAttachmentIndex <= 0) + { + // Try and use my panels then + pEntityToSpawnOn = this; + Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel ); + nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nLLAttachmentIndex <= 0) + return; + } + + Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel ); + int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nURAttachmentIndex <= 0) + { + // Try and use my panels then + Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel ); + nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf); + if (nURAttachmentIndex <= 0) + return; + } + + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + // Compute the screen size from the attachment points... + matrix3x4_t panelToWorld; + pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld ); + + matrix3x4_t worldToPanel; + MatrixInvert( panelToWorld, worldToPanel ); + + // Now get the lower right position + transform into panel space + Vector lr, lrlocal; + pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld ); + MatrixGetColumn( panelToWorld, 3, lr ); + VectorTransform( lr, worldToPanel, lrlocal ); + + float flWidth = lrlocal.x; + float flHeight = lrlocal.y; + + CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex ); + pScreen->ChangeTeam( GetTeamNumber() ); + pScreen->SetActualSize( flWidth, flHeight ); + pScreen->SetActive( true ); + pScreen->MakeVisibleOnlyToTeammates( false ); + int nScreen = m_hScreens.AddToTail( ); + m_hScreens[nScreen].Set( pScreen ); + } + } + + void CPlantedC4::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) + { + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force our screens to be sent too. + for ( int i=0; i < m_hScreens.Count(); i++ ) + { + CVGuiScreen *pScreen = m_hScreens[i].Get(); + pScreen->SetTransmit( pInfo, bAlways ); + } + } + + CPlantedC4* CPlantedC4::ShootSatchelCharge( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles ) + { + CPlantedC4 *pGrenade = dynamic_cast< CPlantedC4* >( CreateEntityByName( "planted_c4" ) ); + if ( pGrenade ) + { + vecAngles[0] = 0; + vecAngles[2] = 0; + pGrenade->Init( pevOwner, vecStart, vecAngles ); + return pGrenade; + } + else + { + Warning( "Can't create planted_c4 entity!\n" ); + return NULL; + } + } + + + void CPlantedC4::Init( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles ) + { + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_NONE ); + + SetModel( PLANTED_C4_MODEL ); // Change this to c4 model + + SetCollisionBounds( Vector( 0, 0, 0 ), Vector( 8, 8, 8 ) ); + + SetAbsOrigin( vecStart ); + SetAbsAngles( vecAngles ); + SetOwnerEntity( pevOwner ); + + //============================================================================= + // HPE_BEGIN: + // [tj] Set the planter when the bomb is planted. + //============================================================================= + + SetPlanter( pevOwner ); + + //============================================================================= + // HPE_END + //============================================================================= + + + // Detonate in "time" seconds + SetThink( &CPlantedC4::C4Think ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + + m_flTimerLength = mp_c4timer.GetInt(); + + m_flC4Blow = gpGlobals->curtime + m_flTimerLength; + m_flNextDefuse = 0; + + m_bStartDefuse = false; + m_bBombTicking = true; + SetFriction( 0.9 ); + + m_flDefuseLength = 0.0f; + + SpawnControlPanels(); + } + + void CPlantedC4::C4Think() + { + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + //Bomb is dead, don't think anymore + if( !m_bBombTicking ) + { + SetThink( NULL ); + return; + } + + + SetNextThink( gpGlobals->curtime + 0.12 ); + +#ifndef CLIENT_DLL + // let the bots hear the bomb beeping + // BOTPORT: Emit beep events at same time as client effects + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_beep" ); + if( event ) + { + event->SetInt( "entindex", entindex() ); + gameeventmanager->FireEvent( event ); + } +#endif + + // IF the timer has expired ! blow this bomb up! + if (m_flC4Blow <= gpGlobals->curtime) + { + // give the defuser credit for defusing the bomb + CCSPlayer* pBombOwner = ToCSPlayer(GetOwnerEntity()); + if ( pBombOwner ) + { + if (CSGameRules()->m_iRoundWinStatus == WINNER_NONE) + pBombOwner->IncrementFragCount( 3 ); + } + + CSGameRules()->m_bBombDropped = false; + + trace_t tr; + Vector vecSpot = GetAbsOrigin(); + vecSpot[2] += 8; + + UTIL_TraceLine( vecSpot, vecSpot + Vector ( 0, 0, -40 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + Explode( &tr, DMG_BLAST ); + + CSGameRules()->m_bBombPlanted = false; + + CCS_GameStats.Event_BombExploded(pBombOwner); + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_exploded" ); + if( event ) + { + event->SetInt( "userid", pBombOwner?pBombOwner->GetUserID():-1 ); + event->SetInt( "site", m_iBombSiteIndex ); + event->SetInt( "priority", 9 ); + gameeventmanager->FireEvent( event ); + } + + // skip additional processing once the bomb has exploded + return; + } + + //if the defusing process has started + if ((m_bStartDefuse == true) && (m_pBombDefuser != NULL)) + { + //if the defusing process has not ended yet + if ( m_flDefuseCountDown > gpGlobals->curtime) + { + int iOnGround = FBitSet( m_pBombDefuser->GetFlags(), FL_ONGROUND ); + + //if the bomb defuser has stopped defusing the bomb + if( m_flNextDefuse < gpGlobals->curtime || !iOnGround ) + { + if ( !iOnGround && m_pBombDefuser->IsAlive() ) + ClientPrint( m_pBombDefuser, HUD_PRINTCENTER, "#C4_Defuse_Must_Be_On_Ground"); + + // release the player from being frozen + m_pBombDefuser->m_bIsDefusing = false; + +#ifndef CLIENT_DLL + // tell the bots someone has aborted defusing + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortdefuse" ); + if( event ) + { + event->SetInt("userid", m_pBombDefuser->GetUserID() ); + event->SetInt( "priority", 6 ); + gameeventmanager->FireEvent( event ); + } +#endif + + //cancel the progress bar + m_pBombDefuser->SetProgressBarTime( 0 ); + m_pBombDefuser->OnCanceledDefuse(); + m_pBombDefuser = NULL; + m_bStartDefuse = false; + m_flDefuseCountDown = 0; + m_flDefuseLength = 0; //force it to show completely defused + } + + return; + } + + //if the defuse process has ended, kill the c4 + if ( !m_pBombDefuser->IsDead() ) + { + //============================================================================= + // HPE_BEGIN + // [dwenger] Stats update for bomb defusing + //============================================================================= + CCS_GameStats.Event_BombDefused( m_pBombDefuser ); + //============================================================================= + // HPE_END + //============================================================================= + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_defused" ); + if( event ) + { + event->SetInt("userid", m_pBombDefuser->GetUserID() ); + event->SetInt("site", m_iBombSiteIndex ); + event->SetInt( "priority", 9 ); + gameeventmanager->FireEvent( event ); + + //============================================================================= + // HPE_BEGIN + // [dwenger] Server-side processing for defusing bombs + //============================================================================= + m_pBombDefuser->AwardAchievement(CSWinBombDefuse); + + float timeToDetonation = (m_flC4Blow - gpGlobals->curtime); + + if ((timeToDetonation > 0.0f) && (timeToDetonation <= AchievementConsts::BombDefuseCloseCall_MaxTimeRemaining)) + { + // Give achievement for defusing with < 1 second before detonation + m_pBombDefuser->AwardAchievement(CSBombDefuseCloseCall); + } + + if ((timeToDetonation > 0.0f) && (m_pBombDefuser->HasDefuser()) && (timeToDetonation < AchievementConsts::BombDefuseNeededKit_MaxTime)) + { + // Give achievement for defusing with a defuse kit when not having the kit would have taken too long + m_pBombDefuser->AwardAchievement(CSDefuseAndNeededKit); + } + + // [dwenger] Added for fun-fact support + if ( m_pBombDefuser->PickedUpDefuser() ) + { + // Defuser kit was picked up, so set the fun fact + m_pBombDefuser->SetDefusedWithPickedUpKit(true); + } + + //============================================================================= + // HPE_END + //============================================================================= + } + + + Vector soundPosition = m_pBombDefuser->GetAbsOrigin() + Vector( 0, 0, 5 ); + CPASAttenuationFilter filter( soundPosition ); + + EmitSound( filter, entindex(), "c4.disarmfinish" ); + + // The bomb has just been disarmed.. Check to see if the round should end now + m_bBombTicking = false; + + // release the player from being frozen + m_pBombDefuser->m_bIsDefusing = false; + + CSGameRules()->m_bBombDefused = true; + //============================================================================= + // HPE_BEGIN: + // [menglish] Give the bomb defuser an mvp if they ended the round + //============================================================================= + bool roundWasAlreadyWon = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE); + + if(CSGameRules()->CheckWinConditions() && !roundWasAlreadyWon) + { + m_pBombDefuser->IncrementNumMVPs( CSMVP_BOMBDEFUSE ); + } + //============================================================================= + // HPE_END + //============================================================================= + + // give the defuser credit for defusing the bomb + m_pBombDefuser->IncrementFragCount( 3 ); + + CSGameRules()->m_bBombDropped = false; + CSGameRules()->m_bBombPlanted = false; + + // Clear their progress bar. + m_pBombDefuser->SetProgressBarTime( 0 ); + + m_pBombDefuser = NULL; + m_bStartDefuse = false; + + m_flDefuseLength = 10; + + return; + } + + //if it gets here then the previouse defuser has taken off or been killed + +#ifndef CLIENT_DLL + // tell the bots someone has aborted defusing + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortdefuse" ); + if ( event ) + { + event->SetInt("userid", m_pBombDefuser->GetUserID() ); + event->SetInt( "priority", 6 ); + gameeventmanager->FireEvent( event ); + } +#endif + + // release the player from being frozen + m_pBombDefuser->m_bIsDefusing = false; + m_bStartDefuse = false; + m_pBombDefuser = NULL; + } + } + + // Regular explosions + void CPlantedC4::Explode( trace_t *pTrace, int bitsDamageType ) + { + // Check to see if the round is over after the bomb went off... + CSGameRules()->m_bTargetBombed = true; + m_bBombTicking = false; + //============================================================================= + // HPE_BEGIN: + // [tj] Saving off this value so we can see if the detonation is what caused the round to end. + //============================================================================= + bool roundWasAlreadyWon = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE); + //============================================================================= + // HPE_END + //============================================================================= + + bool bWin = CSGameRules()->CheckWinConditions(); + + //============================================================================= + // HPE_BEGIN + //============================================================================= + + // [dwenger] Server-side processing for winning round by planting a bomb + if (bWin) + { + CCSPlayer *pBombOwner = ToCSPlayer( GetOwnerEntity() ); + if ( pBombOwner ) + { + pBombOwner->AwardAchievement(CSWinBombPlant); + + //[tj]more specific achievement for planting the bomb after recovering it. + if (m_bPlantedAfterPickup) + { + pBombOwner->AwardAchievement(CSWinBombPlantAfterRecovery); + } + // [menglish] awarding mvp to bomb planter + if (!roundWasAlreadyWon) + { + pBombOwner->IncrementNumMVPs( CSMVP_BOMBPLANT ); + } + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + // Do the Damage + float flBombRadius = 500; + if ( g_pMapInfo ) + flBombRadius = g_pMapInfo->m_flBombRadius; + + // Output to the bomb target ent + CBaseEntity *pTarget = NULL; + variant_t emptyVariant; + while ((pTarget = gEntList.FindEntityByClassname( pTarget, "func_bomb_target" )) != NULL) + { + //Adrian - But only to the one we want! + if ( pTarget->entindex() != m_iBombSiteIndex ) + continue; + + pTarget->AcceptInput( "BombExplode", this, this, emptyVariant, 0 ); + break; + } + + // Pull out of the wall a bit + if ( pTrace->fraction != 1.0 ) + { + SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) ); + } + + { + Vector pos = GetAbsOrigin() + Vector( 0,0,8 ); + + // add an explosion TE so it affects clientside physics + CPASFilter filter( pos ); + te->Explosion( filter, 0.0, + &pos, + g_sModelIndexFireball, + 50.0, + 25, + TE_EXPLFLAG_NONE, + flBombRadius * 3.5, + 200 ); + } + + // Sound! for everyone + CBroadcastRecipientFilter filter; + EmitSound( filter, entindex(), "c4.explode" ); + + + // Decal! + UTIL_DecalTrace( pTrace, "Scorch" ); + + + // Shake! + UTIL_ScreenShake( pTrace->endpos, 25.0, 150.0, 1.0, 3000, SHAKE_START ); + + + SetOwnerEntity( NULL ); // can't traceline attack owner if this is set + + CSGameRules()->RadiusDamage( + CTakeDamageInfo( this, GetOwnerEntity(), flBombRadius, bitsDamageType ), + GetAbsOrigin(), + flBombRadius * 3.5, //Matt - don't ask me, this is how CS does it. + CLASS_NONE, + true ); // IGNORE THE WORLD!! + + // send director message, that something important happed here + /* + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // bomb explode + WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity + WRITE_SHORT( 0 ); // index number of secondary entity + WRITE_LONG( 15 | DRC_FLAG_FINAL ); // eventflags (priority and flags) + MESSAGE_END(); + */ + } + + + // For CTs to defuse the c4 + void CPlantedC4::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + //Can't defuse if its already defused or if it has blown up + if( !m_bBombTicking ) + { + SetUse( NULL ); + return; + } + + CCSPlayer *player = dynamic_cast< CCSPlayer* >( pActivator ); + + if ( !player || player->GetTeamNumber() != TEAM_CT ) + return; + + if ( m_bStartDefuse ) + { + if ( player != m_pBombDefuser ) + { + if ( player->m_iNextTimeCheck < gpGlobals->curtime ) + { + ClientPrint( player, HUD_PRINTCENTER, "#Bomb_Already_Being_Defused" ); + player->m_iNextTimeCheck = gpGlobals->curtime + 1; + } + return; + } + + m_flNextDefuse = gpGlobals->curtime + 0.5; + } + else + { + // freeze the player in place while defusing + + IGameEvent * event = gameeventmanager->CreateEvent("bomb_begindefuse" ); + if( event ) + { + event->SetInt( "userid", player->GetUserID() ); + if ( player->HasDefuser() ) + { + event->SetInt( "haskit", 1 ); + // TODO show messages on clients on event + ClientPrint( player, HUD_PRINTCENTER, "#Defusing_Bomb_With_Defuse_Kit" ); + } + else + { + event->SetInt( "haskit", 0 ); + // TODO show messages on clients on event + ClientPrint( player, HUD_PRINTCENTER, "#Defusing_Bomb_Without_Defuse_Kit" ); + } + event->SetInt( "priority", 8 ); + gameeventmanager->FireEvent( event ); + } + + Vector soundPosition = player->GetAbsOrigin() + Vector( 0, 0, 5 ); + CPASAttenuationFilter filter( soundPosition ); + + EmitSound( filter, entindex(), "c4.disarmstart" ); + + m_flDefuseLength = player->HasDefuser() ? 5 : 10; + + + m_flNextDefuse = gpGlobals->curtime + 0.5; + m_pBombDefuser = player; + m_bStartDefuse = TRUE; + player->m_bIsDefusing = true; + + m_flDefuseCountDown = gpGlobals->curtime + m_flDefuseLength; + + //start the progress bar + player->SetProgressBarTime( m_flDefuseLength ); + + + player->OnStartedDefuse(); + } + } + + +#endif + + + +// -------------------------------------------------------------------------------- // +// Tables. +// -------------------------------------------------------------------------------- // + +IMPLEMENT_NETWORKCLASS_ALIASED( C4, DT_WeaponC4 ) + +BEGIN_NETWORK_TABLE( CC4, DT_WeaponC4 ) + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bStartedArming ) ), + RecvPropBool( RECVINFO( m_bBombPlacedAnimation ) ), + RecvPropFloat( RECVINFO( m_fArmedTime ) ) + #else + SendPropBool( SENDINFO( m_bStartedArming ) ), + SendPropBool( SENDINFO( m_bBombPlacedAnimation ) ), + SendPropFloat( SENDINFO( m_fArmedTime ), 0, SPROP_NOSCALE ) + #endif +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CC4 ) + DEFINE_PRED_FIELD( m_bStartedArming, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bBombPlacedAnimation, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_fArmedTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ) +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_c4, CC4 ); +PRECACHE_WEAPON_REGISTER( weapon_c4 ); + + + +// -------------------------------------------------------------------------------- // +// Globals. +// -------------------------------------------------------------------------------- // + +CUtlVector< CC4* > g_C4s; + + + +// -------------------------------------------------------------------------------- // +// CC4 implementation. +// -------------------------------------------------------------------------------- // + +CC4::CC4() +{ + g_C4s.AddToTail( this ); + m_bDroppedFromDeath = false; + +#if defined( CLIENT_DLL ) + m_szScreenText[0] = '\0'; +#endif + +} + + +CC4::~CC4() +{ + g_C4s.FindAndRemove( this ); +} + +void CC4::Spawn() +{ + BaseClass::Spawn(); + + //Don't allow players to shoot the C4 around + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + + //Don't be damaged / moved by explosions + m_takedamage = DAMAGE_NO; + + m_bBombPlanted = false; +} + +void CC4::ItemPostFrame() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Disable all the firing code.. the C4 grenade is all custom. + if ( pPlayer->m_nButtons & IN_ATTACK ) + { + PrimaryAttack(); + } + else + { + WeaponIdle(); + } +} + +#if defined( CLIENT_DLL ) + + bool CC4::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) + { + if( event == 7001 ) + { + //set the screen text to the string in 'options' + Q_strncpy( m_szScreenText, options, 16 ); + + return true; + } + return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); + } + + char *CC4::GetScreenText( void ) + { + if( m_bStartedArming ) + return m_szScreenText; + else + return ""; + } + +#endif //CLIENT_DLL + +#ifdef GAME_DLL + + + unsigned int CC4::PhysicsSolidMaskForEntity( void ) const + { + return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_PLAYERCLIP; + } + + void CC4::Precache() + { + PrecacheVGuiScreen( "c4_view_panel" ); + + PrecacheScriptSound( "c4.disarmfinish" ); + PrecacheScriptSound( "c4.explode" ); + PrecacheScriptSound( "c4.disarmstart" ); + PrecacheScriptSound( "c4.plant" ); + PrecacheScriptSound( "C4.PlantSound" ); + + BaseClass::Precache(); + } + + //----------------------------------------------------------------------------- + // Purpose: Gets info about the control panels + //----------------------------------------------------------------------------- + void CC4::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) + { + pPanelName = "c4_view_panel"; + } + + bool CC4::Holster( CBaseCombatWeapon *pSwitchingTo ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer ) + pPlayer->SetProgressBarTime( 0 ); + + if ( m_bStartedArming ) + { + AbortBombPlant(); + } + + return BaseClass::Holster( pSwitchingTo ); + } + + + bool CC4::ShouldRemoveOnRoundRestart() + { + // Doesn't matter if we have an owner or not.. always remove the C4 when the round restarts. + // The gamerules will give another C4 to some lucky player. + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer && pPlayer->GetActiveWeapon() == this ) + engine->ClientCommand( pPlayer->edict(), "lastinv reset\n" ); + return true; + } + +#endif + + +void CC4::PrimaryAttack() +{ + bool bArmingTimeSatisfied = false; + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + int onGround = FBitSet( pPlayer->GetFlags(), FL_ONGROUND ); + CBaseEntity *groundEntity = (onGround) ? pPlayer->GetGroundEntity() : NULL; + if ( groundEntity ) + { + // Don't let us stand on players, breakables, or pushaway physics objects to plant + if ( groundEntity->IsPlayer() || + IsPushableEntity( groundEntity ) || +#ifndef CLIENT_DLL + IsBreakableEntity( groundEntity ) || +#endif // !CLIENT_DLL + IsPushAwayEntity( groundEntity ) ) + { + onGround = false; + } + } + + if( m_bStartedArming == false && m_bBombPlanted == false ) + { + if( pPlayer->m_bInBombZone && onGround ) + { + m_bStartedArming = true; + m_fArmedTime = gpGlobals->curtime + WEAPON_C4_ARM_TIME; + m_bBombPlacedAnimation = false; + + +#if !defined( CLIENT_DLL ) + // init the beep flags + int i; + for( i=0;i<NUM_BEEPS;i++ ) + m_bPlayedArmingBeeps[i] = false; + + // freeze the player in place while planting + + // player "arming bomb" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + pPlayer->SetNextAttack( gpGlobals->curtime ); + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_beginplant" ); + if( event ) + { + event->SetInt("userid", pPlayer->GetUserID() ); + event->SetInt("site", pPlayer->m_iBombSiteIndex ); + event->SetInt( "priority", 8 ); + gameeventmanager->FireEvent( event ); + } +#endif + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + FX_PlantBomb( pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), PLANTBOMB_PLANT ); + } + else + { + if ( !pPlayer->m_bInBombZone ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#C4_Plant_At_Bomb_Spot"); + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#C4_Plant_Must_Be_On_Ground"); + } + + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + return; + } + } + else + { + if ( !onGround || !pPlayer->m_bInBombZone ) + { + if( !pPlayer->m_bInBombZone ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#C4_Arming_Cancelled" ); + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#C4_Plant_Must_Be_On_Ground" ); + } + + AbortBombPlant(); + + if(m_bBombPlacedAnimation == true) //this means the placement animation is canceled + { + SendWeaponAnim( ACT_VM_DRAW ); + } + else + { + SendWeaponAnim( ACT_VM_IDLE ); + } + + return; + } + else + { +#ifndef CLIENT_DLL + PlayArmingBeeps(); +#endif + + if( gpGlobals->curtime >= m_fArmedTime ) //the c4 is ready to be armed + { + //check to make sure the player is still in the bomb target area + bArmingTimeSatisfied = true; + } + else if( ( gpGlobals->curtime >= (m_fArmedTime - 0.75) ) && ( !m_bBombPlacedAnimation ) ) + { + //call the c4 Placement animation + m_bBombPlacedAnimation = true; + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + +#if !defined( CLIENT_DLL ) + // player "place" animation + //pPlayer->SetAnimation( PLAYER_HOLDBOMB ); +#endif + } + } + } + + if ( bArmingTimeSatisfied && m_bStartedArming ) + { + m_bStartedArming = false; + m_fArmedTime = 0; + + if( pPlayer->m_bInBombZone ) + { +#if !defined( CLIENT_DLL ) + CPlantedC4 *pC4 = CPlantedC4::ShootSatchelCharge( pPlayer, pPlayer->GetAbsOrigin(), pPlayer->GetAbsAngles() ); + + if ( pC4 ) + { + pC4->SetBombSiteIndex( pPlayer->m_iBombSiteIndex ); + + trace_t tr; + UTIL_TraceEntity( pC4, GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-200), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + pC4->SetAbsOrigin( tr.endpos ); + + CBombTarget *pBombTarget = (CBombTarget*)UTIL_EntityByIndex( pPlayer->m_iBombSiteIndex ); + + if ( pBombTarget ) + { + CBaseEntity *pAttachPoint = gEntList.FindEntityByName( NULL, pBombTarget->GetBombMountTarget() ); + + if ( pAttachPoint ) + { + pC4->SetAbsOrigin( pAttachPoint->GetAbsOrigin() ); + pC4->SetAbsAngles( pAttachPoint->GetAbsAngles() ); + pC4->SetParent( pAttachPoint ); + } + + variant_t emptyVariant; + pBombTarget->AcceptInput( "BombPlanted", pC4, pC4, emptyVariant, 0 ); + } + + // [tj] If the bomb is planted by someone that picked it up after the + // original owner was killed, pass that along to the planted bomb + pC4->SetPlantedAfterPickup( m_bDroppedFromDeath ); + } + + //============================================================================= + // HPE_BEGIN + // [dwenger] Stats update for bomb planting + //============================================================================= + + // Determine how elapsed time from start of round until the bomb was planted + float plantingTime = gpGlobals->curtime - CSGameRules()->GetRoundStartTime(); + + // Award achievement to bomb planter if time <= 25 seconds + if ((plantingTime > 0.0f) && (plantingTime <= AchievementConsts::FastBombPlant_Time)) + { + pPlayer->AwardAchievement(CSPlantBombWithin25Seconds); + } + + CCS_GameStats.Event_BombPlanted( pPlayer ); + + //============================================================================= + // HPE_END + //============================================================================= + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_planted" ); + if( event ) + { + event->SetInt("userid", pPlayer->GetUserID() ); + event->SetInt("site", pPlayer->m_iBombSiteIndex ); + event->SetInt("posx", pPlayer->GetAbsOrigin().x ); + event->SetInt("posy", pPlayer->GetAbsOrigin().y ); + event->SetInt( "priority", 8 ); + gameeventmanager->FireEvent( event ); + } + + // Fire a beep event also so the bots have a chance to hear the bomb + event = gameeventmanager->CreateEvent( "bomb_beep" ); + + if ( event ) + { + event->SetInt( "entindex", entindex() ); + gameeventmanager->FireEvent( event ); + } + + pPlayer->SetProgressBarTime( 0 ); + + CSGameRules()->m_bBombDropped = false; + CSGameRules()->m_bBombPlanted = true; + + // Play the plant sound. + Vector plantPosition = pPlayer->GetAbsOrigin() + Vector( 0, 0, 5 ); + CPASAttenuationFilter filter( plantPosition ); + EmitSound( filter, entindex(), "c4.plant" ); + + // No more c4! + pPlayer->Weapon_Drop( this, NULL, NULL ); + UTIL_Remove( this ); +#endif + + //don't allow the planting to start over again next frame. + m_bBombPlanted = true; + + return; + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#C4_Activated_At_Bomb_Spot" ); + +#if !defined( CLIENT_DLL ) + //pPlayer->SetAnimation( PLAYER_HOLDBOMB ); + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortplant" ); + if( event ) + { + event->SetInt("userid", pPlayer->GetUserID() ); + event->SetInt("site", pPlayer->m_iBombSiteIndex ); + event->SetInt( "priority", 8 ); + gameeventmanager->FireEvent( event ); + } +#endif + + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + return; + } + } + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; + SetWeaponIdleTime( gpGlobals->curtime + SharedRandomFloat("C4IdleTime", 10, 15 ) ); +} + +void CC4::WeaponIdle() +{ + // if the player releases the attack button cancel the arming sequence + if ( m_bStartedArming ) + { + AbortBombPlant(); + + CCSPlayer *pPlayer = GetPlayerOwner(); + + // TODO: make this use SendWeaponAnim and activities when the C4 has the activities hooked up. + if ( pPlayer ) + { + SendWeaponAnim( ACT_VM_IDLE ); + pPlayer->SetNextAttack( gpGlobals->curtime ); + } + + if(m_bBombPlacedAnimation == true) //this means the placement animation is canceled + SendWeaponAnim( ACT_VM_DRAW ); + else + SendWeaponAnim( ACT_VM_IDLE ); + } +} + +void CC4::UpdateShieldState( void ) +{ + //ADRIANTODO + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( pPlayer->HasShield() ) + { + pPlayer->SetShieldDrawnState( false ); + + CBaseViewModel *pVM = pPlayer->GetViewModel( 1 ); + + if ( pVM ) + { + pVM->AddEffects( EF_NODRAW ); + } + //pPlayer->SetHitBoxSet( 3 ); + } + else + BaseClass::UpdateShieldState(); +} + + +int m_iBeepFrames[NUM_BEEPS] = { 27, 37, 45, 51, 57, 63, 67 }; +int iNumArmingAnimFrames = 83; + +void CC4::PlayArmingBeeps( void ) +{ + float flStartTime = m_fArmedTime - WEAPON_C4_ARM_TIME; + + float flProgress = ( gpGlobals->curtime - flStartTime ) / ( WEAPON_C4_ARM_TIME - 0.75 ); + + int currentFrame = (int)( (float)iNumArmingAnimFrames * flProgress ); + + int i; + for( i=0;i<NUM_BEEPS;i++ ) + { + if( currentFrame <= m_iBeepFrames[i] ) + { + break; + } + else if( !m_bPlayedArmingBeeps[i] ) + { + m_bPlayedArmingBeeps[i] = true; + + CCSPlayer *owner = GetPlayerOwner(); + Vector soundPosition = owner->GetAbsOrigin() + Vector( 0, 0, 5 ); + CPASAttenuationFilter filter( soundPosition ); + + filter.RemoveRecipient( owner ); + + // remove anyone that is first person spec'ing the planter + int i; + CBasePlayer *pPlayer; + for( i=1;i<=gpGlobals->maxClients;i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if( pPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pPlayer->GetObserverTarget() == GetOwner() ) + { + filter.RemoveRecipient( pPlayer ); + } + } + + EmitSound(filter, entindex(), "c4.click"); + + break; + } + } +} + +float CC4::GetMaxSpeed() const +{ + if ( m_bStartedArming ) + return CS_PLAYER_SPEED_STOPPED; + else + return BaseClass::GetMaxSpeed(); +} + + +void CC4::OnPickedUp( CBaseCombatCharacter *pNewOwner ) +{ + BaseClass::OnPickedUp( pNewOwner ); + +#if !defined( CLIENT_DLL ) + CCSPlayer *pPlayer = dynamic_cast<CCSPlayer *>( pNewOwner ); + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_pickup" ); + if ( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetInt( "priority", 6 ); + gameeventmanager->FireEvent( event ); + } + + if ( pPlayer->m_bShowHints && !(pPlayer->m_iDisplayHistoryBits & DHF_BOMB_RETRIEVED) ) + { + pPlayer->m_iDisplayHistoryBits |= DHF_BOMB_RETRIEVED; + pPlayer->HintMessage( "#Hint_you_have_the_bomb", false ); + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#Got_bomb" ); + } + + pPlayer->SetBombPickupTime(gpGlobals->curtime); +#endif +} + +// HACK - Ask Mike Booth... +#ifndef CLIENT_DLL + #include "cs_bot.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void CC4::Drop( const Vector &vecVelocity ) +{ +#if !defined( CLIENT_DLL ) + if ( !CSGameRules()->m_bBombPlanted ) // its not dropped if its planted + { + // tell the bots about the dropped bomb + TheCSBots()->SetLooseBomb( this ); + + CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(GetOwnerEntity()); + Assert( pPlayer ); + if ( pPlayer ) + { + IGameEvent * event = gameeventmanager->CreateEvent("bomb_dropped" ); + if ( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetInt( "priority", 6 ); + gameeventmanager->FireEvent( event ); + } + } + } +#endif + + if ( m_bStartedArming ) + AbortBombPlant(); // stop arming sequence + + BaseClass::Drop( vecVelocity ); +} + +void CC4::AbortBombPlant() +{ + m_bStartedArming = false; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + +#if !defined( CLIENT_DLL ) + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + + pPlayer->SetProgressBarTime( 0 ); + + IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortplant" ); + if( event ) + { + event->SetInt("userid", pPlayer->GetUserID() ); + event->SetInt("site", pPlayer->m_iBombSiteIndex ); + event->SetInt( "priority", 8 ); + gameeventmanager->FireEvent( event ); + } + +#endif + + FX_PlantBomb( pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), PLANTBOMB_ABORT ); +}
\ No newline at end of file diff --git a/game/shared/cstrike/weapon_c4.h b/game/shared/cstrike/weapon_c4.h new file mode 100644 index 0000000..7aa50a7 --- /dev/null +++ b/game/shared/cstrike/weapon_c4.h @@ -0,0 +1,221 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_C4_H +#define WEAPON_C4_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_csbase.h" +#include "utlvector.h" + +#define NUM_BEEPS 7 + +#if defined( CLIENT_DLL ) + + #define CC4 C_C4 + +#else + + // ------------------------------------------------------------------------------------------ // + // CPlantedC4 class. + // ------------------------------------------------------------------------------------------ // + + class CPlantedC4 : public CBaseAnimating + { + public: + DECLARE_CLASS( CPlantedC4, CBaseAnimating ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + DECLARE_PREDICTABLE(); + + CPlantedC4(); + virtual ~CPlantedC4(); + + virtual int UpdateTransmitState(); + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ); + + static CPlantedC4* ShootSatchelCharge( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles ); + virtual void Precache(); + + // Set these flags so CTs can use the C4 to disarm it. + virtual int ObjectCaps() { return BaseClass::ObjectCaps() | (FCAP_CONTINUOUS_USE | FCAP_USE_IN_RADIUS); } + + void SetBombSiteIndex( int iIndex ){ m_iBombSiteIndex = iIndex; } + + inline bool IsBombActive( void ) { return m_bBombTicking; } + + //============================================================================= + // HPE_BEGIN: + // [tj] Accessors related to planting of the bomb + //============================================================================= + + CCSPlayer* GetPlanter() { return m_pPlanter; } + void SetPlanter(CCSPlayer* player) { m_pPlanter = player; } + + void SetPlantedAfterPickup (bool plantedAfterPickup) { m_bPlantedAfterPickup = plantedAfterPickup; } + + //============================================================================= + // HPE_END + //============================================================================= + + + public: + + CNetworkVar( bool, m_bBombTicking ); + CNetworkVar( float, m_flC4Blow ); + + private: + + void Init( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles ); + void C4Think(); + + // This becomes the think function when the timer has expired and it is about to explode. + void DetonateThink(); + void Explode( trace_t *pTrace, int bitsDamageType ); + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Replicate timer length to the client for effects + CNetworkVar( float, m_flTimerLength ); + + // Info for defusing. + CHandle<CCSPlayer> m_pBombDefuser; + float m_flNextDefuse; + bool m_bStartDefuse; + int m_iBombSiteIndex; + + CNetworkVar( float, m_flDefuseLength ); //How long does the defuse take? Depends on if a defuser was used + CNetworkVar( float, m_flDefuseCountDown ); //What time does the defuse complete? + + // Control panel + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ); + void SpawnControlPanels(); + + typedef CHandle<CVGuiScreen> ScreenHandle_t; + CUtlVector<ScreenHandle_t> m_hScreens; + + int m_iProgressBarTime; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] We need to store who planted the bomb so we can track who deserves credits for the kills + CHandle<CCSPlayer> m_pPlanter; + + // [tj] We need to know if this was planted by a player who recovered the bomb + bool m_bPlantedAfterPickup; + + //============================================================================= + // HPE_END + //============================================================================= + + }; + + extern CUtlVector< CPlantedC4* > g_PlantedC4s; + +#endif + +#define WEAPON_C4_CLASSNAME "weapon_c4" +#define PLANTED_C4_CLASSNAME "planted_c4" + +class CC4 : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CC4, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CC4(); + virtual ~CC4(); + + virtual void Spawn(); + + bool IsPistol() const; + void ItemPostFrame(); + virtual void PrimaryAttack(); + virtual void WeaponIdle(); + virtual void UpdateShieldState( void ); + virtual float GetMaxSpeed() const; + +// virtual float GetSpread() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_C4; } + + #ifdef CLIENT_DLL + + virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); + char *GetScreenText( void ); + char m_szScreenText[32]; + + #else + + virtual void Precache(); + virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + virtual unsigned int PhysicsSolidMaskForEntity( void ) const; + + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); + virtual bool ShouldRemoveOnRoundRestart(); + + //============================================================================= + // HPE_BEGIN: + // [tj] Simple Setter + //============================================================================= + + void SetDroppedFromDeath(bool droppedFromDeath) { m_bDroppedFromDeath = droppedFromDeath; } + + //============================================================================= + // HPE_END + //============================================================================= + + + #endif + + void AbortBombPlant(); + + void PlayArmingBeeps( void ); + virtual void OnPickedUp( CBaseCombatCharacter *pNewOwner ); + virtual void Drop( const Vector &vecVelocity ); + + CNetworkVar( bool, m_bStartedArming ); + CNetworkVar( float, m_fArmedTime ); + CNetworkVar( bool, m_bBombPlacedAnimation ); + + virtual bool IsRemoveable( void ) { return false; } + +private: + bool m_bPlayedArmingBeeps[NUM_BEEPS]; + bool m_bBombPlanted; + + //============================================================================= + // HPE_BEGIN: + // [tj] we want to store if this bomb was dropped because the original owner was killed + //============================================================================= + + bool m_bDroppedFromDeath; + + //============================================================================= + // HPE_END + //============================================================================= + + +private: + + CC4( const CC4 & ); +}; + + +// All the currently-active C4 grenades. +extern CUtlVector< CC4* > g_C4s; + + +#endif // WEAPON_C4_H diff --git a/game/shared/cstrike/weapon_csbase.cpp b/game/shared/cstrike/weapon_csbase.cpp new file mode 100644 index 0000000..8063b91 --- /dev/null +++ b/game/shared/cstrike/weapon_csbase.cpp @@ -0,0 +1,1917 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Laser Rifle & Shield combo +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "in_buttons.h" +#include "takedamageinfo.h" +#include "weapon_csbase.h" +#include "ammodef.h" +#include "cs_gamerules.h" + +#define ALLOW_WEAPON_SPREAD_DISPLAY 0 + +#if defined( CLIENT_DLL ) + + #include "vgui/ISurface.h" + #include "vgui_controls/Controls.h" + #include "c_cs_player.h" + #include "hud_crosshair.h" + #include "c_te_effect_dispatch.h" + #include "c_te_legacytempents.h" + + extern IVModelInfoClient* modelinfo; + +#else + + #include "cs_player.h" + #include "te_effect_dispatch.h" + #include "KeyValues.h" + #include "cs_ammodef.h" + + extern IVModelInfo* modelinfo; + +#endif + + +ConVar weapon_accuracy_model( "weapon_accuracy_model", "2", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE ); + + +// ----------------------------------------------------------------------------- // +// Global functions. +// ----------------------------------------------------------------------------- // + + + +struct WeaponAliasTranslationInfoStruct +{ + const char* alias; + const char* translatedAlias; +}; + +static const WeaponAliasTranslationInfoStruct s_WeaponAliasTranslationInfo[] = +{ + { "cv47", "ak47" }, + { "defender", "galil" }, + { "krieg552", "sg552" }, + { "magnum", "awp" }, + { "d3au1", "g3sg1" }, + { "clarion", "famas" }, + { "bullpup", "aug" }, + { "krieg550", "sg550" }, + { "9x19mm", "glock" }, + { "km45", "usp" }, + { "228compact", "p228" }, + { "nighthawk", "deagle" }, + { "elites", "elite" }, + { "fn57", "fiveseven" }, + { "12gauge", "m3" }, + { "autoshotgun", "xm1014" }, + { "mp", "tmp" }, + { "smg", "mp5navy" }, + { "mp5", "mp5navy" }, + { "c90", "p90" }, + { "vest", "kevlar" }, + { "vesthelm", "assaultsuit" }, + { "smokegrenade", "sgren" }, + { "smokegrenade", "sgren" }, + { "nvgs", "nightvision" }, + + { "", "" } // this needs to be last +}; + + +struct WeaponAliasInfo +{ + CSWeaponID id; + const char* alias; +}; + +WeaponAliasInfo s_weaponAliasInfo[] = +{ + { WEAPON_P228, "p228" }, + { WEAPON_GLOCK, "glock" }, + { WEAPON_SCOUT, "scout" }, + { WEAPON_XM1014, "xm1014" }, + { WEAPON_MAC10, "mac10" }, + { WEAPON_AUG, "aug" }, + { WEAPON_ELITE, "elite" }, + { WEAPON_FIVESEVEN, "fiveseven" }, + { WEAPON_UMP45, "ump45" }, + { WEAPON_SG550, "sg550" }, + { WEAPON_GALIL, "galil" }, + { WEAPON_FAMAS, "famas" }, + { WEAPON_USP, "usp" }, + { WEAPON_AWP, "awp" }, + { WEAPON_MP5NAVY, "mp5navy" }, + { WEAPON_M249, "m249" }, + { WEAPON_M3, "m3" }, + { WEAPON_M4A1, "m4a1" }, + { WEAPON_TMP, "tmp" }, + { WEAPON_G3SG1, "g3sg1" }, + { WEAPON_DEAGLE, "deagle" }, + { WEAPON_SG552, "sg552" }, + { WEAPON_AK47, "ak47" }, + { WEAPON_P90, "p90" }, + + { WEAPON_KNIFE, "knife" }, + { WEAPON_C4, "c4" }, + { WEAPON_FLASHBANG, "flashbang" }, + { WEAPON_SMOKEGRENADE, "smokegrenade" }, + { WEAPON_SMOKEGRENADE, "sgren" }, + { WEAPON_HEGRENADE, "hegrenade" }, + { WEAPON_HEGRENADE, "hegren" }, + + // not sure any of these are needed + { WEAPON_SHIELDGUN, "shield" }, + { WEAPON_SHIELDGUN, "shieldgun" }, + { WEAPON_KEVLAR, "kevlar" }, + { WEAPON_ASSAULTSUIT, "assaultsuit" }, + { WEAPON_NVG, "nightvision" }, + { WEAPON_NVG, "nvg" }, + + { WEAPON_NONE, "none" }, +}; + + +bool IsAmmoType( int iAmmoType, const char *pAmmoName ) +{ + return GetAmmoDef()->Index( pAmmoName ) == iAmmoType; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Given an alias, return the translated alias. +// +const char * GetTranslatedWeaponAlias( const char *szAlias ) +{ + for ( int i = 0; i < ARRAYSIZE(s_WeaponAliasTranslationInfo); ++i ) + { + if ( Q_stricmp(s_WeaponAliasTranslationInfo[i].alias, szAlias) == 0 ) + { + return s_WeaponAliasTranslationInfo[i].translatedAlias; + } + } + + return szAlias; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Given a translated alias, return the alias. +// +const char * GetWeaponAliasFromTranslated(const char *translatedAlias) +{ + int i = 0; + const WeaponAliasTranslationInfoStruct *info = &(s_WeaponAliasTranslationInfo[i]); + + while (info->alias[0] != 0) + { + if (Q_stricmp(translatedAlias, info->translatedAlias) == 0) + { + return info->alias; + } + info = &(s_WeaponAliasTranslationInfo[++i]); + } + + return translatedAlias; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Given an alias, return the associated weapon ID +// +CSWeaponID AliasToWeaponID( const char *szAlias ) +{ + if ( szAlias ) + { + for ( int i=0; i < ARRAYSIZE(s_weaponAliasInfo); ++i) + { + if ( Q_stricmp( s_weaponAliasInfo[i].alias, szAlias) == 0 ) + return s_weaponAliasInfo[i].id; + } + } + + return WEAPON_NONE; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Given a weapon ID, return its alias +// +const char *WeaponIDToAlias( int id ) +{ + for ( int i=0; i < ARRAYSIZE(s_weaponAliasInfo); ++i) + { + if ( s_weaponAliasInfo[i].id == id ) + return s_weaponAliasInfo[i].alias; + } + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Return true if given weapon ID is a primary weapon +// +bool IsPrimaryWeapon( CSWeaponID id ) +{ + const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( id ); + if ( pWeaponInfo ) + { + return pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------- +// +// Return true if given weapon ID is a secondary weapon +// +bool IsSecondaryWeapon( CSWeaponID id ) +{ + const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( id ); + if ( pWeaponInfo ) + return pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL; + + return false; +} + +#ifdef CLIENT_DLL +int GetShellForAmmoType( const char *ammoname ) +{ + if ( !Q_strcmp( BULLET_PLAYER_762MM, ammoname ) ) + return CS_SHELL_762NATO; + + if ( !Q_strcmp( BULLET_PLAYER_556MM, ammoname ) ) + return CS_SHELL_556; + + if ( !Q_strcmp( BULLET_PLAYER_338MAG, ammoname ) ) + return CS_SHELL_338MAG; + + if ( !Q_strcmp( BULLET_PLAYER_BUCKSHOT, ammoname ) ) + return CS_SHELL_12GAUGE; + + if ( !Q_strcmp( BULLET_PLAYER_57MM, ammoname ) ) + return CS_SHELL_57; + + // default 9 mm + return CS_SHELL_9MM; +} +#endif + + +// ----------------------------------------------------------------------------- // +// CWeaponCSBase tables. +// ----------------------------------------------------------------------------- // + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCSBase, DT_WeaponCSBase ) + +BEGIN_NETWORK_TABLE( CWeaponCSBase, DT_WeaponCSBase ) +#if !defined( CLIENT_DLL ) +SendPropInt( SENDINFO( m_weaponMode ), 1, SPROP_UNSIGNED ), +SendPropFloat(SENDINFO(m_fAccuracyPenalty) ), +// world weapon models have no aminations +SendPropExclude( "DT_AnimTimeMustBeFirst", "m_flAnimTime" ), +SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), +// SendPropExclude( "DT_LocalActiveWeaponData", "m_flTimeWeaponIdle" ), +#else +RecvPropInt( RECVINFO( m_weaponMode ) ), +RecvPropFloat( RECVINFO(m_fAccuracyPenalty)), +#endif +END_NETWORK_TABLE() + +#if defined(CLIENT_DLL) +BEGIN_PREDICTION_DATA( CWeaponCSBase ) + DEFINE_PRED_FIELD( m_flTimeWeaponIdle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_flNextPrimaryAttack, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_flNextSecondaryAttack, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_NOERRORCHECK ), + DEFINE_PRED_FIELD( m_bDelayFire, FIELD_BOOLEAN, 0 ), + DEFINE_PRED_FIELD( m_flAccuracy, FIELD_FLOAT, 0 ), + DEFINE_PRED_FIELD( m_weaponMode, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD_TOL( m_fAccuracyPenalty, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.00005f ), +END_PREDICTION_DATA() +#endif + + +LINK_ENTITY_TO_CLASS( weapon_cs_base, CWeaponCSBase ); + + +#ifdef GAME_DLL + + BEGIN_DATADESC( CWeaponCSBase ) + + //DEFINE_FUNCTION( DefaultTouch ), + DEFINE_THINKFUNC( FallThink ) + + END_DATADESC() + +#endif + +#if defined( CLIENT_DLL ) + ConVar cl_crosshaircolor( "cl_crosshaircolor", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Set crosshair color: 0=green, 1=red, 2=blue, 3=yellow, 4=cyan, 5=custom" ); + ConVar cl_dynamiccrosshair( "cl_dynamiccrosshair", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Enables dynamic crosshair; 0=off, 1=normal behavior (based on actual weapon accuracy), 2=legacy simulated dynamic behavior, 3=legacy simulated static behavior" ); + ConVar cl_crosshairspreadscale( "cl_crosshairspreadscale", "0.3", FCVAR_CLIENTDLL | FCVAR_ARCHIVE); + ConVar cl_scalecrosshair( "cl_scalecrosshair", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Enable crosshair scaling (deprecated)" ); + ConVar cl_crosshairscale( "cl_crosshairscale", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Crosshair scaling factor (deprecated)" ); + ConVar cl_crosshairalpha( "cl_crosshairalpha", "200", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshairusealpha( "cl_crosshairusealpha", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshairsize( "cl_crosshairsize", "5", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshairthickness( "cl_crosshairthickness", "0.5", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshairdot( "cl_crosshairdot", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshaircolor_r( "cl_crosshaircolor_r", "50", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshaircolor_g( "cl_crosshaircolor_g", "250", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + ConVar cl_crosshaircolor_b( "cl_crosshaircolor_b", "50", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); + +#if ALLOW_WEAPON_SPREAD_DISPLAY + ConVar weapon_debug_spread_show( "weapon_debug_spread_show", "0", FCVAR_CLIENTDLL | FCVAR_DEVELOPMENTONLY, "Enables display of weapon accuracy; 1: show accuracy box, 2: show box with recoil offset" ); + ConVar weapon_debug_spread_gap( "weapon_debug_spread_gap", "0.67", FCVAR_CLIENTDLL | FCVAR_DEVELOPMENTONLY ); +#endif + + // [paquin] make sure crosshair scales independent of frame rate + // unless legacy cvar is set + ConVar cl_legacy_crosshair_recoil( "cl_legacy_crosshair_recoil", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Enable legacy framerate dependent crosshair recoil"); + + // use old scaling behavior + ConVar cl_legacy_crosshair_scale( "cl_legacy_crosshair_scale", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Enable legacy crosshair scaling"); + +void DrawCrosshairRect( int x0, int y0, int x1, int y1, bool bAdditive ) +{ + if ( bAdditive ) + { + vgui::surface()->DrawTexturedRect( x0, y0, x1, y1 ); + } + else + { + // Alpha-blended crosshair + vgui::surface()->DrawFilledRect( x0, y0, x1, y1 ); + } +} + +#endif + +// must be included after the above macros +#ifndef CLIENT_DLL + #include "cs_bot.h" +#endif + + +// ----------------------------------------------------------------------------- // +// CWeaponCSBase implementation. +// ----------------------------------------------------------------------------- // +CWeaponCSBase::CWeaponCSBase() +{ + SetPredictionEligible( true ); + m_bDelayFire = true; + m_nextPrevOwnerTouchTime = 0.0; + m_prevOwner = NULL; + AddSolidFlags( FSOLID_TRIGGER ); // Nothing collides with these but it gets touches. + +#ifdef CLIENT_DLL + m_iCrosshairTextureID = 0; +#else + m_iDefaultExtraAmmo = 0; +#endif + + m_fAccuracyPenalty = 0.0f; + + m_weaponMode = Primary_Mode; +} + + +#ifndef CLIENT_DLL +bool CWeaponCSBase::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !BaseClass::KeyValue( szKeyName, szValue ) ) + { + if ( FStrEq( szKeyName, "ammo" ) ) + { + int bullets = atoi( szValue ); + if ( bullets < 0 ) + return false; + + m_iDefaultExtraAmmo = bullets; + + return true; + } + } + + return false; +} +#endif + + +bool CWeaponCSBase::IsPredicted() const +{ + return true; +} + + +bool CWeaponCSBase::IsPistol() const +{ + return GetCSWpnData().m_WeaponType == WEAPONTYPE_PISTOL; +} + + +bool CWeaponCSBase::IsFullAuto() const +{ + return GetCSWpnData().m_bFullAuto; +} + + +bool CWeaponCSBase::PlayEmptySound() +{ + //MIKETODO: certain weapons should override this to make it empty: + // C4 + // Flashbang + // HE Grenade + // Smoke grenade + + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + + if ( IsPistol() ) + { + EmitSound( filter, entindex(), "Default.ClipEmpty_Pistol" ); + } + else + { + EmitSound( filter, entindex(), "Default.ClipEmpty_Rifle" ); + } + + return 0; +} + +CCSPlayer* CWeaponCSBase::GetPlayerOwner() const +{ + return dynamic_cast< CCSPlayer* >( GetOwner() ); +} + +//============================================================================= +// HPE_BEGIN: +//============================================================================= + +//[dwenger] Accessors for the prior owner list +void CWeaponCSBase::AddToPriorOwnerList(CCSPlayer* pPlayer) +{ + if ( !IsAPriorOwner( pPlayer ) ) + { + // Add player to prior owner list + m_PriorOwners.AddToTail( pPlayer ); + } +} + +bool CWeaponCSBase::IsAPriorOwner(CCSPlayer* pPlayer) +{ + return (m_PriorOwners.Find( pPlayer ) != -1); +} + +//============================================================================= +// HPE_END +//============================================================================= + + +void CWeaponCSBase::SecondaryAttack( void ) +{ +#ifndef CLIENT_DLL + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( !pPlayer ) + return; + + if ( pPlayer->HasShield() == false ) + BaseClass::SecondaryAttack(); + else + { + pPlayer->SetShieldDrawnState( !pPlayer->IsShieldDrawn() ); + + if ( pPlayer->IsShieldDrawn() ) + SendWeaponAnim( ACT_SHIELD_UP ); + else + SendWeaponAnim( ACT_SHIELD_DOWN ); + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.4; + m_flNextPrimaryAttack = gpGlobals->curtime + 0.4; + } +#endif +} + +bool CWeaponCSBase::SendWeaponAnim( int iActivity ) +{ +#ifdef CS_SHIELD_ENABLED + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer && pPlayer->HasShield() ) + { + CBaseViewModel *vm = pPlayer->GetViewModel( 1 ); + + if ( vm == NULL ) + return false; + + vm->SetWeaponModel( SHIELD_VIEW_MODEL, this ); + + int idealSequence = vm->SelectWeightedSequence( (Activity)iActivity ); + + if ( idealSequence >= 0 ) + { + vm->SendViewModelMatchingSequence( idealSequence ); + } + } +#endif + + return BaseClass::SendWeaponAnim( iActivity ); +} + + +void CWeaponCSBase::ItemPostFrame() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( !pPlayer ) + return; + + UpdateAccuracyPenalty(); + + UpdateShieldState(); + + if ((m_bInReload) && (pPlayer->m_flNextAttack <= gpGlobals->curtime)) + { + // complete the reload. + int j = MIN( GetMaxClip1() - m_iClip1, pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) ); + + // Add them to the clip + m_iClip1 += j; + pPlayer->RemoveAmmo( j, m_iPrimaryAmmoType ); + + m_bInReload = false; + } + + if ((pPlayer->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + { + if ( pPlayer->HasShield() ) + CWeaponCSBase::SecondaryAttack(); + else + SecondaryAttack(); + + pPlayer->m_nButtons &= ~IN_ATTACK2; + } + else if ((pPlayer->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime )) + { + if ( CSGameRules()->IsFreezePeriod() ) // Can't shoot during the freeze period + return; + + if ( pPlayer->m_bIsDefusing ) + return; + + if ( pPlayer->State_Get() != STATE_ACTIVE ) + return; + + if ( pPlayer->IsShieldDrawn() ) + return; + + // we have to reset the FireOnEmpty flag before we can fire on an empty clip + if ( m_iClip1 == 0 && !m_bFireOnEmpty ) + return; + + // don't repeat fire if this is not a full auto weapon + if ( pPlayer->m_iShotsFired > 0 && !IsFullAuto() ) + return; + +#if !defined(CLIENT_DLL) + // allow the bots to react to the gunfire + if ( GetCSWpnData().m_WeaponType != WEAPONTYPE_GRENADE ) + { + IGameEvent * event = gameeventmanager->CreateEvent( (HasAmmo()) ? "weapon_fire" : "weapon_fire_on_empty" ); + if( event ) + { + const char *weaponName = STRING( m_iClassname ); + if ( strncmp( weaponName, "weapon_", 7 ) == 0 ) + { + weaponName += 7; + } + + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetString( "weapon", weaponName ); + gameeventmanager->FireEvent( event ); + } + } +#endif + PrimaryAttack(); + } + else if ( pPlayer->m_nButtons & IN_RELOAD && GetMaxClip1() != WEAPON_NOCLIP && !m_bInReload && m_flNextPrimaryAttack < gpGlobals->curtime) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + + //MIKETODO: add code for shields... + //if ( !FBitSet( m_iWeaponState, WPNSTATE_SHIELD_DRAWN ) ) + + if ( !pPlayer->IsShieldDrawn() ) + { + if ( Reload() ) + { +#ifndef CLIENT_DLL + // allow the bots to react to the reload + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_reload" ); + if( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif + } + } + } + else if ( !(pPlayer->m_nButtons & (IN_ATTACK|IN_ATTACK2) ) ) + { + if ( weapon_accuracy_model.GetInt() == 2 ) + { + // Fire button not down -- reset the shots fired count + if ( pPlayer->m_iShotsFired > 0 && ( !IsFullAuto() || m_iClip1 == 0 ) ) + { + pPlayer->m_iShotsFired = 0; + } + } + + // The following code prevents the player from tapping the firebutton repeatedly + // to simulate full auto and retaining the single shot accuracy of single fire + if ( m_bDelayFire ) + { + m_bDelayFire = false; + + if (pPlayer->m_iShotsFired > 15) + pPlayer->m_iShotsFired = 15; + + m_flDecreaseShotsFired = gpGlobals->curtime + 0.4; + } + + m_bFireOnEmpty = true; + + // if it's a pistol then set the shots fired to 0 after the player releases a button + if ( IsPistol() ) + { + pPlayer->m_iShotsFired = 0; + } + else + { + if ( (pPlayer->m_iShotsFired > 0) && (m_flDecreaseShotsFired < gpGlobals->curtime) ) + { + m_flDecreaseShotsFired = gpGlobals->curtime + 0.0225; + pPlayer->m_iShotsFired--; + } + } + + if ( (!IsUseable() && m_flNextPrimaryAttack < gpGlobals->curtime) ) + { + // Intentionally blank -- used to switch weapons here + } + else + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip1 == 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime ) + { + Reload(); + return; + } + } + + WeaponIdle( ); + return; + } +} + + +void CWeaponCSBase::ItemBusyFrame() +{ + UpdateAccuracyPenalty(); + + BaseClass::ItemBusyFrame(); +} + + +float CWeaponCSBase::GetInaccuracy() const +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + const CCSWeaponInfo& weaponInfo = GetCSWpnData(); + + float fMaxSpeed = GetMaxSpeed(); + if ( fMaxSpeed == 0.0f ) + fMaxSpeed = GetCSWpnData().m_flMaxSpeed; + + return m_fAccuracyPenalty + + RemapValClamped(pPlayer->GetAbsVelocity().Length2D(), + fMaxSpeed * CS_PLAYER_SPEED_DUCK_MODIFIER, + fMaxSpeed * 0.95f, // max out at 95% of run speed to avoid jitter near max speed + 0.0f, weaponInfo.m_fInaccuracyMove[m_weaponMode]); +} + + +float CWeaponCSBase::GetSpread() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + return 0.0f; + + return GetCSWpnData().m_fSpread[m_weaponMode]; +} + + +float CWeaponCSBase::GetMaxSpeed() const +{ + // The weapon should have set this in its constructor. + float flRet = GetCSWpnData().m_flMaxSpeed; + Assert( flRet > 1 ); + return flRet; +} + +const CCSWeaponInfo &CWeaponCSBase::GetCSWpnData() const +{ + const FileWeaponInfo_t *pWeaponInfo = &GetWpnData(); + const CCSWeaponInfo *pCSInfo; + + #ifdef _DEBUG + pCSInfo = dynamic_cast< const CCSWeaponInfo* >( pWeaponInfo ); + Assert( pCSInfo ); + #else + pCSInfo = static_cast< const CCSWeaponInfo* >( pWeaponInfo ); + #endif + + return *pCSInfo; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CWeaponCSBase::GetViewModel( int /*viewmodelindex = 0 -- this is ignored in the base class here*/ ) const +{ + CCSPlayer *pOwner = GetPlayerOwner(); + + if ( pOwner == NULL ) + return BaseClass::GetViewModel(); + + if ( pOwner->HasShield() && GetCSWpnData().m_bCanUseWithShield ) + return GetCSWpnData().m_szShieldViewModel; + else + return GetWpnData().szViewModel; + + return BaseClass::GetViewModel(); + +} + +void CWeaponCSBase::Precache( void ) +{ + BaseClass::Precache(); + +#ifdef CS_SHIELD_ENABLED + if ( GetCSWpnData().m_bCanUseWithShield ) + { + PrecacheModel( GetCSWpnData().m_szShieldViewModel ); + } +#endif + + PrecacheScriptSound( "Default.ClipEmpty_Pistol" ); + PrecacheScriptSound( "Default.ClipEmpty_Rifle" ); + + PrecacheScriptSound( "Default.Zoom" ); +} + +Activity CWeaponCSBase::GetDeployActivity( void ) +{ + return ACT_VM_DRAW; +} + +bool CWeaponCSBase::DefaultDeploy( char *szViewModel, char *szWeaponModel, int iActivity, char *szAnimExt ) +{ + // Msg( "deploy %s at %f\n", GetClassname(), gpGlobals->curtime ); + CCSPlayer *pOwner = GetPlayerOwner(); + if ( !pOwner ) + { + return false; + } + + pOwner->SetAnimationExtension( szAnimExt ); + + SetViewModel(); + SendWeaponAnim( GetDeployActivity() ); + + pOwner->SetNextAttack( gpGlobals->curtime + SequenceDuration() ); + m_flNextPrimaryAttack = gpGlobals->curtime; + m_flNextSecondaryAttack = gpGlobals->curtime; + + SetWeaponVisible( true ); + pOwner->SetShieldDrawnState( false ); + + if ( pOwner->HasShield() == true ) + SetWeaponModelIndex( SHIELD_WORLD_MODEL); + else + SetWeaponModelIndex( szWeaponModel ); + + return true; +} + +void CWeaponCSBase::UpdateShieldState( void ) +{ + //empty by default. + CCSPlayer *pOwner = GetPlayerOwner(); + + if ( pOwner == NULL ) + return; + + //ADRIANTODO + //Make the hitbox set switches here!!! + if ( pOwner->HasShield() == false ) + { + + pOwner->SetShieldDrawnState( false ); + //pOwner->SetHitBoxSet( 0 ); + return; + } + else + { + //pOwner->SetHitBoxSet( 1 ); + } +} + +void CWeaponCSBase::SetWeaponModelIndex( const char *pName ) +{ + m_iWorldModelIndex = modelinfo->GetModelIndex( pName ); +} + +bool CWeaponCSBase::CanBeSelected( void ) +{ + if ( !VisibleInWeaponSelection() ) + return false; + + return true; +} + +bool CWeaponCSBase::CanDeploy( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if ( pPlayer->HasShield() && GetCSWpnData().m_bCanUseWithShield == false ) + return false; + + return BaseClass::CanDeploy(); +} + +float CWeaponCSBase::CalculateNextAttackTime( float fCycleTime ) +{ + float fCurAttack = m_flNextPrimaryAttack; + float fDeltaAttack = gpGlobals->curtime - fCurAttack; + if ( fDeltaAttack < 0 || fDeltaAttack > gpGlobals->interval_per_tick ) + { + fCurAttack = gpGlobals->curtime; + } + m_flNextSecondaryAttack = m_flNextPrimaryAttack = fCurAttack + fCycleTime; + + return fCurAttack; +} + +bool CWeaponCSBase::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if ( pPlayer ) + pPlayer->SetFOV( pPlayer, 0 ); // reset the default FOV. + + if ( pPlayer ) + pPlayer->SetShieldDrawnState( false ); + + return BaseClass::Holster( pSwitchingTo ); +} + +bool CWeaponCSBase::Deploy() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + +#ifdef CLIENT_DLL + m_iAlpha = 80; + if ( pPlayer ) + { + pPlayer->m_iLastZoom = 0; + pPlayer->SetFOV( pPlayer, 0 ); + } +#else + + m_flDecreaseShotsFired = gpGlobals->curtime; + + + if ( pPlayer ) + { + pPlayer->m_iShotsFired = 0; + pPlayer->m_bResumeZoom = false; + pPlayer->m_iLastZoom = 0; + pPlayer->SetFOV( pPlayer, 0 ); + } +#endif + + m_fAccuracyPenalty = 0.0f; + + return BaseClass::Deploy(); +} + +#ifndef CLIENT_DLL +bool CWeaponCSBase::IsRemoveable() +{ + if ( BaseClass::IsRemoveable() == true ) + { + if ( m_nextPrevOwnerTouchTime > gpGlobals->curtime ) + { + return false; + } + } + + return BaseClass::IsRemoveable(); +} +#endif + +void CWeaponCSBase::Drop(const Vector &vecVelocity) +{ + +#ifdef CLIENT_DLL + BaseClass::Drop(vecVelocity); + return; +#else + + // Once somebody drops a gun, it's fair game for removal when/if + // a game_weapon_manager does a cleanup on surplus weapons in the + // world. + SetRemoveable( true ); + + StopAnimation(); + StopFollowingEntity( ); + SetMoveType( MOVETYPE_FLYGRAVITY ); + // clear follow stuff, setup for collision + SetGravity(1.0); + m_iState = WEAPON_NOT_CARRIED; + RemoveEffects( EF_NODRAW ); + FallInit(); + SetGroundEntity( NULL ); + + m_bInReload = false; // stop reloading + + SetThink( NULL ); + m_nextPrevOwnerTouchTime = gpGlobals->curtime + 0.8f; + m_prevOwner = GetPlayerOwner(); + + SetTouch(&CWeaponCSBase::DefaultTouch); + + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( pObj != NULL ) + { + AngularImpulse angImp( 200, 200, 200 ); + pObj->AddVelocity( &vecVelocity, &angImp ); + } + else + { + SetAbsVelocity( vecVelocity ); + } + + SetNextThink( gpGlobals->curtime ); + + SetOwnerEntity( NULL ); + SetOwner( NULL ); +#endif +} + +// whats going on here is that if the player drops this weapon, they shouldn't take it back themselves +// for a little while. But if they throw it at someone else, the other player should get it immediately. +void CWeaponCSBase::DefaultTouch(CBaseEntity *pOther) +{ + if ((m_prevOwner != NULL) && (pOther == m_prevOwner) && (gpGlobals->curtime < m_nextPrevOwnerTouchTime)) + { + return; + } + + BaseClass::DefaultTouch(pOther); +} + +#if defined( CLIENT_DLL ) + + //----------------------------------------------------------------------------- + // Purpose: Draw the weapon's crosshair + //----------------------------------------------------------------------------- + void CWeaponCSBase::DrawCrosshair() + { + if ( !crosshair.GetInt() ) + return; + + CHudCrosshair *pCrosshair = GET_HUDELEMENT( CHudCrosshair ); + + if ( !pCrosshair ) + return; + + // clear crosshair + pCrosshair->SetCrosshair( 0, Color( 255, 255, 255, 255 ) ); + + CCSPlayer* pPlayer = (CCSPlayer*)C_BasePlayer::GetLocalPlayer(); + + if ( !pPlayer ) + return; + + // localplayer must be owner if not in Spec mode + Assert( (pPlayer == GetPlayerOwner()) || ( pPlayer->GetObserverMode()==OBS_MODE_IN_EYE) ); + + // Draw the targeting zone around the pCrosshair + if ( pPlayer->IsInVGuiInputMode() ) + return; + + int r, g, b; + switch ( cl_crosshaircolor.GetInt() ) + { + case 0 : r = 50; g = 250; b = 50; break; + case 1 : r = 250; g = 50; b = 50; break; + case 2 : r = 50; g = 50; b = 250; break; + case 3 : r = 250; g = 250; b = 50; break; + case 4 : r = 50; g = 250; b = 250; break; + case 5 : + r = cl_crosshaircolor_r.GetInt(); + g = cl_crosshaircolor_g.GetInt(); + b = cl_crosshaircolor_b.GetInt(); + break; + default : r = 50; g = 250; b = 50; break; + } + + // if user is using nightvision, make the crosshair red. + if (pPlayer->m_bNightVisionOn) + { + r = 250; + g = 50; + b = 50; + } + + int alpha = clamp( cl_crosshairalpha.GetInt(), 0, 255 ); + vgui::surface()->DrawSetColor( r, g, b, alpha ); + + if ( !m_iCrosshairTextureID ) + { + CHudTexture *pTexture = gHUD.GetIcon( "whiteAdditive" ); + if ( pTexture ) + { + m_iCrosshairTextureID = pTexture->textureId; + } + } + + bool bAdditive = !cl_crosshairusealpha.GetBool() && !pPlayer->m_bNightVisionOn; + if ( bAdditive ) + { + vgui::surface()->DrawSetColor( r, g, b, 200 ); + vgui::surface()->DrawSetTexture( m_iCrosshairTextureID ); + } + + if ( pPlayer->HasShield() && pPlayer->IsShieldDrawn() == true ) + return; + + // no crosshair for sniper rifles + bool bCrosshairVisible = crosshair.GetBool() && GetCSWpnData().m_WeaponType != WEAPONTYPE_SNIPER_RIFLE; + + if ( !bCrosshairVisible +#if ALLOW_WEAPON_SPREAD_DISPLAY + && !weapon_debug_spread_show.GetBool() +#endif + ) + return; + + float fHalfFov = DEG2RAD(pPlayer->GetFOV()) * 0.5f; + + int iCrosshairDistance; + int iBarSize = RoundFloatToInt(YRES(cl_crosshairsize.GetFloat())); + int iBarThickness = MAX( 1, RoundFloatToInt(YRES(cl_crosshairthickness.GetFloat()))); + + switch ( cl_dynamiccrosshair.GetInt() ) + { + case 0: + default: + { + // static crosshair + float fSpread = (GetCSWpnData().m_fSpread[m_weaponMode] + GetCSWpnData().m_fInaccuracyStand[m_weaponMode]) * 320.0f / tanf(fHalfFov); + iCrosshairDistance = MAX( 0, RoundFloatToInt( YRES( fSpread * cl_crosshairspreadscale.GetFloat() ) ) ); + } + break; + + case 1: + { + float fSpread = (GetInaccuracy() + GetSpread()) * 320.0f / tanf(fHalfFov); + iCrosshairDistance = MAX( 0, RoundFloatToInt( YRES( fSpread * cl_crosshairspreadscale.GetFloat() ) ) ); + } + break; + + case 2: + case 3: + { + float fCrosshairDistanceGoal = GetCSWpnData().m_iCrosshairMinDistance; // The minimum distance the crosshair can achieve... + + // legacy dynamic crosshair + if ( cl_dynamiccrosshair.GetInt() == 2 ) + { + if ( !( pPlayer->GetFlags() & FL_ONGROUND ) ) + fCrosshairDistanceGoal *= 2.0f; + else if ( pPlayer->GetFlags() & FL_DUCKING ) + fCrosshairDistanceGoal *= 0.5f; + else if ( pPlayer->GetAbsVelocity().Length() > 100 ) + fCrosshairDistanceGoal *= 1.5f; + } + + // [jpaquin] changed to only bump up the crosshair size if the player is still shooting or is spectating someone else + int iDeltaDistance = GetCSWpnData().m_iCrosshairDeltaDistance; // Amount by which the crosshair expands when shooting (per frame) + if ( pPlayer->m_iShotsFired > m_iAmmoLastCheck && (pPlayer->m_nButtons & (IN_ATTACK|IN_ATTACK2)) ) + fCrosshairDistanceGoal += iDeltaDistance; + + m_iAmmoLastCheck = pPlayer->m_iShotsFired; + + if ( m_flCrosshairDistance > fCrosshairDistanceGoal ) + { + // [jpaquin] if we're not in legacy crosshair mode, use an exponential decay function so + // that the crosshair shrinks at the same rate regardless of the frame rate + if ( !cl_legacy_crosshair_recoil.GetBool() ) + { + // .44888 on the next line makes the decay very close to what old method produces at 100fps. + m_flCrosshairDistance = Lerp(expf(-gpGlobals->frametime / 0.44888f), fCrosshairDistanceGoal, m_flCrosshairDistance); + } + else + { + m_flCrosshairDistance -= 0.1f + m_flCrosshairDistance * 0.013; + } + } + + // clamp max crosshair expansion + m_flCrosshairDistance = clamp(m_flCrosshairDistance, fCrosshairDistanceGoal, 25.0f); + + if ( cl_legacy_crosshair_scale.GetBool() ) + { + //scale bar size to the resolution + int crosshairScale = cl_crosshairscale.GetInt(); + if ( crosshairScale < 1 ) + { + if ( ScreenHeight() <= 600 ) + { + crosshairScale = 600; + } + else if ( ScreenHeight() <= 768 ) + { + crosshairScale = 768; + } + else + { + crosshairScale = 1200; + } + } + + float scale; + if( cl_scalecrosshair.GetBool() == false ) + { + scale = 1.0f; + } + else + { + scale = (float)ScreenHeight() / (float)crosshairScale; + } + + // calculate the inner distance of the crosshair in current screen units + iCrosshairDistance = (int)ceil( m_flCrosshairDistance * scale ); + + iBarSize = XRES(5); // + (iCrosshairDistance - fCrosshairDistanceGoal) / 2; + iBarSize = MAX( 1, (int)( (float)iBarSize * scale ) ); + iBarThickness = MAX( 1, (int)floor( scale + 0.5f ) ); + } + else + { + iCrosshairDistance = RoundFloatToInt(m_flCrosshairDistance * ScreenHeight() / 1200.0f); + } + } + break; + } + + int iCenterX = ScreenWidth() / 2; + int iCenterY = ScreenHeight() / 2; + + if ( bCrosshairVisible ) + { + // draw horizontal crosshair lines + int iInnerLeft = iCenterX - iCrosshairDistance - iBarThickness / 2; + int iInnerRight = iInnerLeft + 2 * iCrosshairDistance + iBarThickness; + int iOuterLeft = iInnerLeft - iBarSize; + int iOuterRight = iInnerRight + iBarSize; + int y0 = iCenterY - iBarThickness / 2; + int y1 = y0 + iBarThickness; + DrawCrosshairRect( iOuterLeft, y0, iInnerLeft, y1, bAdditive ); + DrawCrosshairRect( iInnerRight, y0, iOuterRight, y1, bAdditive ); + + // draw vertical crosshair lines + int iInnerTop = iCenterY - iCrosshairDistance - iBarThickness / 2; + int iInnerBottom = iInnerTop + 2 * iCrosshairDistance + iBarThickness; + int iOuterTop = iInnerTop - iBarSize; + int iOuterBottom = iInnerBottom + iBarSize; + int x0 = iCenterX - iBarThickness / 2; + int x1 = x0 + iBarThickness; + DrawCrosshairRect( x0, iOuterTop, x1, iInnerTop, bAdditive ); + DrawCrosshairRect( x0, iInnerBottom, x1, iOuterBottom, bAdditive ); + + // draw dot + if ( cl_crosshairdot.GetBool() ) + { + int x0 = iCenterX - iBarThickness / 2; + int x1 = x0 + iBarThickness; + int y0 = iCenterY - iBarThickness / 2; + int y1 = y0 + iBarThickness; + DrawCrosshairRect( x0, y0, x1, y1, bAdditive ); + } + } + +#if ALLOW_WEAPON_SPREAD_DISPLAY + // show accuracy brackets + if ( weapon_debug_spread_show.GetInt() == 1 || weapon_debug_spread_show.GetInt() == 2 ) + { + if ( weapon_debug_spread_show.GetInt() == 2 ) + { + const QAngle& punchAngles = pPlayer->GetPunchAngle(); + Vector vecDirShooting; + AngleVectors( punchAngles, &vecDirShooting ); + + float iOffsetX = RoundFloatToInt(YRES(vecDirShooting.y * 320.0f / tanf(fHalfFov))); + float iOffsetY = RoundFloatToInt(YRES(vecDirShooting.z * 320.0f / tanf(fHalfFov))); + + iCenterX -= iOffsetX; + iCenterY -= iOffsetY; + } + + // colors + r = 250; + g = 250; + b = 50; + vgui::surface()->DrawSetColor( r, g, b, alpha ); + + int iBarThickness = MAX( 1, RoundFloatToInt(YRES(cl_crosshairthickness.GetFloat()))); + + float fSpreadDistance = (GetInaccuracy() + GetSpread()) * 320.0f / tanf(fHalfFov); + int iSpreadDistance = RoundFloatToInt(YRES(fSpreadDistance)); + + // draw vertical spread lines + int iInnerLeft = iCenterX - iSpreadDistance; + int iInnerRight = iCenterX + iSpreadDistance; + int iOuterLeft = iInnerLeft - iBarThickness; + int iOuterRight = iInnerRight + iBarThickness; + int iInnerTop = iCenterY - iSpreadDistance; + int iInnerBottom = iCenterY + iSpreadDistance; + int iOuterTop = iInnerTop - iBarThickness; + int iOuterBottom = iInnerBottom + iBarThickness; + + int iGap = RoundFloatToInt(weapon_debug_spread_gap.GetFloat() * iSpreadDistance); + + // draw horizontal lines + DrawCrosshairRect( iOuterLeft, iOuterTop, iCenterX - iGap, iInnerTop, bAdditive ); + DrawCrosshairRect( iCenterX + iGap, iOuterTop, iOuterRight, iInnerTop, bAdditive ); + DrawCrosshairRect( iOuterLeft, iInnerBottom, iCenterX - iGap, iOuterBottom, bAdditive ); + DrawCrosshairRect( iCenterX + iGap, iInnerBottom, iOuterRight, iOuterBottom, bAdditive ); + + // draw vertical lines + DrawCrosshairRect( iOuterLeft, iOuterTop, iInnerLeft, iCenterY - iGap, bAdditive ); + DrawCrosshairRect( iOuterLeft, iCenterY + iGap, iInnerLeft, iOuterBottom, bAdditive ); + DrawCrosshairRect( iInnerRight, iOuterTop, iOuterRight, iCenterY - iGap, bAdditive ); + DrawCrosshairRect( iInnerRight, iCenterY + iGap, iOuterRight, iOuterBottom, bAdditive ); + } +#endif + } + + void CWeaponCSBase::OnDataChanged( DataUpdateType_t type ) + { + BaseClass::OnDataChanged( type ); + + if ( GetPredictable() && !ShouldPredict() ) + ShutdownPredictable(); + } + + + bool CWeaponCSBase::ShouldPredict() + { + if ( GetOwner() && GetOwner() == C_BasePlayer::GetLocalPlayer() ) + return true; + + return BaseClass::ShouldPredict(); + } + + void CWeaponCSBase::ProcessMuzzleFlashEvent() + { + // This is handled from the player's animstate, so it can match up to the beginning of the fire animation + } + + + bool CWeaponCSBase::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) + { + if( event == 5001 ) + { + C_CSPlayer *pPlayer = ToCSPlayer( GetOwner() ); + if( pPlayer && pPlayer->GetFOV() < pPlayer->GetDefaultFOV() && HideViewModelWhenZoomed() ) + return true; + + CEffectData data; + data.m_fFlags = 0; + data.m_hEntity = pViewModel->GetRefEHandle(); + data.m_nAttachmentIndex = 1; + data.m_flScale = GetCSWpnData().m_flMuzzleScale; + + switch( GetMuzzleFlashStyle() ) + { + case CS_MUZZLEFLASH_NONE: + break; + + case CS_MUZZLEFLASH_X: + { + DispatchEffect( "CS_MuzzleFlash_X", data ); + } + break; + + case CS_MUZZLEFLASH_NORM: + default: + { + DispatchEffect( "CS_MuzzleFlash", data ); + } + break; + } + + return true; + } + + return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); + } + + int CWeaponCSBase::GetMuzzleFlashStyle( void ) + { + return GetCSWpnData().m_iMuzzleFlashStyle; + } + + int CWeaponCSBase::GetMuzzleAttachment( void ) + { + return LookupAttachment( "muzzle_flash" ); + } + +#else + + //----------------------------------------------------------------------------- + // Purpose: Get the accuracy derived from weapon and player, and return it + //----------------------------------------------------------------------------- + const Vector& CWeaponCSBase::GetBulletSpread() + { + static Vector cone = VECTOR_CONE_8DEGREES; + return cone; + } + + //----------------------------------------------------------------------------- + // Purpose: Match the anim speed to the weapon speed while crouching + //----------------------------------------------------------------------------- + float CWeaponCSBase::GetDefaultAnimSpeed() + { + return 1.0; + } + + //----------------------------------------------------------------------------- + // Purpose: Draw the laser rifle effect + //----------------------------------------------------------------------------- + void CWeaponCSBase::BulletWasFired( const Vector &vecStart, const Vector &vecEnd ) + { + } + + + bool CWeaponCSBase::ShouldRemoveOnRoundRestart() + { + if ( GetPlayerOwner() ) + return false; + else + return true; + } + + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Handle round restart processing for the weapon. + //============================================================================= + + void CWeaponCSBase::OnRoundRestart() + { + // Clear out the list of prior owners + m_PriorOwners.RemoveAll(); + } + + //============================================================================= + // HPE_END + //============================================================================= + + //========================================================= + // Materialize - make a CWeaponCSBase visible and tangible + //========================================================= + void CWeaponCSBase::Materialize() + { + if ( IsEffectActive( EF_NODRAW ) ) + { + // changing from invisible state to visible. + RemoveEffects( EF_NODRAW ); + DoMuzzleFlash(); + } + + AddSolidFlags( FSOLID_TRIGGER ); + + //SetTouch( &CWeaponCSBase::DefaultTouch ); + + SetThink( NULL ); + + } + + //========================================================= + // AttemptToMaterialize - the item is trying to rematerialize, + // should it do so now or wait longer? + //========================================================= + void CWeaponCSBase::AttemptToMaterialize() + { + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + SetNextThink( gpGlobals->curtime + time ); + } + + //========================================================= + // CheckRespawn - a player is taking this weapon, should + // it respawn? + //========================================================= + void CWeaponCSBase::CheckRespawn() + { + //GOOSEMAN : Do not respawn weapons! + return; + } + + + //========================================================= + // Respawn- this item is already in the world, but it is + // invisible and intangible. Make it visible and tangible. + //========================================================= + CBaseEntity* CWeaponCSBase::Respawn() + { + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( GetClassname(), g_pGameRules->VecWeaponRespawnSpot( this ), GetAbsAngles(), GetOwner() ); + + if ( pNewWeapon ) + { + pNewWeapon->AddEffects( EF_NODRAW );// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CWeaponCSBase::AttemptToMaterialize ); + + UTIL_DropToFloor( this, MASK_SOLID ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->SetNextThink( gpGlobals->curtime + g_pGameRules->FlWeaponRespawnTime( this ) ); + } + else + { + Msg( "Respawn failed to create %s!\n", GetClassname() ); + } + + return pNewWeapon; + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + void CWeaponCSBase::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + CBasePlayer *pPlayer = ToBasePlayer( pActivator ); + + if ( pPlayer ) + { + m_OnPlayerUse.FireOutput( pActivator, pCaller ); + } + } + + bool CWeaponCSBase::Reload() + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + pPlayer->m_iShotsFired = 0; + + bool retval = BaseClass::Reload(); + + return retval; + } + + void CWeaponCSBase::Spawn() + { + BaseClass::Spawn(); + + // Override the bloat that our base class sets as it's a little bit bigger than we want. + // If it's too big, you drop a weapon and its box is so big that you're still touching it + // when it falls and you pick it up again right away. + CollisionProp()->UseTriggerBounds( true, 30 ); + + // Set this here to allow players to shoot dropped weapons + SetCollisionGroup( COLLISION_GROUP_WEAPON ); + + SetExtraAmmoCount( m_iDefaultExtraAmmo ); //Start with no additional ammo + + m_nextPrevOwnerTouchTime = 0.0; + m_prevOwner = NULL; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] initialize donor of this weapon + m_donor = NULL; + m_donated = false; + + m_weaponMode = Primary_Mode; + + //============================================================================= + // HPE_END + //============================================================================= + } + + bool CWeaponCSBase::DefaultReload( int iClipSize1, int iClipSize2, int iActivity ) + { + if ( BaseClass::DefaultReload( iClipSize1, iClipSize2, iActivity ) ) + { + SendReloadEvents(); + return true; + } + else + { + return false; + } + } + + void CWeaponCSBase::SendReloadEvents() + { + CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( GetOwner() ); + if ( !pPlayer ) + return; + + // Send a message to any clients that have this entity to play the reload. + CPASFilter filter( pPlayer->GetAbsOrigin() ); + filter.RemoveRecipient( pPlayer ); + + UserMessageBegin( filter, "ReloadEffect" ); + WRITE_SHORT( pPlayer->entindex() ); + MessageEnd(); + + // Make the player play his reload animation. + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); + } + +#endif + + +bool CWeaponCSBase::DefaultPistolReload() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if (pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0) + return true; + + if ( !DefaultReload( GetCSWpnData().iDefaultClip1, 0, ACT_VM_RELOAD ) ) + return false; + + pPlayer->m_iShotsFired = 0; + + return true; +} + +bool CWeaponCSBase::IsUseable() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if ( Clip1() <= 0 ) + { + if ( pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0 && GetMaxClip1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return false; + } + } + + return true; +} + + +#if defined( CLIENT_DLL ) + + float g_lateralBob = 0; + float g_verticalBob = 0; + + static ConVar cl_bobcycle( "cl_bobcycle","0.8", FCVAR_CHEAT ); + static ConVar cl_bob( "cl_bob","0.002", FCVAR_CHEAT ); + static ConVar cl_bobup( "cl_bobup","0.5", FCVAR_CHEAT ); + + //----------------------------------------------------------------------------- + // Purpose: + // Output : float + //----------------------------------------------------------------------------- + float CWeaponCSBase::CalcViewmodelBob( void ) + { + static float bobtime; + static float lastbobtime; + static float lastspeed; + float cycle; + + CBasePlayer *player = ToBasePlayer( GetOwner() ); + //Assert( player ); + + //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it + + if ( ( !gpGlobals->frametime ) || + ( player == NULL ) || + ( cl_bobcycle.GetFloat() <= 0.0f ) || + ( cl_bobup.GetFloat() <= 0.0f ) || + ( cl_bobup.GetFloat() >= 1.0f ) ) + { + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f;// just use old value + } + + //Find the speed of the player + float speed = player->GetLocalVelocity().Length2D(); + float flmaxSpeedDelta = MAX( 0, (gpGlobals->curtime - lastbobtime) * 320.0f ); + + // don't allow too big speed changes + speed = clamp( speed, lastspeed-flmaxSpeedDelta, lastspeed+flmaxSpeedDelta ); + speed = clamp( speed, -320, 320 ); + + lastspeed = speed; + + //FIXME: This maximum speed value must come from the server. + // MaxSpeed() is not sufficient for dealing with sprinting - jdw + + + + float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); + + bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; + lastbobtime = gpGlobals->curtime; + + //Calculate the vertical bob + cycle = bobtime - (int)(bobtime/cl_bobcycle.GetFloat())*cl_bobcycle.GetFloat(); + cycle /= cl_bobcycle.GetFloat(); + + if ( cycle < cl_bobup.GetFloat() ) + { + cycle = M_PI * cycle / cl_bobup.GetFloat(); + } + else + { + cycle = M_PI + M_PI*(cycle-cl_bobup.GetFloat())/(1.0 - cl_bobup.GetFloat()); + } + + g_verticalBob = speed*0.005f; + g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); + + g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); + + //Calculate the lateral bob + cycle = bobtime - (int)(bobtime/cl_bobcycle.GetFloat()*2)*cl_bobcycle.GetFloat()*2; + cycle /= cl_bobcycle.GetFloat()*2; + + if ( cycle < cl_bobup.GetFloat() ) + { + cycle = M_PI * cycle / cl_bobup.GetFloat(); + } + else + { + cycle = M_PI + M_PI*(cycle-cl_bobup.GetFloat())/(1.0 - cl_bobup.GetFloat()); + } + + g_lateralBob = speed*0.005f; + g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); + g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f; + + } + + //----------------------------------------------------------------------------- + // Purpose: + // Input : &origin - + // &angles - + // viewmodelindex - + //----------------------------------------------------------------------------- + void CWeaponCSBase::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) + { + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + CalcViewmodelBob(); + + // Apply bob, but scaled down to 40% + VectorMA( origin, g_verticalBob * 0.4f, forward, origin ); + + // Z bob a bit more + origin[2] += g_verticalBob * 0.1f; + + // bob the angles + angles[ ROLL ] += g_verticalBob * 0.5f; + angles[ PITCH ] -= g_verticalBob * 0.4f; + + angles[ YAW ] -= g_lateralBob * 0.3f; + + // VectorMA( origin, g_lateralBob * 0.2f, right, origin ); + } + +#else + + void CWeaponCSBase::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) + { + + } + + float CWeaponCSBase::CalcViewmodelBob( void ) + { + return 0.0f; + } + +#endif + +#ifndef CLIENT_DLL +bool CWeaponCSBase::PhysicsSplash( const Vector ¢erPoint, const Vector &normal, float rawSpeed, float scaledSpeed ) +{ + if ( rawSpeed > 20 ) + { + + float size = 4.0f; + if ( !IsPistol() ) + size += 2.0f; + + // adjust splash size based on speed + size += RemapValClamped( rawSpeed, 0, 400, 0, 3 ); + + CEffectData data; + data.m_vOrigin = centerPoint; + data.m_vNormal = normal; + data.m_flScale = random->RandomFloat( size, size + 1.0f ); + + if ( GetWaterType() & CONTENTS_SLIME ) + { + data.m_fFlags |= FX_WATER_IN_SLIME; + } + + DispatchEffect( "gunshotsplash", data ); + + return true; + } + + return false; +} +#endif // !CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPicker - +//----------------------------------------------------------------------------- +void CWeaponCSBase::OnPickedUp( CBaseCombatCharacter *pNewOwner ) +{ +#if !defined( CLIENT_DLL ) + RemoveEffects( EF_ITEM_BLINK ); + + if( pNewOwner->IsPlayer() && pNewOwner->IsAlive() ) + { + m_OnPlayerPickup.FireOutput(pNewOwner, this); + + // Play the pickup sound for 1st-person observers + CRecipientFilter filter; + for ( int i=0; i<gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = UTIL_PlayerByIndex(i); + if ( player && !player->IsAlive() && player->GetObserverMode() == OBS_MODE_IN_EYE ) + { + filter.AddRecipient( player ); + } + } + if ( filter.GetRecipientCount() ) + { + CBaseEntity::EmitSound( filter, pNewOwner->entindex(), "Player.PickupWeapon" ); + } + + // Robin: We don't want to delete weapons the player has picked up, so + // clear the name of the weapon. This prevents wildcards that are meant + // to find NPCs finding weapons dropped by the NPCs as well. + SetName( NULL_STRING ); + } + + // Someone picked me up, so make it so that I can't be removed. + SetRemoveable( false ); +#endif +} + + +void CWeaponCSBase::UpdateAccuracyPenalty() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + const CCSWeaponInfo& weaponInfo = GetCSWpnData(); + + float fNewPenalty = 0.0f; + + // on ladder? + if ( pPlayer->GetMoveType() == MOVETYPE_LADDER ) + { + fNewPenalty += weaponInfo.m_fInaccuracyStand[m_weaponMode] + weaponInfo.m_fInaccuracyLadder[m_weaponMode]; + } + // in the air? +// else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) +// { +// fNewPenalty += weaponInfo.m_fInaccuracyStand[m_weaponMode] + weaponInfo.m_fInaccuracyJump[m_weaponMode]; +// } + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING) ) + { + fNewPenalty += weaponInfo.m_fInaccuracyCrouch[m_weaponMode]; + } + else + { + fNewPenalty += weaponInfo.m_fInaccuracyStand[m_weaponMode]; + } + + if ( m_bInReload ) + { + fNewPenalty += weaponInfo.m_fInaccuracyReload; + } + + if ( fNewPenalty > m_fAccuracyPenalty ) + { + m_fAccuracyPenalty = fNewPenalty; + } + else + { + float fDecayFactor; + + if ( pPlayer->GetMoveType() == MOVETYPE_LADDER ) + { + fDecayFactor = logf(10.0f) / weaponInfo.m_fRecoveryTimeStand; + } + else if ( !FBitSet(pPlayer->GetFlags(), FL_ONGROUND) ) // in air + { + // enforce a large recovery speed penalty (300%) for players in the air; this helps to provide + // comparable in-air accuracy to the old weapon model + fDecayFactor = logf(10.0f) / (weaponInfo.m_fRecoveryTimeCrouch * 3.0f); + } + else if ( FBitSet(pPlayer->GetFlags(), FL_DUCKING) ) + { + fDecayFactor = logf(10.0f) / weaponInfo.m_fRecoveryTimeCrouch; + } + else + { + fDecayFactor = logf(10.0f) / weaponInfo.m_fRecoveryTimeStand; + } + m_fAccuracyPenalty = Lerp(expf(TICK_INTERVAL * -fDecayFactor), fNewPenalty, (float)m_fAccuracyPenalty); + } +} + + +const float kJumpVelocity = sqrtf(2.0f * 800.0f * 57.0f); // see CCSGameMovement::CheckJumpButton() + +void CWeaponCSBase::OnJump( float fImpulse ) +{ + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyJump[m_weaponMode] * fImpulse / kJumpVelocity; +} + +void CWeaponCSBase::OnLand( float fVelocity ) +{ + float fPenalty = GetCSWpnData().m_fInaccuracyLand[m_weaponMode] * fVelocity / kJumpVelocity; + m_fAccuracyPenalty += fPenalty; + +/* + // this bit of code is only if we want to punch the player view on all landings + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + QAngle angle = pPlayer->GetPunchAngle(); + float fVKick = RAD2DEG(asinf(fPenalty)) * 0.4f; + float fHKick = SharedRandomFloat("LandPunchAngleYaw", -1.0f, +1.0f) * fVKick * 0.1f; + + angle.x += fVKick; // pitch + angle.y += fHKick; // yaw + pPlayer->SetPunchAngle( angle ); +*/ +} diff --git a/game/shared/cstrike/weapon_csbase.h b/game/shared/cstrike/weapon_csbase.h new file mode 100644 index 0000000..9a81b61 --- /dev/null +++ b/game/shared/cstrike/weapon_csbase.h @@ -0,0 +1,289 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_CSBASE_H +#define WEAPON_CSBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cs_playeranimstate.h" +#include "cs_weapon_parse.h" + + +#if defined( CLIENT_DLL ) + #define CWeaponCSBase C_WeaponCSBase +#endif + +extern CSWeaponID AliasToWeaponID( const char *alias ); +extern const char *WeaponIDToAlias( int id ); +extern const char *GetTranslatedWeaponAlias( const char *alias); +extern const char * GetWeaponAliasFromTranslated(const char *translatedAlias); +extern bool IsPrimaryWeapon( CSWeaponID id ); +extern bool IsSecondaryWeapon( CSWeaponID id ); +extern int GetShellForAmmoType( const char *ammoname ); + +#define SHIELD_VIEW_MODEL "models/weapons/v_shield.mdl" +#define SHIELD_WORLD_MODEL "models/weapons/w_shield.mdl" + +class CCSPlayer; + +// These are the names of the ammo types that go in the CAmmoDefs and that the +// weapon script files reference. +#define BULLET_PLAYER_50AE "BULLET_PLAYER_50AE" +#define BULLET_PLAYER_762MM "BULLET_PLAYER_762MM" +#define BULLET_PLAYER_556MM "BULLET_PLAYER_556MM" +#define BULLET_PLAYER_556MM_BOX "BULLET_PLAYER_556MM_BOX" +#define BULLET_PLAYER_338MAG "BULLET_PLAYER_338MAG" +#define BULLET_PLAYER_9MM "BULLET_PLAYER_9MM" +#define BULLET_PLAYER_BUCKSHOT "BULLET_PLAYER_BUCKSHOT" +#define BULLET_PLAYER_45ACP "BULLET_PLAYER_45ACP" +#define BULLET_PLAYER_357SIG "BULLET_PLAYER_357SIG" +#define BULLET_PLAYER_57MM "BULLET_PLAYER_57MM" +#define AMMO_TYPE_HEGRENADE "AMMO_TYPE_HEGRENADE" +#define AMMO_TYPE_FLASHBANG "AMMO_TYPE_FLASHBANG" +#define AMMO_TYPE_SMOKEGRENADE "AMMO_TYPE_SMOKEGRENADE" + +#define CROSSHAIR_CONTRACT_PIXELS_PER_SECOND 7.0f + +// Given an ammo type (like from a weapon's GetPrimaryAmmoType()), this compares it +// against the ammo name you specify. +// MIKETODO: this should use indexing instead of searching and strcmp()'ing all the time. +bool IsAmmoType( int iAmmoType, const char *pAmmoName ); + +enum CSWeaponMode +{ + Primary_Mode = 0, + Secondary_Mode, + WeaponMode_MAX +}; + +#if defined( CLIENT_DLL ) + + //-------------------------------------------------------------------------------------------------------------- + /** + * Returns the client's ID_* value for the currently owned weapon, or ID_NONE if no weapon is owned + */ + CSWeaponID GetClientWeaponID( bool primary ); + +#endif + + //-------------------------------------------------------------------------------------------------------------- + CCSWeaponInfo * GetWeaponInfo( CSWeaponID weaponID ); + + +class CWeaponCSBase : public CBaseCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponCSBase, CBaseCombatWeapon ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponCSBase(); + + #ifdef GAME_DLL + DECLARE_DATADESC(); + + virtual void CheckRespawn(); + virtual CBaseEntity* Respawn(); + + virtual const Vector& GetBulletSpread(); + virtual float GetDefaultAnimSpeed(); + + virtual void BulletWasFired( const Vector &vecStart, const Vector &vecEnd ); + virtual bool ShouldRemoveOnRoundRestart(); + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Handle round restart processing for the weapon. + //============================================================================= + + virtual void OnRoundRestart(); + + //============================================================================= + // HPE_END + //============================================================================= + + virtual bool DefaultReload( int iClipSize1, int iClipSize2, int iActivity ); + + void SendReloadEvents(); + + void Materialize(); + void AttemptToMaterialize(); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual bool IsRemoveable(); + + #endif + + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); + virtual void AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ); + virtual float CalcViewmodelBob( void ); + // All predicted weapons need to implement and return true + virtual bool IsPredicted() const; + + // Pistols reset m_iShotsFired to 0 when the attack button is released. + bool IsPistol() const; + + virtual bool IsFullAuto() const; + + CCSPlayer* GetPlayerOwner() const; + + virtual float GetMaxSpeed() const; // What's the player's max speed while holding this weapon. + + // Get CS-specific weapon data. + CCSWeaponInfo const &GetCSWpnData() const; + + // Get specific CS weapon ID (ie: WEAPON_AK47, etc) + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_NONE; } + + // return true if this weapon is an instance of the given weapon type (ie: "IsA" WEAPON_GLOCK) + bool IsA( CSWeaponID id ) const { return GetWeaponID() == id; } + + // return true if this weapon is a kinf of the given weapon type (ie: "IsKindOf" WEAPONTYPE_RIFLE ) + bool IsKindOf( CSWeaponType type ) const { return GetCSWpnData().m_WeaponType == type; } + + // return true if this weapon has a silencer equipped + virtual bool IsSilenced( void ) const { return false; } + + virtual void SetWeaponModelIndex( const char *pName ); + virtual void OnPickedUp( CBaseCombatCharacter *pNewOwner ); + + virtual void OnJump( float fImpulse ); + virtual void OnLand( float fVelocity ); + +public: + #if defined( CLIENT_DLL ) + + virtual void ProcessMuzzleFlashEvent(); + virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); + virtual bool ShouldPredict(); + virtual void DrawCrosshair(); + virtual void OnDataChanged( DataUpdateType_t type ); + + virtual int GetMuzzleAttachment( void ); + virtual bool HideViewModelWhenZoomed( void ) { return true; } + + float m_flCrosshairDistance; + int m_iAmmoLastCheck; + int m_iAlpha; + int m_iScopeTextureID; + int m_iCrosshairTextureID; // for white additive texture + + virtual int GetMuzzleFlashStyle( void ); + + #else + + virtual bool Reload(); + virtual void Spawn(); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + + virtual bool PhysicsSplash( const Vector ¢erPoint, const Vector &normal, float rawSpeed, float scaledSpeed ); + + #endif + + bool IsUseable(); + virtual bool CanDeploy( void ); + virtual void UpdateShieldState( void ); + virtual bool SendWeaponAnim( int iActivity ); + virtual void SecondaryAttack( void ); + virtual void Precache( void ); + virtual bool CanBeSelected( void ); + virtual Activity GetDeployActivity( void ); + virtual bool DefaultDeploy( char *szViewModel, char *szWeaponModel, int iActivity, char *szAnimExt ); + virtual void DefaultTouch( CBaseEntity *pOther ); // default weapon touch + virtual bool DefaultPistolReload(); + + virtual bool Deploy(); + virtual void Drop( const Vector &vecVelocity ); + bool PlayEmptySound(); + virtual void ItemPostFrame(); + virtual void ItemBusyFrame(); + virtual const char *GetViewModel( int viewmodelindex = 0 ) const; + + + bool m_bDelayFire; // This variable is used to delay the time between subsequent button pressing. + float m_flAccuracy; + + //============================================================================= + // HPE_BEGIN: + // [pfreese] new accuracy model + //============================================================================= + + CNetworkVar( CSWeaponMode, m_weaponMode); + + virtual float GetInaccuracy() const; + virtual float GetSpread() const; + + virtual void UpdateAccuracyPenalty(); + + CNetworkVar( float, m_fAccuracyPenalty ); + + //============================================================================= + // HPE_END + //============================================================================= + + void SetExtraAmmoCount( int count ) { m_iExtraPrimaryAmmo = count; } + int GetExtraAmmoCount( void ) { return m_iExtraPrimaryAmmo; } + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] Accessors for the previous owner of the gun + void SetPreviousOwner(CCSPlayer* player) { m_prevOwner = player; } + CCSPlayer* GetPreviousOwner() { return m_prevOwner; } + + // [tj] Accessors for the donor system + void SetDonor(CCSPlayer* player) { m_donor = player; } + CCSPlayer* GetDonor() { return m_donor; } + void SetDonated(bool donated) { m_donated = true;} + bool GetDonated() { return m_donated; } + + //[dwenger] Accessors for the prior owner list + void AddToPriorOwnerList(CCSPlayer* pPlayer); + bool IsAPriorOwner(CCSPlayer* pPlayer); + + //============================================================================= + // HPE_END + //============================================================================= + +protected: + + float CalculateNextAttackTime( float flCycleTime ); + +private: + + float m_flDecreaseShotsFired; + + CWeaponCSBase( const CWeaponCSBase & ); + + int m_iExtraPrimaryAmmo; + + float m_nextPrevOwnerTouchTime; + CCSPlayer *m_prevOwner; + + int m_iDefaultExtraAmmo; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [dwenger] track all prior owners of this weapon + CUtlVector< CCSPlayer* > m_PriorOwners; + + // [tj] To keep track of people who drop weapons for teammates during the buy round + CHandle<CCSPlayer> m_donor; + bool m_donated; + + //============================================================================= + // HPE_END + //============================================================================= +}; + +extern ConVar weapon_accuracy_model; + +#endif // WEAPON_CSBASE_H diff --git a/game/shared/cstrike/weapon_csbasegun.cpp b/game/shared/cstrike/weapon_csbasegun.cpp new file mode 100644 index 0000000..6ce0f3d --- /dev/null +++ b/game/shared/cstrike/weapon_csbasegun.cpp @@ -0,0 +1,218 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" +#include "fx_cs_shared.h" + +#ifdef CLIENT_DLL + #include "c_cs_player.h" +#else + #include "cs_player.h" +#endif + + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCSBaseGun, DT_WeaponCSBaseGun ) + +BEGIN_NETWORK_TABLE( CWeaponCSBaseGun, DT_WeaponCSBaseGun ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponCSBaseGun ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_csbase_gun, CWeaponCSBaseGun ); + + + +CWeaponCSBaseGun::CWeaponCSBaseGun() +{ +} + +void CWeaponCSBaseGun::Spawn() +{ + m_flAccuracy = 0.2; + m_bDelayFire = false; + m_zoomFullyActiveTime = -1.0f; + + BaseClass::Spawn(); +} + + +bool CWeaponCSBaseGun::Deploy() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + m_flAccuracy = 0.2; + pPlayer->m_iShotsFired = 0; + m_bDelayFire = false; + m_zoomFullyActiveTime = -1.0f; + + return BaseClass::Deploy(); +} + +void CWeaponCSBaseGun::ItemPostFrame() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( !pPlayer ) + return; + + //GOOSEMAN : Return zoom level back to previous zoom level before we fired a shot. This is used only for the AWP. + // And Scout. + if ( (m_flNextPrimaryAttack <= gpGlobals->curtime) && (pPlayer->m_bResumeZoom == TRUE) ) + { + pPlayer->m_bResumeZoom = false; + + if ( m_iClip1 != 0 || ( GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD ) ) + { + m_weaponMode = Secondary_Mode; + pPlayer->SetFOV( pPlayer, pPlayer->m_iLastZoom, 0.05f ); + m_zoomFullyActiveTime = gpGlobals->curtime + 0.05f;// Make sure we think that we are zooming on the server so we don't get instant acc bonus + } + } + + BaseClass::ItemPostFrame(); +} + + +void CWeaponCSBaseGun::PrimaryAttack() +{ + // Derived classes should implement this and call CSBaseGunFire. + Assert( false ); +} + +bool CWeaponCSBaseGun::CSBaseGunFire( float flCycleTime, CSWeaponMode weaponMode ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + const CCSWeaponInfo &pCSInfo = GetCSWpnData(); + + m_bDelayFire = true; + + if ( m_iClip1 > 0 ) + { + pPlayer->m_iShotsFired++; + + // These modifications feed back into flSpread eventually. + if ( pCSInfo.m_flAccuracyDivisor != -1 ) + { + int iShotsFired = pPlayer->m_iShotsFired; + + if ( pCSInfo.m_bAccuracyQuadratic ) + iShotsFired = iShotsFired * iShotsFired; + else + iShotsFired = iShotsFired * iShotsFired * iShotsFired; + + m_flAccuracy = ( iShotsFired / pCSInfo.m_flAccuracyDivisor ) + pCSInfo.m_flAccuracyOffset; + + if ( m_flAccuracy > pCSInfo.m_flMaxInaccuracy ) + m_flAccuracy = pCSInfo.m_flMaxInaccuracy; + } + } + else + { + m_flAccuracy = 0; + + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + + // NOTE[pmf]: we don't want to actually play the dry fire animations, as most seem to depict the weapon actually firing. + // SendWeaponAnim( ACT_VM_DRYFIRE ); + + m_bFireOnEmpty = false; + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + } + + return false; + } + + float flCurAttack = CalculateNextAttackTime( flCycleTime ); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + m_iClip1--; + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + weaponMode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread(), + flCurAttack ); + + DoFireEffects(); + + SetWeaponIdleTime( gpGlobals->curtime + GetCSWpnData().m_flTimeToIdleAfterFire ); + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[weaponMode]; + + return true; +} + + +void CWeaponCSBaseGun::DoFireEffects() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer ) + pPlayer->DoMuzzleFlash(); +} + + +bool CWeaponCSBaseGun::Reload() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if (pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0) + return false; + + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), 0.0f ); + + int iResult = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); + if ( !iResult ) + return false; + + pPlayer->SetAnimation( PLAYER_RELOAD ); + + if ((iResult) && (pPlayer->GetFOV() != pPlayer->GetDefaultFOV())) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() ); + } + + m_flAccuracy = 0.2; + pPlayer->m_iShotsFired = 0; + m_bDelayFire = false; + + pPlayer->SetShieldDrawnState( false ); + return true; +} + +void CWeaponCSBaseGun::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + // only idle if the slid isn't back + if ( m_iClip1 != 0 ) + { + SetWeaponIdleTime( gpGlobals->curtime + GetCSWpnData().m_flIdleInterval ); + SendWeaponAnim( ACT_VM_IDLE ); + } +} diff --git a/game/shared/cstrike/weapon_csbasegun.h b/game/shared/cstrike/weapon_csbasegun.h new file mode 100644 index 0000000..6781340 --- /dev/null +++ b/game/shared/cstrike/weapon_csbasegun.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_CSBASE_GUN_H +#define WEAPON_CSBASE_GUN_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_csbase.h" + + +// This is the base class for pistols and rifles. +#if defined( CLIENT_DLL ) + + #define CWeaponCSBaseGun C_WeaponCSBaseGun + +#else +#endif + + +class CWeaponCSBaseGun : public CWeaponCSBase +{ +public: + + DECLARE_CLASS( CWeaponCSBaseGun, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponCSBaseGun(); + + virtual void PrimaryAttack(); + virtual void Spawn(); + virtual bool Deploy(); + virtual bool Reload(); + virtual void WeaponIdle(); + + // Derived classes call this to fire a bullet. + bool CSBaseGunFire( float flCycleTime, CSWeaponMode weaponMode ); + + // Usually plays the shot sound. Guns with silencers can play different sounds. + virtual void DoFireEffects(); + virtual void ItemPostFrame(); + +protected: + float m_zoomFullyActiveTime; + +private: + + CWeaponCSBaseGun( const CWeaponCSBaseGun & ); +}; + + +#endif // WEAPON_CSBASE_GUN_H diff --git a/game/shared/cstrike/weapon_deagle.cpp b/game/shared/cstrike/weapon_deagle.cpp new file mode 100644 index 0000000..1d0d95c --- /dev/null +++ b/game/shared/cstrike/weapon_deagle.cpp @@ -0,0 +1,235 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "decals.h" +#include "cbase.h" +#include "shake.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CDEagle C_DEagle + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + + +#define DEAGLE_WEIGHT 7 +#define DEAGLE_MAX_CLIP 7 + +enum deagle_e { + DEAGLE_IDLE1 = 0, + DEAGLE_SHOOT1, + DEAGLE_SHOOT2, + DEAGLE_SHOOT_EMPTY, + DEAGLE_RELOAD, + DEAGLE_DRAW, +}; + + + +class CDEagle : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CDEagle, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CDEagle(); + + void Spawn(); + + void PrimaryAttack(); + virtual bool Deploy(); + bool Reload(); + void WeaponIdle(); + void MakeBeam (); + void BeamUpdate (); + virtual bool UseDecrement() {return true;}; + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_DEAGLE; } + +public: + float m_flLastFire; + +private: + CDEagle( const CDEagle & ); +}; + + + +IMPLEMENT_NETWORKCLASS_ALIASED( DEagle, DT_WeaponDEagle ) + +BEGIN_NETWORK_TABLE( CDEagle, DT_WeaponDEagle ) +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CDEagle ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_deagle, CDEagle ); +PRECACHE_WEAPON_REGISTER( weapon_deagle ); + + + +CDEagle::CDEagle() +{ + m_flLastFire = gpGlobals->curtime; +} + + +void CDEagle::Spawn() +{ + BaseClass::Spawn(); + m_flAccuracy = 0.9; +} + + +bool CDEagle::Deploy() +{ + m_flAccuracy = 0.9; + return BaseClass::Deploy(); +} + +float CDEagle::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.5f * (1 - m_flAccuracy); + + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.25f * (1 - m_flAccuracy); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.115f * (1 - m_flAccuracy); + + else + return 0.13f * (1 - m_flAccuracy); + } + else + return BaseClass::GetInaccuracy(); +} + +void CDEagle::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.35)*(0.4 - ( gpGlobals->curtime - m_flLastFire ) ); + + if (m_flAccuracy > 0.9) + m_flAccuracy = 0.9; + else if (m_flAccuracy < 0.55) + m_flAccuracy = 0.55; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_iClip1--; + + pPlayer->DoMuzzleFlash(); + + if( m_iClip1 > 0 ) + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + else + SendWeaponAnim( ACT_VM_DRYFIRE ); + + //SetPlayerShieldAnim(); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + //pPlayer->m_iWeaponVolume = BIG_EXPLOSION_VOLUME; + //pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread()); + + m_flNextPrimaryAttack = gpGlobals->curtime + GetCSWpnData().m_flCycleTime; + + if ( !m_iClip1 && pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0 ) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 1.8 ); + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + QAngle punchAngle = pPlayer->GetPunchAngle(); + punchAngle.x -= 2; + pPlayer->SetPunchAngle( punchAngle ); + + //ResetPlayerShieldAnim(); +} + + +bool CDEagle::Reload() +{ + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.9; + return true; +} + +void CDEagle::WeaponIdle() +{ + if ( m_flTimeWeaponIdle > gpGlobals->curtime ) + return; + + SetWeaponIdleTime( gpGlobals->curtime + 20 ); + + if (m_iClip1 != 0) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + + //if ( FBitSet(m_iWeaponState, WPNSTATE_SHIELD_DRAWN) ) + // SendWeaponAnim( SHIELDGUN_DRAWN_IDLE, UseDecrement() ? 1:0 ); +} + diff --git a/game/shared/cstrike/weapon_elite.cpp b/game/shared/cstrike/weapon_elite.cpp new file mode 100644 index 0000000..b77b87e --- /dev/null +++ b/game/shared/cstrike/weapon_elite.cpp @@ -0,0 +1,317 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponElite C_WeaponElite + #include "c_cs_player.h" + #include "c_te_effect_dispatch.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponElite : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponElite, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponElite(); + + virtual void Spawn(); + virtual void Precache(); + + virtual void PrimaryAttack(); + virtual bool Deploy(); + + virtual bool Reload(); + + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_ELITE; } + +#ifdef CLIENT_DLL + virtual int GetMuzzleAttachment( void ); + virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); +#endif + + virtual const char *GetWorldModel( void ) const; + virtual int GetWorldModelIndex( void ); + +protected: + bool FiringLeft() const; + +private: + + CWeaponElite( const CWeaponElite & ); + float m_flLastFire; + + int m_droppedModelIndex; + bool m_inPrecache; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponElite, DT_WeaponElite ) + +BEGIN_NETWORK_TABLE( CWeaponElite, DT_WeaponElite ) +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponElite ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_elite, CWeaponElite ); +PRECACHE_WEAPON_REGISTER( weapon_elite ); + +CWeaponElite::CWeaponElite() +{ + m_flLastFire = gpGlobals->curtime; + m_inPrecache = false; +} + + +void CWeaponElite::Spawn( ) +{ + m_flAccuracy = 0.88; + BaseClass::Spawn(); +} + + +void CWeaponElite::Precache() +{ + m_inPrecache = true; + BaseClass::Precache(); + + PrecacheModel( "models/weapons/w_eq_eholster_elite.mdl" ); + PrecacheModel( "models/weapons/w_eq_eholster.mdl" ); + PrecacheModel( "models/weapons/w_pist_elite_single.mdl" ); + m_droppedModelIndex = CBaseEntity::PrecacheModel( GetCSWpnData().m_szDroppedModel ); + m_inPrecache = false; +} + +bool CWeaponElite::Deploy( ) +{ + m_flAccuracy = 0.88; + return BaseClass::Deploy(); +} + +int CWeaponElite::GetWorldModelIndex( void ) +{ + if ( GetOwner() || m_inPrecache ) + { + return m_iWorldModelIndex; + } + else + { + return m_droppedModelIndex; + } +} + +const char * CWeaponElite::GetWorldModel( void ) const +{ + if ( GetOwner() || m_inPrecache ) + { + return BaseClass::GetWorldModel(); + } + else + { + return GetCSWpnData().m_szDroppedModel; + } +} + + +bool CWeaponElite::FiringLeft() const +{ + // fire left-hand gun with even number of bullets left + return (m_iClip1 & 1) == 0; +} + + + +float CWeaponElite::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.3f * (1 - m_flAccuracy); + + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.175f * (1 - m_flAccuracy); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.08f * (1 - m_flAccuracy); + + else + return 0.1f * (1 - m_flAccuracy); + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponElite::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.275)*(0.325 - (gpGlobals->curtime - m_flLastFire)); + + if (m_flAccuracy > 0.88) + m_flAccuracy = 0.88; + else if (m_flAccuracy < 0.55) + m_flAccuracy = 0.55; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_iClip1--; + + pPlayer->DoMuzzleFlash(); + + //SetPlayerShieldAnim(); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + FiringLeft() ? Secondary_Mode : Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread()); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + GetCSWpnData().m_flCycleTime; + + if (!m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 2.5 ); + + if ( FiringLeft() ) + { + if ( m_iClip1 > 0 ) + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + else + SendWeaponAnim( ACT_VM_DRYFIRE ); + } + else + { + if ( m_iClip1 > 1 ) + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + else + SendWeaponAnim( ACT_VM_DRYFIRE_LEFT ); + } + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + QAngle punchAngle = pPlayer->GetPunchAngle(); + punchAngle.x -= 2; + pPlayer->SetPunchAngle( punchAngle ); + + //ResetPlayerShieldAnim(); +} + + +bool CWeaponElite::Reload() +{ + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.88; + return true; +} + +void CWeaponElite::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + +/* + // switching to the idle with the slide back on the right pistol causes animation pops transitioning + // from/to the depot/holster animations. The pop transition to the reload is less noticeable, so + // we'll live with that one + + if ( m_iClip1 == 1 ) + { + SendWeaponAnim( ACT_VM_IDLE_EMPTY_LEFT ); + } +*/ + + // only idle if either slide isn't back + if ( m_iClip1 >= 2 ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } +} + +#ifdef CLIENT_DLL + + bool CWeaponElite::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) + { + if( event == 5001 ) + { + C_CSPlayer *pPlayer = ToCSPlayer( GetOwner() ); + if( pPlayer && pPlayer->GetFOV() < pPlayer->GetDefaultFOV() && HideViewModelWhenZoomed() ) + return true; + + CEffectData data; + data.m_fFlags = 0; + data.m_hEntity = pViewModel->GetRefEHandle(); + data.m_nAttachmentIndex = FiringLeft() ? 1 : 2; // toggle muzzle flash + data.m_flScale = GetCSWpnData().m_flMuzzleScale; + + DispatchEffect( "CS_MuzzleFlash", data ); + + return true; + } + + return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); + } + + int CWeaponElite::GetMuzzleAttachment( void ) + { + return LookupAttachment( FiringLeft() ? "muzzle_flash_l" : "muzzle_flash_r" ); + } + +#endif
\ No newline at end of file diff --git a/game/shared/cstrike/weapon_famas.cpp b/game/shared/cstrike/weapon_famas.cpp new file mode 100644 index 0000000..1f22cad --- /dev/null +++ b/game/shared/cstrike/weapon_famas.cpp @@ -0,0 +1,243 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponFamas C_WeaponFamas + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponFamas : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponFamas, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponFamas(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + virtual bool Deploy(); + + virtual float GetInaccuracy() const; + + virtual void ItemPostFrame(); + + void FamasFire( float flSpread, bool bFireBurst ); + void FireRemaining(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_FAMAS; } + +private: + + CWeaponFamas( const CWeaponFamas & ); + CNetworkVar( bool, m_bBurstMode ); + CNetworkVar( int, m_iBurstShotsRemaining ); + float m_fNextBurstShot; // time to shoot the next bullet in burst fire mode +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFamas, DT_WeaponFamas ) + +BEGIN_NETWORK_TABLE( CWeaponFamas, DT_WeaponFamas ) + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bBurstMode ) ), + RecvPropInt( RECVINFO( m_iBurstShotsRemaining ) ), + #else + SendPropBool( SENDINFO( m_bBurstMode ) ), + SendPropInt( SENDINFO( m_iBurstShotsRemaining ) ), + #endif +END_NETWORK_TABLE() + +#if defined(CLIENT_DLL) +BEGIN_PREDICTION_DATA( CWeaponFamas ) +DEFINE_PRED_FIELD( m_iBurstShotsRemaining, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +DEFINE_PRED_FIELD( m_fNextBurstShot, FIELD_FLOAT, 0 ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_famas, CWeaponFamas ); +PRECACHE_WEAPON_REGISTER( weapon_famas ); + + +const float kFamasBurstCycleTime = 0.075f; + + +CWeaponFamas::CWeaponFamas() +{ + m_bBurstMode = false; +} + + +bool CWeaponFamas::Deploy( ) +{ + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; + m_flAccuracy = 0.9f; + + return BaseClass::Deploy(); +} + + +// Secondary attack could be three-round burst mode +void CWeaponFamas::SecondaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( m_bBurstMode ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#Switch_To_FullAuto" ); + m_bBurstMode = false; + m_weaponMode = Primary_Mode; + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#Switch_To_BurstFire" ); + m_bBurstMode = true; + m_weaponMode = Secondary_Mode; + } + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3; +} + +float CWeaponFamas::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + float fAutoPenalty = m_bBurstMode ? 0.0f : 0.01f; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) // if player is in air + return 0.03f + 0.3f * m_flAccuracy + fAutoPenalty; + + else if ( pPlayer->GetAbsVelocity().Length2D() > 140 ) // if player is moving + return 0.03f + 0.07f * m_flAccuracy + fAutoPenalty; + /* new code */ + else + return 0.02f * m_flAccuracy + fAutoPenalty; + } + else + return BaseClass::GetInaccuracy(); +} + + +void CWeaponFamas::ItemPostFrame() +{ + if ( m_iBurstShotsRemaining > 0 && gpGlobals->curtime >= m_fNextBurstShot ) + FireRemaining(); + + BaseClass::ItemPostFrame(); +} + + + +// GOOSEMAN : FireRemaining used by Glock18 +void CWeaponFamas::FireRemaining() +{ + m_iClip1--; + + if (m_iClip1 < 0) + { + m_iClip1 = 0; + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; + return; + } + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + Error( "!pPlayer" ); + + // Famas burst mode + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Secondary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread(), + m_fNextBurstShot); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + pPlayer->DoMuzzleFlash(); + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + pPlayer->m_iShotsFired++; + + --m_iBurstShotsRemaining; + if ( m_iBurstShotsRemaining > 0 ) + m_fNextBurstShot += kFamasBurstCycleTime; + else + m_fNextBurstShot = 0.0f; + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Secondary_Mode]; +} + + +void CWeaponFamas::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // don't fire underwater + if (pPlayer->GetWaterLevel() == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.15; + return; + } + + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + // change a few things if we're in burst mode + if ( m_bBurstMode ) + { + flCycleTime = 0.55f; + m_iBurstShotsRemaining = 2; + m_fNextBurstShot = gpGlobals->curtime + kFamasBurstCycleTime; + } + + if ( !CSBaseGunFire( flCycleTime, m_weaponMode ) ) + return; + + if ( pPlayer->GetAbsVelocity().Length2D() > 5 ) + pPlayer->KickBack ( 1, 0.45, 0.275, 0.05, 4, 2.5, 7 ); + + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack ( 1.25, 0.45, 0.22, 0.18, 5.5, 4, 5 ); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack ( 0.575, 0.325, 0.2, 0.011, 3.25, 2, 8 ); + + else + pPlayer->KickBack ( 0.625, 0.375, 0.25, 0.0125, 3.5, 2.25, 8 ); +} + + diff --git a/game/shared/cstrike/weapon_fiveseven.cpp b/game/shared/cstrike/weapon_fiveseven.cpp new file mode 100644 index 0000000..9b94191 --- /dev/null +++ b/game/shared/cstrike/weapon_fiveseven.cpp @@ -0,0 +1,202 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + +#if defined( CLIENT_DLL ) + + #define CWeaponFiveSeven C_WeaponFiveSeven + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponFiveSeven : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponFiveSeven, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponFiveSeven(); + + virtual void Spawn(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + virtual bool Deploy(); + + virtual bool Reload(); + + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_FIVESEVEN; } + +private: + + CWeaponFiveSeven( const CWeaponFiveSeven & ); + + float m_flLastFire; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFiveSeven, DT_WeaponFiveSeven ) + +BEGIN_NETWORK_TABLE( CWeaponFiveSeven, DT_WeaponFiveSeven ) +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponFiveSeven ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_fiveseven, CWeaponFiveSeven ); +PRECACHE_WEAPON_REGISTER( weapon_fiveseven ); + + + +CWeaponFiveSeven::CWeaponFiveSeven() +{ + m_flLastFire = gpGlobals->curtime; +} + + +void CWeaponFiveSeven::Spawn( ) +{ + BaseClass::Spawn(); + + m_flAccuracy = 0.92; +} + +bool CWeaponFiveSeven::Deploy() +{ + m_flAccuracy = 0.92; + return BaseClass::Deploy(); +} + + +float CWeaponFiveSeven::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.5f * (1 - m_flAccuracy); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.255f * (1 - m_flAccuracy); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.075f * (1 - m_flAccuracy); + else + return 0.15f * (1 - m_flAccuracy); + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponFiveSeven::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.25)*(0.275 - (gpGlobals->curtime - m_flLastFire)); + + if (m_flAccuracy > 0.92) + m_flAccuracy = 0.92; + else if (m_flAccuracy < 0.725) + m_flAccuracy = 0.725; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_iClip1--; + pPlayer->DoMuzzleFlash(); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread()); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + GetCSWpnData().m_flCycleTime; + + if (!m_iClip1 && pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 2 ); + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= 2; + pPlayer->SetPunchAngle( angle ); +} + + +void CWeaponFiveSeven::SecondaryAttack() +{ +} + + +bool CWeaponFiveSeven::Reload() +{ + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.92; + return true; +} + +void CWeaponFiveSeven::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + // only idle if the slid isn't back + if (m_iClip1 != 0) + { + SetWeaponIdleTime( gpGlobals->curtime + 4 ); + SendWeaponAnim( ACT_VM_IDLE ); + } +} diff --git a/game/shared/cstrike/weapon_flashbang.cpp b/game/shared/cstrike/weapon_flashbang.cpp new file mode 100644 index 0000000..4ce1fa9 --- /dev/null +++ b/game/shared/cstrike/weapon_flashbang.cpp @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "gamerules.h" +#include "npcevent.h" +#include "engine/IEngineSound.h" +#include "weapon_flashbang.h" + + +#ifdef CLIENT_DLL + + +#else + + #include "cs_player.h" + #include "items.h" + #include "flashbang_projectile.h" + +#endif + + +#define GRENADE_TIMER 3.0f //Seconds + + + +IMPLEMENT_NETWORKCLASS_ALIASED( Flashbang, DT_Flashbang ) + +BEGIN_NETWORK_TABLE(CFlashbang, DT_Flashbang) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CFlashbang ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_flashbang, CFlashbang ); +PRECACHE_WEAPON_REGISTER( weapon_flashbang ); + + +#ifndef CLIENT_DLL + + BEGIN_DATADESC( CFlashbang ) + END_DATADESC() + + void CFlashbang::EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ) + { + CFlashbangProjectile::Create( + vecSrc, + vecAngles, + vecVel, + angImpulse, + pPlayer ); + } + +#endif + + diff --git a/game/shared/cstrike/weapon_flashbang.h b/game/shared/cstrike/weapon_flashbang.h new file mode 100644 index 0000000..6f44aae --- /dev/null +++ b/game/shared/cstrike/weapon_flashbang.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_FLASHBANG_H +#define WEAPON_FLASHBANG_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_basecsgrenade.h" + + +#ifdef CLIENT_DLL + #define CFlashbang C_Flashbang +#endif + + +//----------------------------------------------------------------------------- +// Fragmentation grenades +//----------------------------------------------------------------------------- +class CFlashbang : public CBaseCSGrenade +{ +public: + DECLARE_CLASS( CFlashbang, CBaseCSGrenade ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CFlashbang() {} + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_FLASHBANG; } + + +#ifdef CLIENT_DLL + +#else + DECLARE_DATADESC(); + + virtual void EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ); +#endif + + CFlashbang( const CFlashbang & ) {} +}; + + +#endif // WEAPON_FLASHBANG_H diff --git a/game/shared/cstrike/weapon_g3sg1.cpp b/game/shared/cstrike/weapon_g3sg1.cpp new file mode 100644 index 0000000..25aeca5 --- /dev/null +++ b/game/shared/cstrike/weapon_g3sg1.cpp @@ -0,0 +1,213 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponG3SG1 C_WeaponG3SG1 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "KeyValues.h" + +#endif + + +class CWeaponG3SG1 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponG3SG1, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponG3SG1(); + + virtual void Spawn(); + virtual void SecondaryAttack(); + virtual void PrimaryAttack(); + virtual bool Reload(); + virtual bool Deploy(); + + virtual float GetInaccuracy() const; + virtual float GetMaxSpeed(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_G3SG1; } + +private: + CWeaponG3SG1( const CWeaponG3SG1 & ); + + float m_flLastFire; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponG3SG1, DT_WeaponG3SG1 ) + +BEGIN_NETWORK_TABLE( CWeaponG3SG1, DT_WeaponG3SG1 ) +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponG3SG1 ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_g3sg1, CWeaponG3SG1 ); +PRECACHE_WEAPON_REGISTER( weapon_g3sg1 ); + + + +CWeaponG3SG1::CWeaponG3SG1() +{ + m_flLastFire = gpGlobals->curtime; +} + +void CWeaponG3SG1::Spawn() +{ + BaseClass::Spawn(); + m_flAccuracy = 0.98; +} + + +void CWeaponG3SG1::SecondaryAttack() +{ + const float kZoomTime = 0.10f; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + { + pPlayer->SetFOV( pPlayer, 40, kZoomTime ); + m_weaponMode = Secondary_Mode; + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyAltSwitch; + } + else if (pPlayer->GetFOV() == 40) + { + pPlayer->SetFOV( pPlayer, 15, kZoomTime ); + m_weaponMode = Secondary_Mode; + } + else if (pPlayer->GetFOV() == 15) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), kZoomTime ); + m_weaponMode = Primary_Mode; + } + +#ifndef CLIENT_DLL + // If this isn't guarded, the sound will be emitted twice, once by the server and once by the client. + // Let the server play it since if only the client plays it, it's liable to get played twice cause of + // a prediction error. joy. + + //============================================================================= + // HPE_BEGIN: + // [tj] Playing this from the player so that we don't try to play the sound outside the level. + //============================================================================= + if ( GetPlayerOwner() ) + { + GetPlayerOwner()->EmitSound( "Default.Zoom" ); // zoom sound + } + //============================================================================= + // HPE_END + //============================================================================= + // let the bots hear the rifle zoom + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_zoom" ); + if( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3f; + m_zoomFullyActiveTime = gpGlobals->curtime + 0.3; // The worst zoom time from above. +} + +float CWeaponG3SG1::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + float fSpread = 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + fSpread = 0.45f * (1.0f - m_flAccuracy); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + fSpread = 0.15f; + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + fSpread = 0.035f * (1.0f - m_flAccuracy); + else + fSpread = 0.055f * (1.0f - m_flAccuracy); + + // If we are not zoomed in, or we have very recently zoomed and are still transitioning, the bullet diverts more. + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV() || (gpGlobals->curtime < m_zoomFullyActiveTime)) + fSpread += 0.025; + + return fSpread; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponG3SG1::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy = 0.55 + (0.3) * (gpGlobals->curtime - m_flLastFire); + + if (m_flAccuracy > 0.98) + m_flAccuracy = 0.98; + + m_flLastFire = gpGlobals->curtime; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, m_weaponMode ) ) + return; + + // Adjust the punch angle. + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= SharedRandomFloat("G3SG1PunchAngleX", 0.75, 1.75 ) + ( angle.x / 4 ); + angle.y += SharedRandomFloat("G3SG1PunchAngleY", -0.75, 0.75 ); + pPlayer->SetPunchAngle( angle ); +} + + +bool CWeaponG3SG1::Reload() +{ + bool ret = BaseClass::Reload(); + + m_flAccuracy = 0.98; + m_weaponMode = Primary_Mode; + + return ret; +} + +bool CWeaponG3SG1::Deploy() +{ + bool ret = BaseClass::Deploy(); + + m_flAccuracy = 0.98; + m_weaponMode = Primary_Mode; + + return ret; +} + +float CWeaponG3SG1::GetMaxSpeed() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer && pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + return BaseClass::GetMaxSpeed(); + else + return 150; // zoomed in +} diff --git a/game/shared/cstrike/weapon_galil.cpp b/game/shared/cstrike/weapon_galil.cpp new file mode 100644 index 0000000..961d75a --- /dev/null +++ b/game/shared/cstrike/weapon_galil.cpp @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponGalil C_WeaponGalil + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponGalil : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponGalil, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponGalil(); + + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_GALIL; } + +private: + + CWeaponGalil( const CWeaponGalil & ); + + void GalilFire( float flSpread ); + +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponGalil, DT_WeaponGalil ) + +BEGIN_NETWORK_TABLE( CWeaponGalil, DT_WeaponGalil ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponGalil ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_galil, CWeaponGalil ); +PRECACHE_WEAPON_REGISTER( weapon_galil ); + + + +CWeaponGalil::CWeaponGalil() +{ +} + +float CWeaponGalil::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.04f + 0.3f * m_flAccuracy; + else if (pPlayer->GetAbsVelocity().Length2D() > 140) + return 0.04f + 0.07f * m_flAccuracy; + else + return 0.0375f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponGalil::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // don't fire underwater + if (pPlayer->GetWaterLevel() == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.15; + return; + } + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (1.0, 0.45, 0.28, 0.045, 3.75, 3, 7); + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (1.2, 0.5, 0.23, 0.15, 5.5, 3.5, 6); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.6, 0.3, 0.2, 0.0125, 3.25, 2, 7); + else + pPlayer->KickBack (0.65, 0.35, 0.25, 0.015, 3.5, 2.25, 7); +} + + diff --git a/game/shared/cstrike/weapon_glock.cpp b/game/shared/cstrike/weapon_glock.cpp new file mode 100644 index 0000000..a094a4c --- /dev/null +++ b/game/shared/cstrike/weapon_glock.cpp @@ -0,0 +1,369 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponGlock C_WeaponGlock + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponGlock : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponGlock, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponGlock(); + + virtual void Spawn(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + virtual bool Deploy(); + + virtual void ItemPostFrame(); + + void GlockFire( float fSpread, bool bFireBurst ); + void FireRemaining( float fSpread ); + + virtual bool Reload(); + + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_GLOCK; } + +private: + + CWeaponGlock( const CWeaponGlock & ); + + CNetworkVar( bool, m_bBurstMode ); + CNetworkVar( int, m_iBurstShotsRemaining ); // used to keep track of the shots fired during the Glock18 burst fire mode. + float m_fNextBurstShot; // time to shoot the next bullet in burst fire mode + float m_flLastFire; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponGlock, DT_WeaponGlock ) + +BEGIN_NETWORK_TABLE( CWeaponGlock, DT_WeaponGlock ) + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bBurstMode ) ), + RecvPropInt( RECVINFO( m_iBurstShotsRemaining ) ), + #else + SendPropBool( SENDINFO( m_bBurstMode ) ), + SendPropInt( SENDINFO( m_iBurstShotsRemaining ) ), + #endif +END_NETWORK_TABLE() + +#if defined(CLIENT_DLL) +BEGIN_PREDICTION_DATA( CWeaponGlock ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), + DEFINE_PRED_FIELD( m_iBurstShotsRemaining, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_fNextBurstShot, FIELD_FLOAT, 0 ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_glock, CWeaponGlock ); +PRECACHE_WEAPON_REGISTER( weapon_glock ); + +const float kGlockBurstCycleTime = 0.06f; + +CWeaponGlock::CWeaponGlock() +{ + m_bBurstMode = false; + m_flLastFire = gpGlobals->curtime; + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; +} + + +void CWeaponGlock::Spawn( ) +{ + BaseClass::Spawn(); + + m_bBurstMode = false; + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; + m_flAccuracy = 0.9f; +} + +bool CWeaponGlock::Deploy( ) +{ + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; + m_flAccuracy = 0.9f; + + return BaseClass::Deploy(); +} + +void CWeaponGlock::SecondaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( m_bBurstMode ) + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#Switch_To_SemiAuto" ); + m_bBurstMode = false; + m_weaponMode = Primary_Mode; + } + else + { + ClientPrint( pPlayer, HUD_PRINTCENTER, "#Switch_To_BurstFire" ); + m_bBurstMode = true; + m_weaponMode = Secondary_Mode; + } + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3; +} + +float CWeaponGlock::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( m_bBurstMode ) + { + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.2f * (1 - m_flAccuracy); + + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.185f * (1 - m_flAccuracy); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.095f * (1 - m_flAccuracy); + + else + return 0.3f * (1 - m_flAccuracy); + } + else + { + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.0f * (1 - m_flAccuracy); + + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.165f * (1 - m_flAccuracy); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.075f * (1 - m_flAccuracy); + + else + return 0.1f * (1 - m_flAccuracy); + } + } + else + return BaseClass::GetInaccuracy(); +} + + +void CWeaponGlock::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + float flCycleTime = m_bBurstMode ? 0.5f : GetCSWpnData().m_flCycleTime; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.275)*(0.325 - (gpGlobals->curtime - m_flLastFire)); + + if (m_flAccuracy > 0.9) + m_flAccuracy = 0.9; + else if (m_flAccuracy < 0.6) + m_flAccuracy = 0.6; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_iClip1--; + + pPlayer->DoMuzzleFlash(); + + //SetPlayerShieldAnim(); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // non-silenced + //pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + //pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, // wrap it for network traffic so it's the same between client and server + GetInaccuracy(), + GetSpread(), + gpGlobals->curtime); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + flCycleTime; + + if (!m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 2.5f ); + + if ( m_bBurstMode ) + { + // Fire off the next two rounds + m_fNextBurstShot = gpGlobals->curtime + kGlockBurstCycleTime; + m_iBurstShotsRemaining = 2; + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + } + else + { + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + } + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[m_weaponMode]; + + //ResetPlayerShieldAnim(); +} + + +// GOOSEMAN : FireRemaining used by Glock18 + +void CWeaponGlock::FireRemaining( float fSpread ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + Error( "!pPlayer" ); + + if ( m_iBurstShotsRemaining == 0 ) + return; + + if (m_iClip1 <= 0) + { + m_iClip1 = 0; + m_iBurstShotsRemaining = 0; + m_fNextBurstShot = 0.0f; + return; + } + --m_iClip1; + + // TODO FIXME damage = 18, rangemode 0.9 + + float fInaccuracy = GetInaccuracy(); + if ( weapon_accuracy_model.GetInt() == 1 ) + fInaccuracy = 0.05; + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Secondary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, // wrap it for network traffic so it's the same between client and server + fInaccuracy, + GetSpread(), + m_fNextBurstShot); + + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + pPlayer->m_iShotsFired++; + + --m_iBurstShotsRemaining; + + if ( m_iBurstShotsRemaining > 0 ) + m_fNextBurstShot += kGlockBurstCycleTime; + else + m_fNextBurstShot = 0.0; + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Secondary_Mode]; +} + + +void CWeaponGlock::ItemPostFrame() +{ + while ( m_iBurstShotsRemaining > 0 && gpGlobals->curtime >= m_fNextBurstShot ) + { + if ( weapon_accuracy_model.GetInt() == 1 ) + FireRemaining(0.05f); + else + FireRemaining(GetSpread()); + } + + BaseClass::ItemPostFrame(); +} + + +bool CWeaponGlock::Reload() +{ + if ( m_iBurstShotsRemaining != 0 ) + return true; + + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.9; + return true; +} + +void CWeaponGlock::WeaponIdle() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + if ( pPlayer->HasShield() ) + { + SetWeaponIdleTime( gpGlobals->curtime + 20 ); + + //MIKETODO: shields + //if ( FBitSet(m_iWeaponState, WPNSTATE_SHIELD_DRAWN) ) + // SendWeaponAnim( GLOCK18_SHIELD_IDLE, UseDecrement() ? 1:0 ); + } + else + { + // only idle if the slid isn't back + if (m_iClip1 != 0) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} diff --git a/game/shared/cstrike/weapon_hegrenade.cpp b/game/shared/cstrike/weapon_hegrenade.cpp new file mode 100644 index 0000000..e8303d0 --- /dev/null +++ b/game/shared/cstrike/weapon_hegrenade.cpp @@ -0,0 +1,67 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "gamerules.h" +#include "npcevent.h" +#include "engine/IEngineSound.h" +#include "weapon_hegrenade.h" + + +#ifdef CLIENT_DLL + +#else + + #include "cs_player.h" + #include "items.h" + #include "hegrenade_projectile.h" + +#endif + + +#define GRENADE_TIMER 3.0f //Seconds + + + +IMPLEMENT_NETWORKCLASS_ALIASED( HEGrenade, DT_HEGrenade ) + +BEGIN_NETWORK_TABLE(CHEGrenade, DT_HEGrenade) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CHEGrenade ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_hegrenade, CHEGrenade ); +PRECACHE_WEAPON_REGISTER( weapon_hegrenade ); + + +#ifndef CLIENT_DLL + + BEGIN_DATADESC( CHEGrenade ) + END_DATADESC() + + void CHEGrenade::EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ) + { + //============================================================================= + // HPE_BEGIN: + // [Forrest] Start a new bullet group so that damage dealt by the grenade will be counted as a separate hit from damage previously dealt. + //============================================================================= +#ifdef GAME_DLL + CCSPlayer *pCSPlayer = ToCSPlayer( pPlayer ); + if ( pCSPlayer ) + { + pCSPlayer->StartNewBulletGroup(); + } +#endif + //============================================================================= + // HPE_END + //============================================================================= + CHEGrenadeProjectile::Create( vecSrc, vecAngles, vecVel, angImpulse, pPlayer, GRENADE_TIMER ); + } + +#endif + diff --git a/game/shared/cstrike/weapon_hegrenade.h b/game/shared/cstrike/weapon_hegrenade.h new file mode 100644 index 0000000..20e2d99 --- /dev/null +++ b/game/shared/cstrike/weapon_hegrenade.h @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_HEGRENADE_H +#define WEAPON_HEGRENADE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_basecsgrenade.h" + + +#ifdef CLIENT_DLL + + #define CHEGrenade C_HEGrenade + +#endif + +//----------------------------------------------------------------------------- +// Fragmentation grenades +//----------------------------------------------------------------------------- +class CHEGrenade : public CBaseCSGrenade +{ +public: + DECLARE_CLASS( CHEGrenade, CBaseCSGrenade ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CHEGrenade() {} + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_HEGRENADE; } + +#ifdef CLIENT_DLL + +#else + DECLARE_DATADESC(); + + virtual void EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ); + +#endif + + CHEGrenade( const CHEGrenade & ) {} +}; + + +#endif // WEAPON_HEGRENADE_H diff --git a/game/shared/cstrike/weapon_knife.cpp b/game/shared/cstrike/weapon_knife.cpp new file mode 100644 index 0000000..9bc549b --- /dev/null +++ b/game/shared/cstrike/weapon_knife.cpp @@ -0,0 +1,517 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_knife.h" +#include "cs_gamerules.h" + +#if defined( CLIENT_DLL ) + #include "c_cs_player.h" +#else + #include "cs_player.h" + #include "ilagcompensationmanager.h" + #include "cs_gamestats.h" +#endif + + +#define KNIFE_BODYHIT_VOLUME 128 +#define KNIFE_WALLHIT_VOLUME 512 + + +Vector head_hull_mins( -16, -16, -18 ); +Vector head_hull_maxs( 16, 16, 18 ); + +#ifndef CLIENT_DLL + //----------------------------------------------------------------------------- + // Purpose: Only send to local player if this weapon is the active weapon + // Input : *pStruct - + // *pVarData - + // *pRecipients - + // objectID - + // Output : void* + //----------------------------------------------------------------------------- + void* SendProxy_SendActiveLocalKnifeDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) + { + // Get the weapon entity + CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pVarData; + if ( pWeapon ) + { + // Only send this chunk of data to the player carrying this weapon + CBasePlayer *pPlayer = ToBasePlayer( pWeapon->GetOwner() ); + if ( pPlayer /*&& pPlayer->GetActiveWeapon() == pWeapon*/ ) + { + pRecipients->SetOnly( pPlayer->GetClientIndex() ); + return (void*)pVarData; + } + } + + return NULL; + } + REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendActiveLocalKnifeDataTable ); +#endif + +// ----------------------------------------------------------------------------- // +// CKnife tables. +// ----------------------------------------------------------------------------- // + +IMPLEMENT_NETWORKCLASS_ALIASED( Knife, DT_WeaponKnife ) + +BEGIN_NETWORK_TABLE_NOBASE( CKnife, DT_LocalActiveWeaponKnifeData ) + #if !defined( CLIENT_DLL ) + SendPropTime( SENDINFO( m_flSmackTime ) ), + #else + RecvPropTime( RECVINFO( m_flSmackTime ) ), + #endif +END_NETWORK_TABLE() + + +BEGIN_NETWORK_TABLE( CKnife, DT_WeaponKnife ) + #if !defined( CLIENT_DLL ) + SendPropDataTable("LocalActiveWeaponKnifeData", 0, &REFERENCE_SEND_TABLE(DT_LocalActiveWeaponKnifeData), SendProxy_SendActiveLocalKnifeDataTable ), + #else + RecvPropDataTable("LocalActiveWeaponKnifeData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalActiveWeaponKnifeData)), + #endif +END_NETWORK_TABLE() + + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CKnife ) + DEFINE_PRED_FIELD( m_flSmackTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + + +LINK_ENTITY_TO_CLASS( weapon_knife, CKnife ); +PRECACHE_WEAPON_REGISTER( weapon_knife ); + +#ifndef CLIENT_DLL + + BEGIN_DATADESC( CKnife ) + DEFINE_THINKFUNC( Smack ) + END_DATADESC() + +#endif + +// ----------------------------------------------------------------------------- // +// CKnife implementation. +// ----------------------------------------------------------------------------- // + +CKnife::CKnife() +{ +} + + +bool CKnife::HasPrimaryAmmo() +{ + return true; +} + + +bool CKnife::CanBeSelected() +{ + return true; +} + +void CKnife::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Weapon_Knife.Deploy" ); + PrecacheScriptSound( "Weapon_Knife.Slash" ); + PrecacheScriptSound( "Weapon_Knife.Stab" ); + PrecacheScriptSound( "Weapon_Knife.Hit" ); +} + +void CKnife::Spawn() +{ + Precache(); + + m_iClip1 = -1; + BaseClass::Spawn(); +} + + +bool CKnife::Deploy() +{ + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + EmitSound( filter, entindex(), "Weapon_Knife.Deploy" ); + + return BaseClass::Deploy(); +} + +void CKnife::Holster( int skiplocal ) +{ + if ( GetPlayerOwner() ) + { + GetPlayerOwner()->m_flNextAttack = gpGlobals->curtime + 0.5; + } +} + +void CKnife::WeaponAnimation ( int iAnimation ) +{ + /* + int flag; + #if defined( CLIENT_WEAPONS ) + flag = FEV_NOTHOST; + #else + flag = 0; + #endif + + PLAYBACK_EVENT_FULL( flag, pPlayer->edict(), m_usKnife, + 0.0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, + 0.0, + iAnimation, 2, 3, 4 ); + */ +} + +void FindHullIntersection( const Vector &vecSrc, trace_t &tr, const Vector &mins, const Vector &maxs, CBaseEntity *pEntity ) +{ + int i, j, k; + float distance; + Vector minmaxs[2] = {mins, maxs}; + trace_t tmpTrace; + Vector vecHullEnd = tr.endpos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &tmpTrace ); + if ( tmpTrace.fraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &tmpTrace ); + if ( tmpTrace.fraction < 1.0 ) + { + float thisDistance = (tmpTrace.endpos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + + +void CKnife::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer ) + { +#if !defined (CLIENT_DLL) + // Move other players back to history positions based on local player's lag + lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() ); +#endif + SwingOrStab( false ); +#if !defined (CLIENT_DLL) + lagcompensation->FinishLagCompensation( pPlayer ); +#endif + } +} + +void CKnife::SecondaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer && !pPlayer->m_bIsDefusing && !CSGameRules()->IsFreezePeriod() ) + { +#if !defined (CLIENT_DLL) + // Move other players back to history positions based on local player's lag + lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() ); +#endif + SwingOrStab( true ); +#if !defined (CLIENT_DLL) + lagcompensation->FinishLagCompensation( pPlayer ); +#endif + } +} + +#include "effect_dispatch_data.h" + +void CKnife::Smack( void ) +{ + if ( !GetPlayerOwner() ) + return; + + m_trHit.m_pEnt = m_pTraceHitEnt; + + if ( !m_trHit.m_pEnt || (m_trHit.surface.flags & SURF_SKY) ) + return; + + if ( m_trHit.fraction == 1.0 ) + return; + + if ( m_trHit.m_pEnt ) + { + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + + if( m_trHit.m_pEnt->IsPlayer() ) + { + EmitSound( filter, entindex(), m_bStab?"Weapon_Knife.Stab":"Weapon_Knife.Hit" ); + } + else + { + EmitSound( filter, entindex(), "Weapon_Knife.HitWall" ); + } + } + + CEffectData data; + data.m_vOrigin = m_trHit.endpos; + data.m_vStart = m_trHit.startpos; + data.m_nSurfaceProp = m_trHit.surface.surfaceProps; + data.m_nDamageType = DMG_SLASH; + data.m_nHitBox = m_trHit.hitbox; +#ifdef CLIENT_DLL + data.m_hEntity = m_trHit.m_pEnt->GetRefEHandle(); +#else + data.m_nEntIndex = m_trHit.m_pEnt->entindex(); +#endif + + CPASFilter filter( data.m_vOrigin ); + +#ifndef CLIENT_DLL + filter.RemoveRecipient( GetPlayerOwner() ); +#endif + + data.m_vAngles = GetPlayerOwner()->GetAbsAngles(); + data.m_fFlags = 0x1; //IMPACT_NODECAL; + te->DispatchEffect( filter, 0.0, data.m_vOrigin, "KnifeSlash", data ); +} + +void CKnife::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( pPlayer->IsShieldDrawn() ) + return; + + SetWeaponIdleTime( gpGlobals->curtime + 20 ); + + // only idle if the slid isn't back + SendWeaponAnim( ACT_VM_IDLE ); +} + +//============================================================================= +// HPE_BEGIN: +// [tj] Hacky cheat code to control knife damage +//============================================================================= +#ifndef CLIENT_DLL + ConVar KnifeDamageScale("knife_damage_scale", "100", FCVAR_DEVELOPMENTONLY); +#endif + +//============================================================================= +// HPE_END +//============================================================================= + + +bool CKnife::SwingOrStab( bool bStab ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + float fRange = bStab ? 32 : 48; // knife range + + Vector vForward; AngleVectors( pPlayer->EyeAngles(), &vForward ); + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecEnd = vecSrc + vForward * fRange; + + trace_t tr; + UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + + //check for hitting glass - TODO - fix this hackiness, doesn't always line up with what FindHullIntersection returns +#ifndef CLIENT_DLL + CTakeDamageInfo glassDamage( pPlayer, pPlayer, 42.0f, DMG_BULLET | DMG_NEVERGIB ); + TraceAttackToTriggers( glassDamage, tr.startpos, tr.endpos, vForward ); +#endif + + if ( tr.fraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, head_hull_mins, head_hull_maxs, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = tr.m_pEnt; + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer ); + vecEnd = tr.endpos; // This is the point on the actual surface (the hull could have hit space) + } + } + + bool bDidHit = tr.fraction < 1.0f; + +#ifndef CLIENT_DLL + bool bFirstSwing = (m_flNextPrimaryAttack + 0.4) < gpGlobals->curtime; +#endif + + float fPrimDelay, fSecDelay; + + if ( bStab ) + { + SendWeaponAnim( bDidHit ? ACT_VM_HITCENTER : ACT_VM_MISSCENTER ); + + fPrimDelay = fSecDelay = bDidHit ? 1.1f : 1.0f; + + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY ); + } + else // swing + { + SendWeaponAnim( bDidHit ? ACT_VM_HITCENTER : ACT_VM_MISSCENTER ); + + fPrimDelay = bDidHit ? 0.5f : 0.4f; + fSecDelay = bDidHit ? 0.5f : 0.5f; + + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_SECONDARY ); + } + + if ( pPlayer->HasShield() ) + { + fPrimDelay += 0.7f; // 0.7 seconds slower if we carry a shield + fSecDelay += 0.7f; + } + + m_flNextPrimaryAttack = gpGlobals->curtime + fPrimDelay; + m_flNextSecondaryAttack = gpGlobals->curtime + fSecDelay; + SetWeaponIdleTime( gpGlobals->curtime + 2 ); + + if ( !bDidHit ) + { + // play wiff or swish sound + CPASAttenuationFilter filter( this ); + filter.UsePredictionRules(); + EmitSound( filter, entindex(), "Weapon_Knife.Slash" ); + } + +#ifndef CLIENT_DLL + + float flDamage = 0.0f; + if ( bDidHit ) + { + // play thwack, smack, or dong sound + + CBaseEntity *pEntity = tr.m_pEnt; + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + ClearMultiDamage(); + + flDamage = 42.0f; + + if ( bStab ) + { + flDamage = 65.0f; + + if ( pEntity && pEntity->IsPlayer() ) + { + Vector vTragetForward; + + AngleVectors( pEntity->GetAbsAngles(), &vTragetForward ); + + Vector2D vecLOS = (pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin()).AsVector2D(); + Vector2DNormalize( vecLOS ); + + float flDot = vecLOS.Dot( vTragetForward.AsVector2D() ); + + //Triple the damage if we are stabbing them in the back. + if ( flDot > 0.80f ) + flDamage *= 3; + } + } + else + { + if ( bFirstSwing ) + { + // first swing does full damage + flDamage = 20; + } + else + { + // subsequent swings do less + flDamage = 15; + } + } + + //============================================================================= + // HPE_BEGIN: + // [tj] Hacky cheat to lower knife damage for testing + //============================================================================= + + flDamage *= (KnifeDamageScale.GetInt() / 100.0f); + + //============================================================================= + // HPE_END + //============================================================================= + + + CTakeDamageInfo info( pPlayer, pPlayer, flDamage, DMG_BULLET | DMG_NEVERGIB ); + + CalculateMeleeDamageForce( &info, vForward, tr.endpos, 1.0f/flDamage ); + pEntity->DispatchTraceAttack( info, vForward, &tr ); + ApplyMultiDamage(); + } + + CCS_GameStats.Event_KnifeUse( pPlayer, bStab, flDamage ); + +#endif + + if ( bDidHit ) + { + // delay the decal a bit + m_trHit = tr; + + // Store the ent in an EHANDLE, just in case it goes away by the time we get into our think function. + m_pTraceHitEnt = tr.m_pEnt; + + m_bStab = bStab; //store this so we know what hit sound to play + + m_flSmackTime = gpGlobals->curtime + (bStab?0.2f:0.1f); + } + + return bDidHit; +} + +void CKnife::ItemPostFrame( void ) +{ + if( m_flSmackTime > 0 && gpGlobals->curtime > m_flSmackTime ) + { + Smack(); + m_flSmackTime = -1; + } + + BaseClass::ItemPostFrame(); +} + +bool CKnife::CanDrop() +{ + return false; +} + + diff --git a/game/shared/cstrike/weapon_knife.h b/game/shared/cstrike/weapon_knife.h new file mode 100644 index 0000000..f7bf851 --- /dev/null +++ b/game/shared/cstrike/weapon_knife.h @@ -0,0 +1,81 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_KNIFE_H +#define WEAPON_KNIFE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_csbase.h" + + +#if defined( CLIENT_DLL ) + + #define CKnife C_Knife + +#endif + + +// ----------------------------------------------------------------------------- // +// CKnife class definition. +// ----------------------------------------------------------------------------- // + +class CKnife : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CKnife, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + #ifndef CLIENT_DLL + DECLARE_DATADESC(); + #endif + + + CKnife(); + + // We say yes to this so the weapon system lets us switch to it. + virtual bool HasPrimaryAmmo(); + virtual bool CanBeSelected(); + + virtual void Precache(); + + void Spawn(); + void Smack(); + //void Smack( trace_t *pTr, float delay ); + bool SwingOrStab( bool bStab ); + void PrimaryAttack(); + void SecondaryAttack(); + void WeaponAnimation( int iAnimation ); + + virtual void ItemPostFrame( void ); + +// virtual float GetSpread() const; + + bool Deploy(); + void Holster( int skiplocal = 0 ); + bool CanDrop(); + + void WeaponIdle(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_KNIFE; } + +public: + + trace_t m_trHit; + EHANDLE m_pTraceHitEnt; + + CNetworkVar( float, m_flSmackTime ); + bool m_bStab; + +private: + CKnife( const CKnife & ) {} +}; + + +#endif // WEAPON_KNIFE_H diff --git a/game/shared/cstrike/weapon_m249.cpp b/game/shared/cstrike/weapon_m249.cpp new file mode 100644 index 0000000..90ee803 --- /dev/null +++ b/game/shared/cstrike/weapon_m249.cpp @@ -0,0 +1,105 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponM249 C_WeaponM249 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponM249 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponM249, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponM249(); + + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_M249; } + + +private: + CWeaponM249( const CWeaponM249 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponM249, DT_WeaponM249 ) + +BEGIN_NETWORK_TABLE( CWeaponM249, DT_WeaponM249 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponM249 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_m249, CWeaponM249 ); +PRECACHE_WEAPON_REGISTER( weapon_m249 ); + + + +CWeaponM249::CWeaponM249() +{ +} + +float CWeaponM249::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.045f + 0.5f * m_flAccuracy; + else if (pPlayer->GetAbsVelocity().Length2D() > 140) + return 0.045f + 0.095f * m_flAccuracy; + else + return 0.03f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponM249::PrimaryAttack( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + pPlayer = GetPlayerOwner(); + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + if ( !pPlayer ) + return; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (1.8, 0.65, 0.45, 0.125, 5, 3.5, 8); + + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (1.1, 0.5, 0.3, 0.06, 4, 3, 8); + + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.75, 0.325, 0.25, 0.025, 3.5, 2.5, 9); + + else + pPlayer->KickBack (0.8, 0.35, 0.3, 0.03, 3.75, 3, 9); +} diff --git a/game/shared/cstrike/weapon_m3.cpp b/game/shared/cstrike/weapon_m3.cpp new file mode 100644 index 0000000..d31cc16 --- /dev/null +++ b/game/shared/cstrike/weapon_m3.cpp @@ -0,0 +1,293 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponM3 C_WeaponM3 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "te_shotgun_shot.h" + +#endif + + +class CWeaponM3 : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponM3, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponM3(); + + virtual void PrimaryAttack(); + virtual bool Reload(); + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + virtual float GetSpread() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_M3; } + +private: + + CWeaponM3( const CWeaponM3 & ); + + float m_flPumpTime; + CNetworkVar( int, m_reloadState ); + +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponM3, DT_WeaponM3 ) + +BEGIN_NETWORK_TABLE( CWeaponM3, DT_WeaponM3 ) +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_reloadState ) ) +#else + SendPropInt( SENDINFO( m_reloadState ), 2, SPROP_UNSIGNED ) +#endif +END_NETWORK_TABLE() + +#if defined(CLIENT_DLL) +BEGIN_PREDICTION_DATA( CWeaponM3 ) +DEFINE_PRED_FIELD( m_reloadState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_m3, CWeaponM3 ); +PRECACHE_WEAPON_REGISTER( weapon_m3 ); + + + +CWeaponM3::CWeaponM3() +{ + m_flPumpTime = 0; + m_reloadState = 0; +} + +float CWeaponM3::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + return 0.0f; + } + else + return BaseClass::GetInaccuracy(); +} + +float CWeaponM3::GetSpread() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + return 0.0675f; + + return GetCSWpnData().m_fSpread[Primary_Mode]; +} + +void CWeaponM3::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + // don't fire underwater + if (pPlayer->GetWaterLevel() == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.15; + return; + } + + // Out of ammo? + if ( m_iClip1 <= 0 ) + { + Reload(); + if ( m_iClip1 == 0 ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + } + + return; + } + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + m_iClip1--; + pPlayer->DoMuzzleFlash(); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // Dispatch the FX right away with full accuracy. + float flCurAttack = CalculateNextAttackTime( flCycleTime ); + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, // wrap it for network traffic so it's the same between client and server + GetInaccuracy(), + GetSpread(), + flCurAttack ); + + if (!m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + if (m_iClip1 != 0) + m_flPumpTime = gpGlobals->curtime + 0.5; + + if (m_iClip1 != 0) + SetWeaponIdleTime( gpGlobals->curtime + 2.5 ); + else + SetWeaponIdleTime( gpGlobals->curtime + 0.875 ); + m_reloadState = 0; + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + // Update punch angles. + QAngle angle = pPlayer->GetPunchAngle(); + + if ( pPlayer->GetFlags() & FL_ONGROUND ) + { + angle.x -= SharedRandomInt( "M3PunchAngleGround", 4, 6 ); + } + else + { + angle.x -= SharedRandomInt( "M3PunchAngleAir", 8, 11 ); + } + + pPlayer->SetPunchAngle( angle ); +} + + +bool CWeaponM3::Reload() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if (pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 || m_iClip1 == GetMaxClip1()) + return true; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > gpGlobals->curtime) + return true; + + // check to see if we're ready to reload + if (m_reloadState == 0) + { + pPlayer->SetAnimation( PLAYER_RELOAD ); + + SendWeaponAnim( ACT_SHOTGUN_RELOAD_START ); + m_reloadState = 1; + pPlayer->m_flNextAttack = gpGlobals->curtime + 0.5; + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; + SetWeaponIdleTime( gpGlobals->curtime + 0.5 ); + +#ifdef GAME_DLL + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_START ); +#endif + + return true; + } + else if (m_reloadState == 1) + { + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return true; + // was waiting for gun to move to side + m_reloadState = 2; + + SendWeaponAnim( ACT_VM_RELOAD ); + SetWeaponIdleTime( gpGlobals->curtime + 0.5 ); +#ifdef GAME_DLL + if ( m_iClip1 == 7 ) + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_END ); + } + else + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_LOOP ); + } +#endif + } + else + { + // Add them to the clip + m_iClip1 += 1; + +#ifdef GAME_DLL + SendReloadEvents(); +#endif + + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer ) + pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType ); + + m_reloadState = 1; + } + + return true; +} + + +void CWeaponM3::WeaponIdle() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (m_flPumpTime && m_flPumpTime < gpGlobals->curtime) + { + // play pumping sound + m_flPumpTime = 0; + } + + if (m_flTimeWeaponIdle < gpGlobals->curtime) + { + if (m_iClip1 == 0 && m_reloadState == 0 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + { + Reload( ); + } + else if (m_reloadState != 0) + { + if (m_iClip1 != 8 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + { + Reload( ); + } + else + { + // reload debounce has timed out + //MIKETODO: shotgun anims + SendWeaponAnim( ACT_SHOTGUN_RELOAD_FINISH ); + + // play cocking sound + m_reloadState = 0; + SetWeaponIdleTime( gpGlobals->curtime + 1.5 ); + } + } + else + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} diff --git a/game/shared/cstrike/weapon_m4a1.cpp b/game/shared/cstrike/weapon_m4a1.cpp new file mode 100644 index 0000000..e6ad1aa --- /dev/null +++ b/game/shared/cstrike/weapon_m4a1.cpp @@ -0,0 +1,351 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + +#if defined( CLIENT_DLL ) + + #define CWeaponM4A1 C_WeaponM4A1 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponM4A1 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponM4A1, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponM4A1(); + + virtual void Spawn(); + virtual void Precache(); + + virtual void SecondaryAttack(); + virtual void PrimaryAttack(); + virtual bool Deploy(); + virtual bool Reload(); + virtual void WeaponIdle(); + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); + virtual void Drop( const Vector &vecVelocity ); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_M4A1; } + + // return true if this weapon has a silencer equipped + virtual bool IsSilenced( void ) const { return m_bSilencerOn; } + + virtual Activity GetDeployActivity( void ); + +#ifdef CLIENT_DLL + virtual int GetMuzzleFlashStyle( void ); +#endif + + virtual const char *GetWorldModel( void ) const; + virtual int GetWorldModelIndex( void ); + +private: + + CWeaponM4A1( const CWeaponM4A1 & ); + + void DoFireEffects(); + + CNetworkVar( bool, m_bSilencerOn ); + CNetworkVar( float, m_flDoneSwitchingSilencer ); // soonest time switching the silencer will be complete + + int m_silencedModelIndex; + bool m_inPrecache; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponM4A1, DT_WeaponM4A1 ) + +BEGIN_NETWORK_TABLE( CWeaponM4A1, DT_WeaponM4A1 ) + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bSilencerOn ) ), + RecvPropTime( RECVINFO( m_flDoneSwitchingSilencer ) ), + #else + SendPropBool( SENDINFO( m_bSilencerOn ) ), + SendPropTime( SENDINFO( m_flDoneSwitchingSilencer ) ), + #endif +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponM4A1 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_m4a1, CWeaponM4A1 ); +PRECACHE_WEAPON_REGISTER( weapon_m4a1 ); + + + +CWeaponM4A1::CWeaponM4A1() +{ + m_bSilencerOn = false; + m_flDoneSwitchingSilencer = 0.0f; + m_inPrecache = false; +} + + +void CWeaponM4A1::Spawn( ) +{ + BaseClass::Spawn(); + + m_bSilencerOn = false; + m_weaponMode = Primary_Mode; + m_flDoneSwitchingSilencer = 0.0f; + m_bDelayFire = true; +} + + +void CWeaponM4A1::Precache() +{ + m_inPrecache = true; + BaseClass::Precache(); + + m_silencedModelIndex = CBaseEntity::PrecacheModel( GetCSWpnData().m_szSilencerModel ); + m_inPrecache = false; +} + + +int CWeaponM4A1::GetWorldModelIndex( void ) +{ + if ( !m_bSilencerOn || m_inPrecache ) + { + return m_iWorldModelIndex; + } + else + { + return m_silencedModelIndex; + } +} + + +const char * CWeaponM4A1::GetWorldModel( void ) const +{ + if ( !m_bSilencerOn || m_inPrecache ) + { + return BaseClass::GetWorldModel(); + } + else + { + return GetCSWpnData().m_szSilencerModel; + } +} + + +bool CWeaponM4A1::Deploy() +{ + bool ret = BaseClass::Deploy(); + + m_flDoneSwitchingSilencer = 0.0f; + m_bDelayFire = true; + + return ret; +} + +Activity CWeaponM4A1::GetDeployActivity( void ) +{ + if( IsSilenced() ) + { + return ACT_VM_DRAW_SILENCED; + } + else + { + return ACT_VM_DRAW; + } +} + +bool CWeaponM4A1::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( gpGlobals->curtime < m_flDoneSwitchingSilencer ) + { + // still switching the silencer. Cancel the switch. + m_bSilencerOn = !m_bSilencerOn; + m_weaponMode = m_bSilencerOn ? Secondary_Mode : Primary_Mode; + SetWeaponModelIndex( GetWorldModel() ); + } + + return BaseClass::Holster( pSwitchingTo ); +} + +void CWeaponM4A1::Drop( const Vector &vecVelocity ) +{ + if ( gpGlobals->curtime < m_flDoneSwitchingSilencer ) + { + // still switching the silencer. Cancel the switch. + m_bSilencerOn = !m_bSilencerOn; + m_weaponMode = m_bSilencerOn ? Secondary_Mode : Primary_Mode; + SetWeaponModelIndex( GetWorldModel() ); + } + + BaseClass::Drop( vecVelocity ); +} + +void CWeaponM4A1::SecondaryAttack() +{ + if ( m_bSilencerOn ) + { + m_bSilencerOn = false; + m_weaponMode = Primary_Mode; + SendWeaponAnim( ACT_VM_DETACH_SILENCER ); + } + else + { + m_bSilencerOn = true; + m_weaponMode = Secondary_Mode; + SendWeaponAnim( ACT_VM_ATTACH_SILENCER ); + } + m_flDoneSwitchingSilencer = gpGlobals->curtime + 2; + + m_flNextSecondaryAttack = gpGlobals->curtime + 2; + m_flNextPrimaryAttack = gpGlobals->curtime + 2; + SetWeaponIdleTime( gpGlobals->curtime + 2 ); + + SetWeaponModelIndex( GetWorldModel() ); +} + +float CWeaponM4A1::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + { + return 0.035f + 0.4f * m_flAccuracy; + } + else if (pPlayer->GetAbsVelocity().Length2D() > 140) + { + return 0.035f + 0.07f * m_flAccuracy; + } + else + { + if ( m_bSilencerOn ) + return 0.025f * m_flAccuracy; + else + return 0.02f * m_flAccuracy; + } + } + else + { + return BaseClass::GetInaccuracy(); + } +} + + +void CWeaponM4A1::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, m_weaponMode ) ) + return; + + if ( m_bSilencerOn ) + SendWeaponAnim( ACT_VM_PRIMARYATTACK_SILENCED ); + + pPlayer = GetPlayerOwner(); + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + if ( !pPlayer ) + return; + + if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (1.0, 0.45, 0.28, 0.045, 3.75, 3, 7); + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (1.2, 0.5, 0.23, 0.15, 5.5, 3.5, 6); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.6, 0.3, 0.2, 0.0125, 3.25, 2, 7); + else + pPlayer->KickBack (0.65, 0.35, 0.25, 0.015, 3.5, 2.25, 7); +} + + +void CWeaponM4A1::DoFireEffects() +{ + if ( !m_bSilencerOn ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( pPlayer ) + { + pPlayer->DoMuzzleFlash(); + } + } +} + +bool CWeaponM4A1::Reload() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if (pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0) + return false; + + int iResult = 0; + + if ( m_bSilencerOn ) + iResult = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD_SILENCED ); + else + iResult = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); + + if ( !iResult ) + return false; + + pPlayer->SetAnimation( PLAYER_RELOAD ); + + if ((iResult) && (pPlayer->GetFOV() != pPlayer->GetDefaultFOV())) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() ); + } + + m_flAccuracy = 0.2; + pPlayer->m_iShotsFired = 0; + m_bDelayFire = false; + return true; +} + + +void CWeaponM4A1::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + // only idle if the slid isn't back + if ( m_iClip1 != 0 ) + { + SetWeaponIdleTime( gpGlobals->curtime + GetCSWpnData().m_flIdleInterval ); + if ( m_bSilencerOn ) + SendWeaponAnim( ACT_VM_IDLE_SILENCED ); + else + SendWeaponAnim( ACT_VM_IDLE ); + } +} + + +#ifdef CLIENT_DLL +int CWeaponM4A1::GetMuzzleFlashStyle( void ) +{ + if( m_bSilencerOn ) + { + return CS_MUZZLEFLASH_NONE; + } + else + { + return CS_MUZZLEFLASH_X; + } +} +#endif diff --git a/game/shared/cstrike/weapon_mac10.cpp b/game/shared/cstrike/weapon_mac10.cpp new file mode 100644 index 0000000..e3979fd --- /dev/null +++ b/game/shared/cstrike/weapon_mac10.cpp @@ -0,0 +1,130 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponMAC10 C_WeaponMAC10 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponMAC10 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponMAC10, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponMAC10(); + + virtual void Spawn(); + virtual void PrimaryAttack(); + virtual bool Deploy(); + virtual bool Reload(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_MAC10; } + + +private: + CWeaponMAC10( const CWeaponMAC10 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponMAC10, DT_WeaponMAC10 ) + +BEGIN_NETWORK_TABLE( CWeaponMAC10, DT_WeaponMAC10 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponMAC10 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_mac10, CWeaponMAC10 ); +PRECACHE_WEAPON_REGISTER( weapon_mac10 ); + + + +CWeaponMAC10::CWeaponMAC10() +{ +} + + +void CWeaponMAC10::Spawn( ) +{ + BaseClass::Spawn(); + + m_flAccuracy = 0.15; +} + + +bool CWeaponMAC10::Deploy() +{ + bool ret = BaseClass::Deploy(); + + m_flAccuracy = 0.15; + + return ret; +} + +bool CWeaponMAC10::Reload() +{ + bool ret = BaseClass::Reload(); + + m_flAccuracy = 0.15; + + return ret; +} + + +float CWeaponMAC10::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.375f * m_flAccuracy; + else + return 0.03f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponMAC10::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) // jumping + pPlayer->KickBack (1.3, 0.55, 0.4, 0.05, 4.75, 3.75, 5); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) // running + pPlayer->KickBack (0.9, 0.45, 0.25, 0.035, 3.5, 2.75, 7); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) // ducking + pPlayer->KickBack (0.75, 0.4, 0.175, 0.03, 2.75, 2.5, 10); + else // standing + pPlayer->KickBack (0.775, 0.425, 0.2, 0.03, 3, 2.75, 9); +} diff --git a/game/shared/cstrike/weapon_mp5navy.cpp b/game/shared/cstrike/weapon_mp5navy.cpp new file mode 100644 index 0000000..1544550 --- /dev/null +++ b/game/shared/cstrike/weapon_mp5navy.cpp @@ -0,0 +1,129 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponMP5Navy C_WeaponMP5Navy + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponMP5Navy : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponMP5Navy, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponMP5Navy(); + + virtual void Spawn(); + virtual void PrimaryAttack(); + virtual bool Deploy(); + virtual bool Reload(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_MP5NAVY; } + + +private: + CWeaponMP5Navy( const CWeaponMP5Navy & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponMP5Navy, DT_WeaponMP5Navy ) + +BEGIN_NETWORK_TABLE( CWeaponMP5Navy, DT_WeaponMP5Navy ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponMP5Navy ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_mp5navy, CWeaponMP5Navy ); +PRECACHE_WEAPON_REGISTER( weapon_mp5navy ); + + + +CWeaponMP5Navy::CWeaponMP5Navy() +{ +} + +void CWeaponMP5Navy::Spawn() +{ + BaseClass::Spawn(); + + m_flAccuracy = 0.0; +} + + +bool CWeaponMP5Navy::Deploy( ) +{ + bool ret = BaseClass::Deploy(); + + m_flAccuracy = 0.0; + + return ret; +} + +bool CWeaponMP5Navy::Reload( ) +{ + bool ret = BaseClass::Reload(); + + m_flAccuracy = 0.0; + + return ret; +} + +float CWeaponMP5Navy::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.2f * m_flAccuracy; + else + return 0.04f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponMP5Navy::PrimaryAttack( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Kick the gun based on the state of the player. + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (0.9, 0.475, 0.35, 0.0425, 5, 3, 6); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (0.5, 0.275, 0.2, 0.03, 3, 2, 10); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.225, 0.15, 0.1, 0.015, 2, 1, 10); + else + pPlayer->KickBack (0.25, 0.175, 0.125, 0.02, 2.25, 1.25, 10); +} diff --git a/game/shared/cstrike/weapon_p228.cpp b/game/shared/cstrike/weapon_p228.cpp new file mode 100644 index 0000000..04bec84 --- /dev/null +++ b/game/shared/cstrike/weapon_p228.cpp @@ -0,0 +1,204 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponP228 C_WeaponP228 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponP228 : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponP228, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponP228(); + + virtual void Spawn(); + + virtual void PrimaryAttack(); + virtual bool Deploy(); + + virtual bool Reload(); + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_P228; } + +private: + + CWeaponP228( const CWeaponP228 & ); + + float m_flLastFire; +}; + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponP228 ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_p228, CWeaponP228 ); +PRECACHE_WEAPON_REGISTER( weapon_p228 ); + + + +CWeaponP228::CWeaponP228() +{ + m_flLastFire = gpGlobals->curtime; +} + + +void CWeaponP228::Spawn( ) +{ + m_flAccuracy = 0.9; + + BaseClass::Spawn(); +} + + +bool CWeaponP228::Deploy( ) +{ + m_flAccuracy = 0.9; + + return BaseClass::Deploy(); +} + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponP228, DT_WeaponP228 ) + +BEGIN_NETWORK_TABLE( CWeaponP228, DT_WeaponP228 ) +END_NETWORK_TABLE() + + +float CWeaponP228::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.5f * (1 - m_flAccuracy); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.255f * (1 - m_flAccuracy); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.075f * (1 - m_flAccuracy); + else + return 0.15f * (1 - m_flAccuracy); + } + else + return BaseClass::GetInaccuracy(); +} + + +void CWeaponP228::PrimaryAttack( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.3)*(0.325 - (gpGlobals->curtime - m_flLastFire)); + + if (m_flAccuracy > 0.9) + m_flAccuracy = 0.9; + else if (m_flAccuracy < 0.6) + m_flAccuracy = 0.6; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.1f; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_iClip1--; + + pPlayer->DoMuzzleFlash(); + //SetPlayerShieldAnim(); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // Aiming + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread()); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + GetCSWpnData().m_flCycleTime; + + if (!m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 2 ); + + //ResetPlayerShieldAnim(); + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= 2; + pPlayer->SetPunchAngle( angle ); +} + + +bool CWeaponP228::Reload() +{ + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.9; + return true; +} + +void CWeaponP228::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + // only idle if the slid isn't back + if (m_iClip1 != 0) + { + SetWeaponIdleTime( gpGlobals->curtime + 3.0 ) ; + SendWeaponAnim( ACT_VM_IDLE ); + } +} diff --git a/game/shared/cstrike/weapon_p90.cpp b/game/shared/cstrike/weapon_p90.cpp new file mode 100644 index 0000000..80ac973 --- /dev/null +++ b/game/shared/cstrike/weapon_p90.cpp @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponP90 C_WeaponP90 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponP90 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponP90, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponP90(); + + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_P90; } + + +private: + CWeaponP90( const CWeaponP90 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponP90, DT_WeaponP90 ) + +BEGIN_NETWORK_TABLE( CWeaponP90, DT_WeaponP90 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponP90 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_p90, CWeaponP90 ); +PRECACHE_WEAPON_REGISTER( weapon_p90 ); + + + +CWeaponP90::CWeaponP90() +{ +} + +float CWeaponP90::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.3f * m_flAccuracy; + else if (pPlayer->GetAbsVelocity().Length2D() > 170) + return 0.115f * m_flAccuracy; + else + return 0.045f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponP90::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // Kick the gun based on the state of the player. + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (0.9, 0.45, 0.35, 0.04, 5.25, 3.5, 4); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (0.45, 0.3, 0.2, 0.0275, 4, 2.25, 7); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.275, 0.2, 0.125, 0.02, 3, 1, 9); + else + pPlayer->KickBack (0.3, 0.225, 0.125, 0.02, 3.25, 1.25, 8); +} diff --git a/game/shared/cstrike/weapon_scout.cpp b/game/shared/cstrike/weapon_scout.cpp new file mode 100644 index 0000000..8722e48 --- /dev/null +++ b/game/shared/cstrike/weapon_scout.cpp @@ -0,0 +1,220 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponScout C_WeaponScout + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "KeyValues.h" + +#endif + +const int cScoutMidZoomFOV = 40; +const int cScoutMaxZoomFOV = 15; + + +class CWeaponScout : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponScout, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponScout(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + + virtual float GetInaccuracy() const; + virtual float GetMaxSpeed() const; + virtual bool Reload(); + virtual bool Deploy(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_SCOUT; } + + +private: + + CWeaponScout( const CWeaponScout & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponScout, DT_WeaponScout ) + +BEGIN_NETWORK_TABLE( CWeaponScout, DT_WeaponScout ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponScout ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_scout, CWeaponScout ); +PRECACHE_WEAPON_REGISTER( weapon_scout ); + + + +CWeaponScout::CWeaponScout() +{ +} + +void CWeaponScout::SecondaryAttack() +{ + const float kZoomTime = 0.10f; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if (pPlayer == NULL) + { + Assert(pPlayer != NULL); + return; + } + + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV()) + { + pPlayer->SetFOV( pPlayer, cScoutMidZoomFOV, kZoomTime ); + m_weaponMode = Secondary_Mode; + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyAltSwitch; + } + else if (pPlayer->GetFOV() == cScoutMidZoomFOV) + { + pPlayer->SetFOV( pPlayer, cScoutMaxZoomFOV, kZoomTime ); + m_weaponMode = Secondary_Mode; + } + else if (pPlayer->GetFOV() == cScoutMaxZoomFOV) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), kZoomTime ); + m_weaponMode = Primary_Mode; + } + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3f; + m_zoomFullyActiveTime = gpGlobals->curtime + 0.15; // The worst zoom time from above. + +#ifndef CLIENT_DLL + // If this isn't guarded, the sound will be emitted twice, once by the server and once by the client. + // Let the server play it since if only the client plays it, it's liable to get played twice cause of + // a prediction error. joy. + + //============================================================================= + // HPE_BEGIN: + // [tj] Playing this from the player so that we don't try to play the sound outside the level. + //============================================================================= + if ( GetPlayerOwner() ) + { + GetPlayerOwner()->EmitSound( "Default.Zoom" ); // zoom sound + } + //============================================================================= + // HPE_END + //============================================================================= + // let the bots hear the rifle zoom + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_zoom" ); + if( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif +} + +float CWeaponScout::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if (pPlayer == NULL) + return 0.0f; + + float fSpread = 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + fSpread = 0.2f; + else if (pPlayer->GetAbsVelocity().Length2D() > 170) + fSpread = 0.075f; + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + fSpread = 0.0f; + else + fSpread = 0.007f; + + // If we are not zoomed in, or we have very recently zoomed and are still transitioning, the bullet diverts more. + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV() || (gpGlobals->curtime < m_zoomFullyActiveTime)) + { + fSpread += 0.025; + } + + return fSpread; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponScout::PrimaryAttack( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if (pPlayer == NULL) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, m_weaponMode ) ) + return; + + if ( m_weaponMode == Secondary_Mode ) + { + float midFOVdistance = fabs( pPlayer->GetFOV() - (float)cScoutMidZoomFOV ); + float farFOVdistance = fabs( pPlayer->GetFOV() - (float)cScoutMaxZoomFOV ); + + if ( midFOVdistance < farFOVdistance ) + { + pPlayer->m_iLastZoom = cScoutMidZoomFOV; + } + else + { + pPlayer->m_iLastZoom = cScoutMaxZoomFOV; + } + +// #ifndef CLIENT_DLL + pPlayer->m_bResumeZoom = true; + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), 0.05f ); + m_weaponMode = Primary_Mode; +// #endif + } + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= 2; + pPlayer->SetPunchAngle( angle ); +} + + +float CWeaponScout::GetMaxSpeed() const +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if (pPlayer == NULL) + { + Assert(pPlayer != NULL); + return BaseClass::GetMaxSpeed(); + } + + if ( pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + return BaseClass::GetMaxSpeed(); + else + return 220; // zoomed in. +} + + +bool CWeaponScout::Reload() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Reload(); + +} + +bool CWeaponScout::Deploy() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Deploy(); +} diff --git a/game/shared/cstrike/weapon_sg550.cpp b/game/shared/cstrike/weapon_sg550.cpp new file mode 100644 index 0000000..fc74183 --- /dev/null +++ b/game/shared/cstrike/weapon_sg550.cpp @@ -0,0 +1,214 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponSG550 C_WeaponSG550 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "KeyValues.h" + +#endif + + +class CWeaponSG550 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponSG550, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponSG550(); + + virtual void Spawn(); + virtual void SecondaryAttack(); + virtual void PrimaryAttack(); + virtual bool Reload(); + virtual bool Deploy(); + + virtual float GetInaccuracy() const; + virtual float GetMaxSpeed() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_SG550; } + + +private: + CWeaponSG550( const CWeaponSG550 & ); + + float m_flLastFire; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSG550, DT_WeaponSG550 ) + +BEGIN_NETWORK_TABLE( CWeaponSG550, DT_WeaponSG550 ) +END_NETWORK_TABLE() + +#if defined CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponSG550 ) + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_sg550, CWeaponSG550 ); +PRECACHE_WEAPON_REGISTER( weapon_sg550 ); + + + +CWeaponSG550::CWeaponSG550() +{ + m_flLastFire = gpGlobals->curtime; +} + +void CWeaponSG550::Spawn() +{ + BaseClass::Spawn(); + m_flAccuracy = 0.98; +} + + +void CWeaponSG550::SecondaryAttack() +{ + const float kZoomTime = 0.10f; + + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV()) + { + pPlayer->SetFOV( pPlayer, 40, kZoomTime ); + m_weaponMode = Secondary_Mode; + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyAltSwitch; + } + else if (pPlayer->GetFOV() == 40) + { + pPlayer->SetFOV( pPlayer, 15, kZoomTime ); + m_weaponMode = Secondary_Mode; + } + else if (pPlayer->GetFOV() == 15) + { + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV(), kZoomTime ); + m_weaponMode = Primary_Mode; + } + + +#ifndef CLIENT_DLL + // If this isn't guarded, the sound will be emitted twice, once by the server and once by the client. + // Let the server play it since if only the client plays it, it's liable to get played twice cause of + // a prediction error. joy. + + //============================================================================= + // HPE_BEGIN: + // [tj] Playing this from the player so that we don't try to play the sound outside the level. + //============================================================================= + if ( GetPlayerOwner() ) + { + GetPlayerOwner()->EmitSound( "Default.Zoom" ); // zoom sound. + } + //============================================================================= + // HPE_END + //============================================================================= + // let the bots hear the rifle zoom + IGameEvent * event = gameeventmanager->CreateEvent( "weapon_zoom" ); + if( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( event ); + } +#endif + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3f; + m_zoomFullyActiveTime = gpGlobals->curtime + 0.3; // The worst zoom time from above. +} + +float CWeaponSG550::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + float fSpread = 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + fSpread = 0.45f * (1 - m_flAccuracy); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + fSpread = 0.15f; + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + fSpread = 0.04f * (1 - m_flAccuracy); + else + fSpread = 0.05f * (1 - m_flAccuracy); + + // If we are not zoomed in, or we have very recently zoomed and are still transitioning, the bullet diverts more. + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV() || (gpGlobals->curtime < m_zoomFullyActiveTime)) + fSpread += 0.025; + + return fSpread; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponSG550::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy = 0.65 + (0.35) * (gpGlobals->curtime - m_flLastFire); + + if (m_flAccuracy > 0.98) + m_flAccuracy = 0.98; + + m_flLastFire = gpGlobals->curtime; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, m_weaponMode ) ) + return; + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= SharedRandomFloat("SG550PunchAngleX", 0.75, 1.25 ) + ( angle.x / 4 ); + angle.y += SharedRandomFloat("SG550PunchAngleY", -0.75, 0.75 ); + pPlayer->SetPunchAngle( angle ); +} + +bool CWeaponSG550::Reload() +{ + bool ret = BaseClass::Reload(); + + m_flAccuracy = 0.98; + m_weaponMode = Primary_Mode; + + return ret; +} + +bool CWeaponSG550::Deploy() +{ + bool ret = BaseClass::Deploy(); + + m_flAccuracy = 0.98; + m_weaponMode = Primary_Mode; + + return ret; +} + +float CWeaponSG550::GetMaxSpeed() const +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( !pPlayer || pPlayer->GetFOV() == 90 ) + return BaseClass::GetMaxSpeed(); + else + return 150; // zoomed in +} diff --git a/game/shared/cstrike/weapon_sg552.cpp b/game/shared/cstrike/weapon_sg552.cpp new file mode 100644 index 0000000..ab84095 --- /dev/null +++ b/game/shared/cstrike/weapon_sg552.cpp @@ -0,0 +1,170 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponSG552 C_WeaponSG552 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponSG552 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponSG552, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponSG552(); + + virtual void SecondaryAttack(); + virtual void PrimaryAttack(); + + virtual float GetInaccuracy() const; + virtual float GetMaxSpeed() const; + virtual bool Reload(); + virtual bool Deploy(); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_SG552; } + +#ifdef CLIENT_DLL + virtual bool HideViewModelWhenZoomed( void ) { return false; } +#endif + +private: + + CWeaponSG552( const CWeaponSG552 & ); + + void SG552Fire( float flSpread, bool bZoomed ); + +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSG552, DT_WeaponSG552 ) + +BEGIN_NETWORK_TABLE( CWeaponSG552, DT_WeaponSG552 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponSG552 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_sg552, CWeaponSG552 ); +PRECACHE_WEAPON_REGISTER( weapon_sg552 ); + + + +CWeaponSG552::CWeaponSG552() +{ +} + + +void CWeaponSG552::SecondaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (pPlayer->GetFOV() == pPlayer->GetDefaultFOV()) + { + pPlayer->SetFOV( pPlayer, 55, 0.2f ); + m_weaponMode = Secondary_Mode; + } + else if (pPlayer->GetFOV() == 55) + { + pPlayer->SetFOV( pPlayer, 0, 0.15f ); + m_weaponMode = Secondary_Mode; + } + else + { + //FIXME: This seems wrong + pPlayer->SetFOV( pPlayer, pPlayer->GetDefaultFOV() ); + m_weaponMode = Primary_Mode; + } + + m_flNextSecondaryAttack = gpGlobals->curtime + 0.3; +} + +float CWeaponSG552::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.035f + 0.45f * m_flAccuracy; + else if (pPlayer->GetAbsVelocity().Length2D() > 140) + return 0.035f + 0.075f * m_flAccuracy; + else + return 0.02f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponSG552::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + bool bZoomed = pPlayer->GetFOV() < pPlayer->GetDefaultFOV(); + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + if ( bZoomed ) + flCycleTime = 0.135f; + + if ( !CSBaseGunFire( flCycleTime, m_weaponMode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (1, 0.45, 0.28, 0.04, 4.25, 2.5, 7); + else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (1.25, 0.45, 0.22, 0.18, 6, 4, 5); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.6, 0.35, 0.2, 0.0125, 3.7, 2, 10); + else + pPlayer->KickBack (0.625, 0.375, 0.25, 0.0125, 4, 2.25, 9); +} + + +float CWeaponSG552::GetMaxSpeed() const +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( !pPlayer || pPlayer->GetFOV() == pPlayer->GetDefaultFOV() ) + return BaseClass::GetMaxSpeed(); + else + return 200; // zoomed in. +} + + +bool CWeaponSG552::Reload() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Reload(); +} + +bool CWeaponSG552::Deploy() +{ + m_weaponMode = Primary_Mode; + return BaseClass::Deploy(); +} diff --git a/game/shared/cstrike/weapon_smokegrenade.cpp b/game/shared/cstrike/weapon_smokegrenade.cpp new file mode 100644 index 0000000..1703fbf --- /dev/null +++ b/game/shared/cstrike/weapon_smokegrenade.cpp @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "gamerules.h" +#include "npcevent.h" +#include "engine/IEngineSound.h" +#include "weapon_smokegrenade.h" + + +#ifdef CLIENT_DLL + +#else + + #include "cs_player.h" + #include "items.h" + #include "smokegrenade_projectile.h" + +#endif + + +IMPLEMENT_NETWORKCLASS_ALIASED( SmokeGrenade, DT_SmokeGrenade ) + +BEGIN_NETWORK_TABLE(CSmokeGrenade, DT_SmokeGrenade) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CSmokeGrenade ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_smokegrenade, CSmokeGrenade ); +PRECACHE_WEAPON_REGISTER( weapon_smokegrenade ); + + +#ifndef CLIENT_DLL + + BEGIN_DATADESC( CSmokeGrenade ) + END_DATADESC() + + void CSmokeGrenade::EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ) + { + CSmokeGrenadeProjectile::Create( vecSrc, vecAngles, vecVel, angImpulse, pPlayer ); + } + +#endif + diff --git a/game/shared/cstrike/weapon_smokegrenade.h b/game/shared/cstrike/weapon_smokegrenade.h new file mode 100644 index 0000000..8d654cf --- /dev/null +++ b/game/shared/cstrike/weapon_smokegrenade.h @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WEAPON_SMOKEGRENADE_H +#define WEAPON_SMOKEGRENADE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_basecsgrenade.h" + + +#ifdef CLIENT_DLL + + #define CSmokeGrenade C_SmokeGrenade + +#endif + + +//----------------------------------------------------------------------------- +// Smoke grenades +//----------------------------------------------------------------------------- +class CSmokeGrenade : public CBaseCSGrenade +{ +public: + DECLARE_CLASS( CSmokeGrenade, CBaseCSGrenade ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CSmokeGrenade() {} + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_SMOKEGRENADE; } + +#ifdef CLIENT_DLL + +#else + DECLARE_DATADESC(); + + void EmitGrenade( Vector vecSrc, QAngle vecAngles, Vector vecVel, AngularImpulse angImpulse, CBasePlayer *pPlayer ); + +#endif + + CSmokeGrenade( const CSmokeGrenade & ) {} +}; + + +#endif // WEAPON_SMOKEGRENADE_H diff --git a/game/shared/cstrike/weapon_tmp.cpp b/game/shared/cstrike/weapon_tmp.cpp new file mode 100644 index 0000000..b750d84 --- /dev/null +++ b/game/shared/cstrike/weapon_tmp.cpp @@ -0,0 +1,106 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponTMP C_WeaponTMP + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponTMP : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponTMP, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponTMP(); + + virtual void PrimaryAttack(); + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_TMP; } + virtual bool IsSilenced( void ) const { return true; } + + virtual float GetInaccuracy() const; + +private: + + CWeaponTMP( const CWeaponTMP & ); + + void DoFireEffects( void ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponTMP, DT_WeaponTMP ) + +BEGIN_NETWORK_TABLE( CWeaponTMP, DT_WeaponTMP ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponTMP ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_tmp, CWeaponTMP ); +PRECACHE_WEAPON_REGISTER( weapon_tmp ); + + +CWeaponTMP::CWeaponTMP() +{ +} + + +float CWeaponTMP::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.25f * m_flAccuracy; + else + return 0.03f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponTMP::PrimaryAttack( void ) +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (1.1, 0.5, 0.35, 0.045, 4.5, 3.5, 6); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (0.8, 0.4, 0.2, 0.03, 3, 2.5, 7); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.7, 0.35, 0.125, 0.025, 2.5, 2, 10); + else + pPlayer->KickBack (0.725, 0.375, 0.15, 0.025, 2.75, 2.25, 9); +} + +void CWeaponTMP::DoFireEffects( void ) +{ + // TMP is silenced, so do nothing +} diff --git a/game/shared/cstrike/weapon_ump45.cpp b/game/shared/cstrike/weapon_ump45.cpp new file mode 100644 index 0000000..8bcc3e0 --- /dev/null +++ b/game/shared/cstrike/weapon_ump45.cpp @@ -0,0 +1,132 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbasegun.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponUMP45 C_WeaponUMP45 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponUMP45 : public CWeaponCSBaseGun +{ +public: + DECLARE_CLASS( CWeaponUMP45, CWeaponCSBaseGun ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponUMP45(); + + virtual void Spawn(); + virtual void PrimaryAttack(); + virtual bool Deploy(); + virtual bool Reload(); + + virtual float GetInaccuracy() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_UMP45; } + + +private: + + CWeaponUMP45( const CWeaponUMP45 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponUMP45, DT_WeaponUMP45 ) + +BEGIN_NETWORK_TABLE( CWeaponUMP45, DT_WeaponUMP45 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponUMP45 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_ump45, CWeaponUMP45 ); +PRECACHE_WEAPON_REGISTER( weapon_ump45 ); + + + +CWeaponUMP45::CWeaponUMP45() +{ +} + + +void CWeaponUMP45::Spawn() +{ + BaseClass::Spawn(); + + m_flAccuracy = 0.0; +} + + +bool CWeaponUMP45::Deploy() +{ + bool ret = BaseClass::Deploy(); + + m_flAccuracy = 0.0; + + return ret; +} + +bool CWeaponUMP45::Reload() +{ + bool ret = BaseClass::Reload(); + + m_flAccuracy = 0.0; + + return ret; +} + +float CWeaponUMP45::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 0.24f * m_flAccuracy; + else + return 0.04f * m_flAccuracy; + } + else + return BaseClass::GetInaccuracy(); +} + +void CWeaponUMP45::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if ( !CSBaseGunFire( GetCSWpnData().m_flCycleTime, Primary_Mode ) ) + return; + + // CSBaseGunFire can kill us, forcing us to drop our weapon, if we shoot something that explodes + pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + // Kick the gun based on the state of the player. + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + pPlayer->KickBack (0.125, 0.65, 0.55, 0.0475, 5.5, 4, 10); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + pPlayer->KickBack (0.55, 0.3, 0.225, 0.03, 3.5, 2.5, 10); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + pPlayer->KickBack (0.25, 0.175, 0.125, 0.02, 2.25, 1.25, 10); + else + pPlayer->KickBack (0.275, 0.2, 0.15, 0.0225, 2.5, 1.5, 10); +} + diff --git a/game/shared/cstrike/weapon_usp.cpp b/game/shared/cstrike/weapon_usp.cpp new file mode 100644 index 0000000..6a99f02 --- /dev/null +++ b/game/shared/cstrike/weapon_usp.cpp @@ -0,0 +1,395 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponUSP C_WeaponUSP + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + +#endif + + +class CWeaponUSP : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponUSP, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponUSP(); + + virtual void Spawn(); + virtual void Precache(); + + virtual void PrimaryAttack(); + virtual void SecondaryAttack(); + virtual bool Deploy(); + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); + virtual void Drop( const Vector &vecVelocity ); + + virtual float GetInaccuracy() const; + + virtual bool Reload(); + virtual void WeaponIdle(); + + // We overload this so we can translate all weapon activities to silenced versions. + virtual bool SendWeaponAnim( int iActivity ); + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_USP; } + + // return true if this weapon has a silencer equipped + virtual bool IsSilenced( void ) const { return m_bSilencerOn; } + + virtual Activity GetDeployActivity( void ); + +#ifdef CLIENT_DLL + virtual int GetMuzzleFlashStyle( void ); +#endif + + virtual const char *GetWorldModel( void ) const; + virtual int GetWorldModelIndex( void ); + +private: + CWeaponUSP( const CWeaponUSP & ); + + CNetworkVar( bool, m_bSilencerOn ); + CNetworkVar( float, m_flDoneSwitchingSilencer ); // soonest time switching the silencer will be complete + float m_flLastFire; + + int m_silencedModelIndex; + bool m_inPrecache; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponUSP, DT_WeaponUSP ) + +BEGIN_NETWORK_TABLE( CWeaponUSP, DT_WeaponUSP ) +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bSilencerOn ) ), + RecvPropTime( RECVINFO( m_flDoneSwitchingSilencer ) ), +#else + SendPropBool( SENDINFO( m_bSilencerOn ) ), + SendPropTime( SENDINFO( m_flDoneSwitchingSilencer ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponUSP ) + DEFINE_PRED_FIELD( m_bSilencerOn, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_FIELD( m_flLastFire, FIELD_FLOAT ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_usp, CWeaponUSP ); +PRECACHE_WEAPON_REGISTER( weapon_usp ); + + +Activity g_SilencedTranslations[][2] = +{ + { ACT_VM_RELOAD, ACT_VM_RELOAD_SILENCED }, + { ACT_VM_PRIMARYATTACK, ACT_VM_PRIMARYATTACK_SILENCED }, + { ACT_VM_DRAW, ACT_VM_DRAW_SILENCED }, +}; + + + +CWeaponUSP::CWeaponUSP() +{ + m_flLastFire = gpGlobals->curtime; + m_bSilencerOn = false; + m_flDoneSwitchingSilencer = 0.0f; + m_inPrecache = false; +} + + +void CWeaponUSP::Spawn() +{ + //m_iDefaultAmmo = 12; + m_flAccuracy = 0.92; + m_bSilencerOn = false; + m_weaponMode = Primary_Mode; + m_flDoneSwitchingSilencer = 0.0f; + + //FallInit();// get ready to fall down. + BaseClass::Spawn(); +} + + +void CWeaponUSP::Precache() +{ + m_inPrecache = true; + BaseClass::Precache(); + + m_silencedModelIndex = CBaseEntity::PrecacheModel( GetCSWpnData().m_szSilencerModel ); + m_inPrecache = false; +} + + +int CWeaponUSP::GetWorldModelIndex( void ) +{ + if ( !m_bSilencerOn || m_inPrecache ) + { + return m_iWorldModelIndex; + } + else + { + return m_silencedModelIndex; + } +} + + +const char * CWeaponUSP::GetWorldModel( void ) const +{ + if ( !m_bSilencerOn || m_inPrecache ) + { + return BaseClass::GetWorldModel(); + } + else + { + return GetCSWpnData().m_szSilencerModel; + } +} + + +bool CWeaponUSP::Deploy() +{ + m_flAccuracy = 0.92; + m_flDoneSwitchingSilencer = 0.0f; + + return BaseClass::Deploy(); +} + +bool CWeaponUSP::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( gpGlobals->curtime < m_flDoneSwitchingSilencer ) + { + // still switching the silencer. Cancel the switch. + m_bSilencerOn = !m_bSilencerOn; + m_weaponMode = m_bSilencerOn ? Secondary_Mode : Primary_Mode; + SetWeaponModelIndex( GetWorldModel() ); + } + + return BaseClass::Holster( pSwitchingTo ); +} + +void CWeaponUSP::Drop( const Vector &vecVelocity ) +{ + if ( gpGlobals->curtime < m_flDoneSwitchingSilencer ) + { + // still switching the silencer. Cancel the switch. + m_bSilencerOn = !m_bSilencerOn; + m_weaponMode = m_bSilencerOn ? Secondary_Mode : Primary_Mode; + SetWeaponModelIndex( GetWorldModel() ); + } + + BaseClass::Drop( vecVelocity ); +} + +Activity CWeaponUSP::GetDeployActivity( void ) +{ + if( IsSilenced() ) + { + return ACT_VM_DRAW_SILENCED; + } + else + { + return ACT_VM_DRAW; + } +} + +void CWeaponUSP::SecondaryAttack() +{ + if ( m_bSilencerOn ) + { + SendWeaponAnim( ACT_VM_DETACH_SILENCER ); + } + else + { + SendWeaponAnim( ACT_VM_ATTACH_SILENCER ); + } + m_bSilencerOn = !m_bSilencerOn; + m_weaponMode = m_bSilencerOn ? Secondary_Mode : Primary_Mode; + m_flDoneSwitchingSilencer = gpGlobals->curtime + 3; + + m_flNextSecondaryAttack = gpGlobals->curtime + 3; + m_flNextPrimaryAttack = gpGlobals->curtime + 3; + SetWeaponIdleTime( gpGlobals->curtime + 3 ); + + SetWeaponModelIndex( GetWorldModel() ); +} + + +float CWeaponUSP::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + { + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return 0.0f; + + if ( m_bSilencerOn ) + { + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.3f * (1 - m_flAccuracy); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.25f * (1 - m_flAccuracy); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.125f * (1 - m_flAccuracy); + else + return 0.15f * (1 - m_flAccuracy); + } + else + { + if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) ) + return 1.2f * (1 - m_flAccuracy ); + else if (pPlayer->GetAbsVelocity().Length2D() > 5) + return 0.225f * (1 - m_flAccuracy); + else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) ) + return 0.08f * (1 - m_flAccuracy); + else + return 0.1f * (1 - m_flAccuracy); + } + } + else + return BaseClass::GetInaccuracy(); +} + + +void CWeaponUSP::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + // Mark the time of this shot and determine the accuracy modifier based on the last shot fired... + m_flAccuracy -= (0.275)*(0.3 - (gpGlobals->curtime - m_flLastFire)); + + if (m_flAccuracy > 0.92) + m_flAccuracy = 0.92; + else if (m_flAccuracy < 0.6) + m_flAccuracy = 0.6; + + m_flLastFire = gpGlobals->curtime; + + if (m_iClip1 <= 0) + { + if ( m_bFireOnEmpty ) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + m_bFireOnEmpty = false; + } + + return; + } + + pPlayer->m_iShotsFired++; + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + flCycleTime; + + m_iClip1--; + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + + if ( !m_bSilencerOn ) + { + pPlayer->DoMuzzleFlash(); + } + + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + m_weaponMode, + CBaseEntity::GetPredictionRandomSeed() & 255, + GetInaccuracy(), + GetSpread()); + + if (!m_iClip1 && pPlayer->GetAmmoCount( GetPrimaryAmmoType() ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + SetWeaponIdleTime( gpGlobals->curtime + 2 ); + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[m_weaponMode]; + + QAngle angle = pPlayer->GetPunchAngle(); + angle.x -= 2; + pPlayer->SetPunchAngle( angle ); +} + + +bool CWeaponUSP::Reload() +{ + if ( !DefaultPistolReload() ) + return false; + + m_flAccuracy = 0.92; + return true; +} + +void CWeaponUSP::WeaponIdle() +{ + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return; + + // only idle if the slid isn't back + if (m_iClip1 != 0) + { + SetWeaponIdleTime( gpGlobals->curtime + 6.0 ); + } +} + +bool CWeaponUSP::SendWeaponAnim( int iActivity ) +{ + // Translate the activity? + if ( m_bSilencerOn ) + { + for ( int i=0; i < ARRAYSIZE( g_SilencedTranslations ); i++ ) + { + if ( g_SilencedTranslations[i][0] == iActivity ) + { + iActivity = g_SilencedTranslations[i][1]; + break; + } + } + } + + return BaseClass::SendWeaponAnim( iActivity ); +} + + +#ifdef CLIENT_DLL +int CWeaponUSP::GetMuzzleFlashStyle( void ) +{ + if( m_bSilencerOn ) + { + return CS_MUZZLEFLASH_NONE; + } + else + { + return CS_MUZZLEFLASH_NORM; + } +} +#endif diff --git a/game/shared/cstrike/weapon_xm1014.cpp b/game/shared/cstrike/weapon_xm1014.cpp new file mode 100644 index 0000000..4cead32 --- /dev/null +++ b/game/shared/cstrike/weapon_xm1014.cpp @@ -0,0 +1,304 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_csbase.h" +#include "fx_cs_shared.h" + + +#if defined( CLIENT_DLL ) + + #define CWeaponXM1014 C_WeaponXM1014 + #include "c_cs_player.h" + +#else + + #include "cs_player.h" + #include "te_shotgun_shot.h" + +#endif + + +class CWeaponXM1014 : public CWeaponCSBase +{ +public: + DECLARE_CLASS( CWeaponXM1014, CWeaponCSBase ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponXM1014(); + + virtual void Spawn(); + virtual void PrimaryAttack(); + virtual bool Reload(); + virtual void WeaponIdle(); + + virtual float GetInaccuracy() const; + virtual float GetSpread() const; + + virtual CSWeaponID GetWeaponID( void ) const { return WEAPON_XM1014; } + +private: + + CWeaponXM1014( const CWeaponXM1014 & ); + + float m_flPumpTime; + CNetworkVar( int, m_reloadState ); // special reload state for shotgun + +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponXM1014, DT_WeaponXM1014 ) + +BEGIN_NETWORK_TABLE( CWeaponXM1014, DT_WeaponXM1014 ) +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_reloadState ) ) +#else + SendPropInt( SENDINFO( m_reloadState ), 2, SPROP_UNSIGNED ) +#endif +END_NETWORK_TABLE() + +#if defined(CLIENT_DLL) +BEGIN_PREDICTION_DATA( CWeaponXM1014 ) + DEFINE_PRED_FIELD( m_reloadState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_xm1014, CWeaponXM1014 ); +PRECACHE_WEAPON_REGISTER( weapon_xm1014 ); + + +CWeaponXM1014::CWeaponXM1014() +{ + m_flPumpTime = 0; + m_reloadState = 0; +} + +void CWeaponXM1014::Spawn() +{ + //m_iDefaultAmmo = M3_DEFAULT_GIVE; + //FallInit();// get ready to fall + BaseClass::Spawn(); +} + +float CWeaponXM1014::GetInaccuracy() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + return 0.0f; + else + return BaseClass::GetInaccuracy(); +} + +float CWeaponXM1014::GetSpread() const +{ + if ( weapon_accuracy_model.GetInt() == 1 ) + return 0.0725f; + + return GetCSWpnData().m_fSpread[Primary_Mode]; +} + +void CWeaponXM1014::PrimaryAttack() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + float flCycleTime = GetCSWpnData().m_flCycleTime; + + // don't fire underwater + if (pPlayer->GetWaterLevel() == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.15; + return; + } + + if (m_iClip1 <= 0) + { + Reload(); + + if (m_iClip1 == 0) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.25; + } + + return; + } + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + //pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + //pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_iClip1--; + pPlayer->DoMuzzleFlash(); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // Dispatch the FX right away with full accuracy. + float flCurAttack = CalculateNextAttackTime( flCycleTime ); + FX_FireBullets( + pPlayer->entindex(), + pPlayer->Weapon_ShootPosition(), + pPlayer->EyeAngles() + 2.0f * pPlayer->GetPunchAngle(), + GetWeaponID(), + Primary_Mode, + CBaseEntity::GetPredictionRandomSeed() & 255, // wrap it for network traffic so it's the same between client and server + GetInaccuracy(), + GetSpread(), // flSpread + flCurAttack ); + + if (!m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", false, 0); + } + + if (m_iClip1 != 0) + m_flPumpTime = gpGlobals->curtime + 0.5; + + if (m_iClip1 != 0) + SetWeaponIdleTime( gpGlobals->curtime + 2.5 ); + else + SetWeaponIdleTime( gpGlobals->curtime + 0.25 ); + m_reloadState = 0; + + // update accuracy + m_fAccuracyPenalty += GetCSWpnData().m_fInaccuracyImpulseFire[Primary_Mode]; + + // Update punch angles. + QAngle angle = pPlayer->GetPunchAngle(); + + if ( pPlayer->GetFlags() & FL_ONGROUND ) + { + angle.x -= SharedRandomInt( "XM1014PunchAngleGround", 3, 5 ); + } + else + { + angle.x -= SharedRandomInt( "XM1014PunchAngleAir", 7, 10 ); + } + + pPlayer->SetPunchAngle( angle ); +} + + +bool CWeaponXM1014::Reload() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return false; + + if (pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 || m_iClip1 == GetMaxClip1()) + return true; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > gpGlobals->curtime) + return true; + + //MIKETODO: shotgun reloading (wait until we get content) + + // check to see if we're ready to reload + if (m_reloadState == 0) + { + pPlayer->SetAnimation( PLAYER_RELOAD ); + + SendWeaponAnim( ACT_SHOTGUN_RELOAD_START ); + m_reloadState = 1; + pPlayer->m_flNextAttack = gpGlobals->curtime + 0.5; + SetWeaponIdleTime( gpGlobals->curtime + 0.5 ); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; + +#ifdef GAME_DLL + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_START ); +#endif + + return true; + } + else if (m_reloadState == 1) + { + if (m_flTimeWeaponIdle > gpGlobals->curtime) + return true; + // was waiting for gun to move to side + m_reloadState = 2; + + SendWeaponAnim( ACT_VM_RELOAD ); + SetWeaponIdleTime( gpGlobals->curtime + 0.5 ); +#ifdef GAME_DLL + if ( m_iClip1 == 6 ) + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_END ); + } + else + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_LOOP ); + } +#endif + } + else + { + // Add them to the clip + m_iClip1 += 1; + +#ifdef GAME_DLL + SendReloadEvents(); +#endif + + CCSPlayer *pPlayer = GetPlayerOwner(); + + if ( pPlayer ) + pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType ); + + m_reloadState = 1; + } + + + return true; +} + + +void CWeaponXM1014::WeaponIdle() +{ + CCSPlayer *pPlayer = GetPlayerOwner(); + if ( !pPlayer ) + return; + + if (m_flPumpTime && m_flPumpTime < gpGlobals->curtime) + { + // play pumping sound + m_flPumpTime = 0; + } + + if (m_flTimeWeaponIdle < gpGlobals->curtime) + { + if (m_iClip1 == 0 && m_reloadState == 0 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + { + Reload( ); + } + else if (m_reloadState != 0) + { + if (m_iClip1 != 7 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + { + Reload( ); + } + else + { + // reload debounce has timed out + //MIKETODO: shotgun anims + SendWeaponAnim( ACT_SHOTGUN_RELOAD_FINISH ); + + // play cocking sound + m_reloadState = 0; + SetWeaponIdleTime( gpGlobals->curtime + 1.5 ); + } + } + else + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} |