From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/shared/multiplay_gamerules.cpp | 3642 ++++++++++++++-------------- 1 file changed, 1821 insertions(+), 1821 deletions(-) (limited to 'mp/src/game/shared/multiplay_gamerules.cpp') diff --git a/mp/src/game/shared/multiplay_gamerules.cpp b/mp/src/game/shared/multiplay_gamerules.cpp index 2cbb6f61..409f2362 100644 --- a/mp/src/game/shared/multiplay_gamerules.cpp +++ b/mp/src/game/shared/multiplay_gamerules.cpp @@ -1,1821 +1,1821 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Contains the implementation of game rules for multiplayer. -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" -#include "cdll_int.h" -#include "multiplay_gamerules.h" -#include "viewport_panel_names.h" -#include "gameeventdefs.h" -#include -#include "filesystem.h" -#include "mp_shareddefs.h" -#include "utlbuffer.h" - -#ifdef CLIENT_DLL - -#else - - #include "eventqueue.h" - #include "player.h" - #include "basecombatweapon.h" - #include "gamerules.h" - #include "game.h" - #include "items.h" - #include "entitylist.h" - #include "in_buttons.h" - #include - #include "voice_gamemgr.h" - #include "iscorer.h" - #include "hltvdirector.h" - #include "AI_Criteria.h" - #include "sceneentity.h" - #include "basemultiplayerplayer.h" - #include "team.h" - #include "usermessages.h" - #include "tier0/icommandline.h" - -#ifdef NEXT_BOT - #include "NextBotManager.h" -#endif - -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - - -REGISTER_GAMERULES_CLASS( CMultiplayRules ); - -ConVar mp_chattime( - "mp_chattime", - "10", - FCVAR_REPLICATED, - "amount of time players can chat after the game is over", - true, 1, - true, 120 ); - -#ifdef GAME_DLL -void MPTimeLimitCallback( IConVar *var, const char *pOldString, float flOldValue ) -{ - if ( mp_timelimit.GetInt() < 0 ) - { - mp_timelimit.SetValue( 0 ); - } - - if ( MultiplayRules() ) - { - MultiplayRules()->HandleTimeLimitChange(); - } -} -#endif - -ConVar mp_timelimit( "mp_timelimit", "0", FCVAR_NOTIFY|FCVAR_REPLICATED, "game time per map in minutes" -#ifdef GAME_DLL - , MPTimeLimitCallback -#endif - ); - -ConVar fraglimit( "mp_fraglimit","0", FCVAR_NOTIFY|FCVAR_REPLICATED, "The number of kills at which the map ends"); - -ConVar mp_show_voice_icons( "mp_show_voice_icons", "1", FCVAR_REPLICATED, "Show overhead player voice icons when players are speaking.\n" ); - -#ifdef GAME_DLL - -ConVar tv_delaymapchange( "tv_delaymapchange", "0", 0, "Delays map change until broadcast is complete" ); - -ConVar mp_restartgame( "mp_restartgame", "0", FCVAR_GAMEDLL, "If non-zero, game will restart in the specified number of seconds" ); -ConVar mp_restartgame_immediate( "mp_restartgame_immediate", "0", FCVAR_GAMEDLL, "If non-zero, game will restart immediately" ); - -ConVar mp_mapcycle_empty_timeout_seconds( "mp_mapcycle_empty_timeout_seconds", "0", FCVAR_REPLICATED, "If nonzero, server will cycle to the next map if it has been empty on the current map for N seconds"); - -void cc_SkipNextMapInCycle() -{ - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; - - if ( MultiplayRules() ) - { - MultiplayRules()->SkipNextMapInCycle(); - } -} - -void cc_GotoNextMapInCycle() -{ - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; - - if ( MultiplayRules() ) - { - MultiplayRules()->ChangeLevel(); - } -} - -ConCommand skip_next_map( "skip_next_map", cc_SkipNextMapInCycle, "Skips the next map in the map rotation for the server." ); -ConCommand changelevel_next( "changelevel_next", cc_GotoNextMapInCycle, "Immediately changes to the next map in the map rotation for the server." ); - -#ifndef TF_DLL // TF overrides the default value of this convar -ConVar mp_waitingforplayers_time( "mp_waitingforplayers_time", "0", FCVAR_GAMEDLL, "WaitingForPlayers time length in seconds" ); -#endif - -ConVar mp_waitingforplayers_restart( "mp_waitingforplayers_restart", "0", FCVAR_GAMEDLL, "Set to 1 to start or restart the WaitingForPlayers period." ); -ConVar mp_waitingforplayers_cancel( "mp_waitingforplayers_cancel", "0", FCVAR_GAMEDLL, "Set to 1 to end the WaitingForPlayers period." ); -ConVar mp_clan_readyrestart( "mp_clan_readyrestart", "0", FCVAR_GAMEDLL, "If non-zero, game will restart once someone from each team gives the ready signal" ); -ConVar mp_clan_ready_signal( "mp_clan_ready_signal", "ready", FCVAR_GAMEDLL, "Text that team leader from each team must speak for the match to begin" ); - -ConVar nextlevel( "nextlevel", - "", - FCVAR_GAMEDLL | FCVAR_NOTIFY, -#if defined( CSTRIKE_DLL ) || defined( TF_DLL ) - "If set to a valid map name, will trigger a changelevel to the specified map at the end of the round" ); -#else - "If set to a valid map name, will change to this map during the next changelevel" ); -#endif // CSTRIKE_DLL || TF_DLL - -#endif - -#ifndef CLIENT_DLL -int CMultiplayRules::m_nMapCycleTimeStamp = 0; -int CMultiplayRules::m_nMapCycleindex = 0; -CUtlVector CMultiplayRules::m_MapList; -#endif - -//========================================================= -//========================================================= -bool CMultiplayRules::IsMultiplayer( void ) -{ - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CMultiplayRules::Damage_GetTimeBased( void ) -{ - int iDamage = ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ); - return iDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CMultiplayRules::Damage_GetShouldGibCorpse( void ) -{ - int iDamage = ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ); - return iDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CMultiplayRules::Damage_GetShowOnHud( void ) -{ - int iDamage = ( DMG_POISON | DMG_ACID | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK ); - return iDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CMultiplayRules::Damage_GetNoPhysicsForce( void ) -{ - int iTimeBasedDamage = Damage_GetTimeBased(); - int iDamage = ( DMG_FALL | DMG_BURN | DMG_PLASMA | DMG_DROWN | iTimeBasedDamage | DMG_CRUSH | DMG_PHYSGUN | DMG_PREVENT_PHYSICS_FORCE ); - return iDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CMultiplayRules::Damage_GetShouldNotBleed( void ) -{ - int iDamage = ( DMG_POISON | DMG_ACID ); - return iDamage; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iDmgType - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMultiplayRules::Damage_IsTimeBased( int iDmgType ) -{ - // Damage types that are time-based. - return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ) ) != 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iDmgType - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMultiplayRules::Damage_ShouldGibCorpse( int iDmgType ) -{ - // Damage types that gib the corpse. - return ( ( iDmgType & ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) ) != 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iDmgType - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMultiplayRules::Damage_ShowOnHUD( int iDmgType ) -{ - // Damage types that have client HUD art. - return ( ( iDmgType & ( DMG_POISON | DMG_ACID | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK ) ) != 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iDmgType - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMultiplayRules::Damage_NoPhysicsForce( int iDmgType ) -{ - // Damage types that don't have to supply a physics force & position. - int iTimeBasedDamage = Damage_GetTimeBased(); - return ( ( iDmgType & ( DMG_FALL | DMG_BURN | DMG_PLASMA | DMG_DROWN | iTimeBasedDamage | DMG_CRUSH | DMG_PHYSGUN | DMG_PREVENT_PHYSICS_FORCE ) ) != 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : iDmgType - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CMultiplayRules::Damage_ShouldNotBleed( int iDmgType ) -{ - // Damage types that don't make the player bleed. - return ( ( iDmgType & ( DMG_POISON | DMG_ACID ) ) != 0 ); -} - -//********************************************************* -// Rules for the half-life multiplayer game. -//********************************************************* -CMultiplayRules::CMultiplayRules() -{ -#ifndef CLIENT_DLL - m_flTimeLastMapChangeOrPlayerWasConnected = 0.0f; - - RefreshSkillData( true ); - - // 11/8/98 - // Modified by YWB: Server .cfg file is now a cvar, so that - // server ops can run multiple game servers, with different server .cfg files, - // from a single installed directory. - // Mapcyclefile is already a cvar. - - // 3/31/99 - // Added lservercfg file cvar, since listen and dedicated servers should not - // share a single config file. (sjb) - if ( engine->IsDedicatedServer() ) - { - // dedicated server - const char *cfgfile = servercfgfile.GetString(); - - if ( cfgfile && cfgfile[0] ) - { - char szCommand[256]; - - Log( "Executing dedicated server config file %s\n", cfgfile ); - Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile ); - engine->ServerCommand( szCommand ); - } - } - else - { - // listen server - const char *cfgfile = lservercfgfile.GetString(); - - if ( cfgfile && cfgfile[0] ) - { - char szCommand[256]; - - Log( "Executing listen server config file %s\n", cfgfile ); - Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile ); - engine->ServerCommand( szCommand ); - } - } - - nextlevel.SetValue( "" ); - LoadMapCycleFile(); - -#endif - - LoadVoiceCommandScript(); -} - -bool CMultiplayRules::Init() -{ -#ifdef GAME_DLL - - // Initialize the custom response rule dictionaries. - InitCustomResponseRulesDicts(); - -#endif - - return BaseClass::Init(); -} - - -#ifdef CLIENT_DLL - - -#else - - extern bool g_fGameOver; - - #define ITEM_RESPAWN_TIME 30 - #define WEAPON_RESPAWN_TIME 20 - #define AMMO_RESPAWN_TIME 20 - - //========================================================= - //========================================================= - void CMultiplayRules::RefreshSkillData( bool forceUpdate ) - { - // load all default values - BaseClass::RefreshSkillData( forceUpdate ); - - // override some values for multiplay. - - // suitcharger -#ifndef TF_DLL -//============================================================================= -// HPE_BEGIN: -// [menglish] CS doesn't have the suitcharger either -//============================================================================= -#ifndef CSTRIKE_DLL -ConVarRef suitcharger( "sk_suitcharger" ); - suitcharger.SetValue( 30 ); - #endif -//============================================================================= -// HPE_END -//============================================================================= -#endif - } - - - //========================================================= - //========================================================= - void CMultiplayRules::Think ( void ) - { - BaseClass::Think(); - - ///// Check game rules ///// - - if ( g_fGameOver ) // someone else quit the game already - { - ChangeLevel(); // intermission is over - return; - } - - float flTimeLimit = mp_timelimit.GetFloat() * 60; - float flFragLimit = fraglimit.GetFloat(); - - if ( flTimeLimit != 0 && gpGlobals->curtime >= flTimeLimit ) - { - GoToIntermission(); - return; - } - - if ( flFragLimit ) - { - // check if any player is over the frag limit - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) - { - GoToIntermission(); - return; - } - } - } - } - - //========================================================= - //========================================================= - void CMultiplayRules::FrameUpdatePostEntityThink() - { - BaseClass::FrameUpdatePostEntityThink(); - - float flNow = Plat_FloatTime(); - - // Update time when client was last connected - if ( m_flTimeLastMapChangeOrPlayerWasConnected <= 0.0f ) - { - m_flTimeLastMapChangeOrPlayerWasConnected = flNow; - } - else - { - for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) - { - player_info_t pi; - if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) - continue; -#if defined( REPLAY_ENABLED ) - if ( pi.ishltv || pi.isreplay || pi.fakeplayer ) -#else - if ( pi.ishltv || pi.fakeplayer ) -#endif - continue; - - m_flTimeLastMapChangeOrPlayerWasConnected = flNow; - break; - } - } - - // Check if we should cycle the map because we've been empty - // for long enough - if ( mp_mapcycle_empty_timeout_seconds.GetInt() > 0 ) - { - int iIdleSeconds = (int)( flNow - m_flTimeLastMapChangeOrPlayerWasConnected ); - if ( iIdleSeconds >= mp_mapcycle_empty_timeout_seconds.GetInt() ) - { - - Log( "Server has been empty for %d seconds on this map, cycling map as per mp_mapcycle_empty_timeout_seconds\n", iIdleSeconds ); - ChangeLevel(); - } - } - } - - - //========================================================= - //========================================================= - bool CMultiplayRules::IsDeathmatch( void ) - { - return true; - } - - //========================================================= - //========================================================= - bool CMultiplayRules::IsCoOp( void ) - { - return false; - } - - //========================================================= - //========================================================= - bool CMultiplayRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) - { - if ( !pPlayer->Weapon_CanSwitchTo( pWeapon ) ) - { - // Can't switch weapons for some reason. - return false; - } - - if ( !pPlayer->GetActiveWeapon() ) - { - // Player doesn't have an active item, might as well switch. - return true; - } - - if ( !pWeapon->AllowsAutoSwitchTo() ) - { - // The given weapon should not be auto switched to from another weapon. - return false; - } - - if ( !pPlayer->GetActiveWeapon()->AllowsAutoSwitchFrom() ) - { - // The active weapon does not allow autoswitching away from it. - return false; - } - - if ( pWeapon->GetWeight() > pPlayer->GetActiveWeapon()->GetWeight() ) - { - return true; - } - - return false; - } - - //----------------------------------------------------------------------------- - // Purpose: Returns the weapon in the player's inventory that would be better than - // the given weapon. - //----------------------------------------------------------------------------- - CBaseCombatWeapon *CMultiplayRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) - { - CBaseCombatWeapon *pCheck; - CBaseCombatWeapon *pBest;// this will be used in the event that we don't find a weapon in the same category. - - int iCurrentWeight = -1; - int iBestWeight = -1;// no weapon lower than -1 can be autoswitched to - pBest = NULL; - - // If I have a weapon, make sure I'm allowed to holster it - if ( pCurrentWeapon ) - { - if ( !pCurrentWeapon->AllowsAutoSwitchFrom() || !pCurrentWeapon->CanHolster() ) - { - // Either this weapon doesn't allow autoswitching away from it or I - // can't put this weapon away right now, so I can't switch. - return NULL; - } - - iCurrentWeight = pCurrentWeapon->GetWeight(); - } - - for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) - { - pCheck = pPlayer->GetWeapon( i ); - if ( !pCheck ) - continue; - - // If we have an active weapon and this weapon doesn't allow autoswitching away - // from another weapon, skip it. - if ( pCurrentWeapon && !pCheck->AllowsAutoSwitchTo() ) - continue; - - if ( pCheck->GetWeight() > -1 && pCheck->GetWeight() == iCurrentWeight && pCheck != pCurrentWeapon ) - { - // this weapon is from the same category. - if ( pCheck->HasAnyAmmo() ) - { - if ( pPlayer->Weapon_CanSwitchTo( pCheck ) ) - { - return pCheck; - } - } - } - else if ( pCheck->GetWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of - { - //Msg( "Considering %s\n", STRING( pCheck->GetClassname() ); - // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight - // that the player was using. This will end up leaving the player with his heaviest-weighted - // weapon. - if ( pCheck->HasAnyAmmo() ) - { - // if this weapon is useable, flag it as the best - iBestWeight = pCheck->GetWeight(); - pBest = pCheck; - } - } - } - - // if we make it here, we've checked all the weapons and found no useable - // weapon in the same catagory as the current weapon. - - // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always - // at least get the crowbar, but ya never know. - return pBest; - } - - //----------------------------------------------------------------------------- - // Purpose: - // Output : Returns true on success, false on failure. - //----------------------------------------------------------------------------- - bool CMultiplayRules::SwitchToNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) - { - CBaseCombatWeapon *pWeapon = GetNextBestWeapon( pPlayer, pCurrentWeapon ); - - if ( pWeapon != NULL ) - return pPlayer->Weapon_Switch( pWeapon ); - - return false; - } - - //========================================================= - //========================================================= - bool CMultiplayRules::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) - { - GetVoiceGameMgr()->ClientConnected( pEntity ); - return true; - } - - void CMultiplayRules::InitHUD( CBasePlayer *pl ) - { - } - - //========================================================= - //========================================================= - void CMultiplayRules::ClientDisconnected( edict_t *pClient ) - { - if ( pClient ) - { - CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); - - if ( pPlayer ) - { - FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); - - pPlayer->RemoveAllItems( true );// destroy all of the players weapons and items - - // Kill off view model entities - pPlayer->DestroyViewModels(); - - pPlayer->SetConnected( PlayerDisconnected ); - } - } - } - - //========================================================= - //========================================================= - float CMultiplayRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) - { - int iFallDamage = (int)falldamage.GetFloat(); - - switch ( iFallDamage ) - { - case 1://progressive - pPlayer->m_Local.m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; - return pPlayer->m_Local.m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; - break; - default: - case 0:// fixed - return 10; - break; - } - } - - //========================================================= - //========================================================= - bool CMultiplayRules::AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info ) - { - return true; - } - - //========================================================= - //========================================================= - bool CMultiplayRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) - { - return true; - } - - //========================================================= - //========================================================= - void CMultiplayRules::PlayerThink( CBasePlayer *pPlayer ) - { - if ( g_fGameOver ) - { - // clear attack/use commands from player - pPlayer->m_afButtonPressed = 0; - pPlayer->m_nButtons = 0; - pPlayer->m_afButtonReleased = 0; - } - } - - //========================================================= - //========================================================= - void CMultiplayRules::PlayerSpawn( CBasePlayer *pPlayer ) - { - bool addDefault; - CBaseEntity *pWeaponEntity = NULL; - - pPlayer->EquipSuit(); - - addDefault = true; - - while ( (pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL) - { - pWeaponEntity->Touch( pPlayer ); - addDefault = false; - } - } - - //========================================================= - //========================================================= - bool CMultiplayRules::FPlayerCanRespawn( CBasePlayer *pPlayer ) - { - return true; - } - - //========================================================= - //========================================================= - float CMultiplayRules::FlPlayerSpawnTime( CBasePlayer *pPlayer ) - { - return gpGlobals->curtime;//now! - } - - bool CMultiplayRules::AllowAutoTargetCrosshair( void ) - { - return ( aimcrosshair.GetInt() != 0 ); - } - - //========================================================= - // IPointsForKill - how many points awarded to anyone - // that kills this player? - //========================================================= - int CMultiplayRules::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) - { - return 1; - } - - //----------------------------------------------------------------------------- - // Purpose: - //----------------------------------------------------------------------------- - CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor ) - { - if ( pKiller) - { - if ( pKiller->Classify() == CLASS_PLAYER ) - return (CBasePlayer*)pKiller; - - // Killing entity might be specifying a scorer player - IScorer *pScorerInterface = dynamic_cast( pKiller ); - if ( pScorerInterface ) - { - CBasePlayer *pPlayer = pScorerInterface->GetScorer(); - if ( pPlayer ) - return pPlayer; - } - - // Inflicting entity might be specifying a scoring player - pScorerInterface = dynamic_cast( pInflictor ); - if ( pScorerInterface ) - { - CBasePlayer *pPlayer = pScorerInterface->GetScorer(); - if ( pPlayer ) - return pPlayer; - } - } - - return NULL; - } - - //----------------------------------------------------------------------------- - // Purpose: Returns player who should receive credit for kill - //----------------------------------------------------------------------------- - CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor, CBaseEntity *pVictim ) - { - // if this method not overridden by subclass, just call our default implementation - return GetDeathScorer( pKiller, pInflictor ); - } - - //========================================================= - // PlayerKilled - someone/something killed this player - //========================================================= - void CMultiplayRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) - { - DeathNotice( pVictim, info ); - - // Find the killer & the scorer - CBaseEntity *pInflictor = info.GetInflictor(); - CBaseEntity *pKiller = info.GetAttacker(); - CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim ); - - pVictim->IncrementDeathCount( 1 ); - - // 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 ); - - // Did the player kill himself? - if ( pVictim == pScorer ) - { - if ( UseSuicidePenalty() ) - { - // Players lose a frag for killing themselves - pVictim->IncrementFragCount( -1 ); - } - } - else if ( pScorer ) - { - // if a player dies in a deathmatch game and the killer is a client, award the killer some points - pScorer->IncrementFragCount( IPointsForKill( pScorer, pVictim ) ); - - // Allow the scorer to immediately paint a decal - pScorer->AllowImmediateDecalPainting(); - - // dvsents2: uncomment when removing all FireTargets - //variant_t value; - //g_EventQueue.AddEvent( "game_playerkill", "Use", value, 0, pScorer, pScorer ); - FireTargets( "game_playerkill", pScorer, pScorer, USE_TOGGLE, 0 ); - } - else - { - if ( UseSuicidePenalty() ) - { - // Players lose a frag for letting the world kill them - pVictim->IncrementFragCount( -1 ); - } - } - } - - //========================================================= - // Deathnotice. - //========================================================= - void CMultiplayRules::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, pVictim ); - - // Custom damage type? - if ( info.GetDamageCustom() ) - { - killer_weapon_name = GetDamageCustomString( info ); - if ( pScorer ) - { - killer_ID = pScorer->GetUserID(); - } - } - else - { - // Is the killer a client? - if ( pScorer ) - { - killer_ID = pScorer->GetUserID(); - - 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()->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; - } - } - - IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); - if ( event ) - { - event->SetInt("userid", pVictim->GetUserID() ); - event->SetInt("attacker", killer_ID ); - event->SetInt("customkill", info.GetDamageCustom() ); - event->SetInt("priority", 7 ); // HLTV event priority, not transmitted - - gameeventmanager->FireEvent( event ); - } - - } - - //========================================================= - // FlWeaponRespawnTime - what is the time in the future - // at which this weapon may spawn? - //========================================================= - float CMultiplayRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) - { - if ( weaponstay.GetInt() > 0 ) - { - // make sure it's only certain weapons - if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) - { - return gpGlobals->curtime + 0; // weapon respawns almost instantly - } - } - - return gpGlobals->curtime + WEAPON_RESPAWN_TIME; - } - - // when we are within this close to running out of entities, items - // marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn - #define ENTITY_INTOLERANCE 100 - - //========================================================= - // FlWeaponRespawnTime - Returns 0 if the weapon can respawn - // now, otherwise it returns the time at which it can try - // to spawn again. - //========================================================= - float CMultiplayRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) - { - if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) - { - if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) - return 0; - - // we're past the entity tolerance level, so delay the respawn - return FlWeaponRespawnTime( pWeapon ); - } - - return 0; - } - - //========================================================= - // VecWeaponRespawnSpot - where should this weapon spawn? - // Some game variations may choose to randomize spawn locations - //========================================================= - Vector CMultiplayRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) - { - return pWeapon->GetAbsOrigin(); - } - - //========================================================= - // WeaponShouldRespawn - any conditions inhibiting the - // respawning of this weapon? - //========================================================= - int CMultiplayRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) - { - if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) - { - return GR_WEAPON_RESPAWN_NO; - } - - return GR_WEAPON_RESPAWN_YES; - } - - //========================================================= - // CanHaveWeapon - returns false if the player is not allowed - // to pick up this weapon - //========================================================= - bool CMultiplayRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) - { - if ( weaponstay.GetInt() > 0 ) - { - if ( pItem->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD ) - return BaseClass::CanHavePlayerItem( pPlayer, pItem ); - - // check if the player already has this weapon - for ( int i = 0 ; i < pPlayer->WeaponCount() ; i++ ) - { - if ( pPlayer->GetWeapon(i) == pItem ) - { - return false; - } - } - } - - return BaseClass::CanHavePlayerItem( pPlayer, pItem ); - } - - //========================================================= - //========================================================= - bool CMultiplayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) - { - return true; - } - - //========================================================= - //========================================================= - void CMultiplayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) - { - } - - //========================================================= - //========================================================= - int CMultiplayRules::ItemShouldRespawn( CItem *pItem ) - { - if ( pItem->HasSpawnFlags( SF_NORESPAWN ) ) - { - return GR_ITEM_RESPAWN_NO; - } - - return GR_ITEM_RESPAWN_YES; - } - - - //========================================================= - // At what time in the future may this Item respawn? - //========================================================= - float CMultiplayRules::FlItemRespawnTime( CItem *pItem ) - { - return gpGlobals->curtime + ITEM_RESPAWN_TIME; - } - - //========================================================= - // Where should this item respawn? - // Some game variations may choose to randomize spawn locations - //========================================================= - Vector CMultiplayRules::VecItemRespawnSpot( CItem *pItem ) - { - return pItem->GetAbsOrigin(); - } - - //========================================================= - // What angles should this item use to respawn? - //========================================================= - QAngle CMultiplayRules::VecItemRespawnAngles( CItem *pItem ) - { - return pItem->GetAbsAngles(); - } - - //========================================================= - //========================================================= - void CMultiplayRules::PlayerGotAmmo( CBaseCombatCharacter *pPlayer, char *szName, int iCount ) - { - } - - //========================================================= - //========================================================= - bool CMultiplayRules::IsAllowedToSpawn( CBaseEntity *pEntity ) - { - // if ( pEntity->GetFlags() & FL_NPC ) - // return false; - - return true; - } - - - //========================================================= - //========================================================= - float CMultiplayRules::FlHealthChargerRechargeTime( void ) - { - return 60; - } - - - float CMultiplayRules::FlHEVChargerRechargeTime( void ) - { - return 30; - } - - //========================================================= - //========================================================= - int CMultiplayRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) - { - return GR_PLR_DROP_GUN_ACTIVE; - } - - //========================================================= - //========================================================= - int CMultiplayRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) - { - return GR_PLR_DROP_AMMO_ACTIVE; - } - - CBaseEntity *CMultiplayRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) - { - CBaseEntity *pentSpawnSpot = BaseClass::GetPlayerSpawnSpot( pPlayer ); - - //!! replace this with an Event - /* - if ( IsMultiplayer() && pentSpawnSpot->m_target ) - { - FireTargets( STRING(pentSpawnSpot->m_target), pPlayer, pPlayer, USE_TOGGLE, 0 ); // dvsents2: what is this code supposed to do? - } - */ - - return pentSpawnSpot; - } - - - //========================================================= - //========================================================= - bool CMultiplayRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker ) - { - return ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE ); - } - - int CMultiplayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) - { - // half life deathmatch has only enemies - return GR_NOTTEAMMATE; - } - - bool CMultiplayRules::PlayFootstepSounds( CBasePlayer *pl ) - { - if ( footsteps.GetInt() == 0 ) - return false; - - if ( pl->IsOnLadder() || pl->GetAbsVelocity().Length2D() > 220 ) - return true; // only make step sounds in multiplayer if the player is moving fast enough - - return false; - } - - bool CMultiplayRules::FAllowFlashlight( void ) - { - return flashlight.GetInt() != 0; - } - - //========================================================= - //========================================================= - bool CMultiplayRules::FAllowNPCs( void ) - { - return true; // E3 hack - return ( allowNPCs.GetInt() != 0 ); - } - - //========================================================= - //======== CMultiplayRules private functions =========== - - void CMultiplayRules::GoToIntermission( void ) - { - if ( g_fGameOver ) - return; - - g_fGameOver = true; - - float flWaitTime = mp_chattime.GetInt(); - - if ( tv_delaymapchange.GetBool() ) - { - if ( HLTVDirector()->IsActive() ) - flWaitTime = MAX( flWaitTime, HLTVDirector()->GetDelay() ); - } - - m_flIntermissionEndTime = gpGlobals->curtime + flWaitTime; - - for ( int i = 1; i <= MAX_PLAYERS; i++ ) - { - CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); - - if ( !pPlayer ) - continue; - - pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); - } - } - - void StripChar(char *szBuffer, const char cWhiteSpace ) - { - - while ( char *pSpace = strchr( szBuffer, cWhiteSpace ) ) - { - char *pNextChar = pSpace + sizeof(char); - V_strcpy( pSpace, pNextChar ); - } - } - - void CMultiplayRules::GetNextLevelName( char *pszNextMap, int bufsize, bool bRandom /* = false */ ) - { - char mapcfile[256]; - DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false ); - - // Check the time of the mapcycle file and re-populate the list of level names if the file has been modified - const int nMapCycleTimeStamp = filesystem->GetPathTime( mapcfile, "GAME" ); - - if ( 0 == nMapCycleTimeStamp ) - { - // Map cycle file does not exist, make a list containing only the current map - char *szCurrentMapName = new char[MAX_MAP_NAME]; - Q_strncpy( szCurrentMapName, STRING(gpGlobals->mapname), MAX_MAP_NAME ); - m_MapList.AddToTail( szCurrentMapName ); - } - else - { - // If map cycle file has changed or this is the first time through ... - if ( m_nMapCycleTimeStamp != nMapCycleTimeStamp ) - { - // Reset map index and map cycle timestamp - m_nMapCycleTimeStamp = nMapCycleTimeStamp; - m_nMapCycleindex = 0; - - LoadMapCycleFile(); - } - } - - // If somehow we have no maps in the list then add the current one - if ( 0 == m_MapList.Count() ) - { - char *szDefaultMapName = new char[MAX_MAP_NAME]; - Q_strncpy( szDefaultMapName, STRING(gpGlobals->mapname), MAX_MAP_NAME ); - m_MapList.AddToTail( szDefaultMapName ); - } - - if ( bRandom ) - { - m_nMapCycleindex = RandomInt( 0, m_MapList.Count() - 1 ); - } - - // Here's the return value - Q_strncpy( pszNextMap, m_MapList[m_nMapCycleindex], bufsize); - } - - void CMultiplayRules::DetermineMapCycleFilename( char *pszResult, int nSizeResult, bool bForceSpew ) - { - static char szLastResult[ 256]; - - const char *pszVar = mapcyclefile.GetString(); - if ( *pszVar == '\0' ) - { - if ( bForceSpew || V_stricmp( szLastResult, "__novar") ) - { - Msg( "mapcyclefile convar not set.\n" ); - V_strcpy_safe( szLastResult, "__novar" ); - } - *pszResult = '\0'; - return; - } - - char szRecommendedName[ 256 ]; - V_sprintf_safe( szRecommendedName, "cfg/%s", pszVar ); - - // First, look for a mapcycle file in the cfg directory, which is preferred - V_strncpy( pszResult, szRecommendedName, nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) - { - if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) - { - Msg( "Using map cycle file '%s'.\n", pszResult ); - V_strcpy_safe( szLastResult, pszResult ); - } - return; - } - - // Nope? Try the root. - V_strncpy( pszResult, pszVar, nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) - { - if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) - { - Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); - V_strcpy_safe( szLastResult, pszResult ); - } - return; - } - - // Nope? Use the default. - if ( !V_stricmp( pszVar, "mapcycle.txt" ) ) - { - V_strncpy( pszResult, "cfg/mapcycle_default.txt", nSizeResult ); - if ( filesystem->FileExists( pszResult, "GAME" ) ) - { - if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) - { - Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); - V_strcpy_safe( szLastResult, pszResult ); - } - return; - } - } - - // Failed - *pszResult = '\0'; - if ( bForceSpew || V_stricmp( szLastResult, "__notfound") ) - { - Msg( "Map cycle file '%s' was not found.\n", szRecommendedName ); - V_strcpy_safe( szLastResult, "__notfound" ); - } - } - - void CMultiplayRules::LoapMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector &mapList ) - { - CUtlBuffer buf; - if ( !filesystem->ReadFile( pszMapCycleFile, "GAME", buf ) ) - return; - buf.PutChar( 0 ); - V_SplitString( (char*)buf.Base(), "\n", mapList ); - - for ( int i = 0; i < mapList.Count(); i++ ) - { - bool bIgnore = false; - - // Strip out the spaces in the name - StripChar( mapList[i] , '\r'); - StripChar( mapList[i] , ' '); - - if ( !Q_strncmp( mapList[i], "//", 2 ) || mapList[i][0] == '\0' ) - { - bIgnore = true; - } - else if ( !engine->IsMapValid( mapList[i] ) ) - { - bIgnore = true; - - // If the engine doesn't consider it a valid map remove it from the lists - Warning( "Invalid map '%s' included in map cycle file. Ignored.\n", mapList[i] ); - } - - if ( bIgnore ) - { - delete [] mapList[i]; - mapList.Remove( i ); - --i; - } - } - } - - void CMultiplayRules::FreeMapCycleFileVector( CUtlVector &mapList ) - { - // Clear out existing map list. Not using Purge() or PurgeAndDeleteAll() because they won't delete [] each element. - for ( int i = 0; i < mapList.Count(); i++ ) - { - delete [] mapList[i]; - } - - mapList.RemoveAll(); - } - - bool CMultiplayRules::IsMapInMapCycle( const char *pszName ) - { - for ( int i = 0; i < m_MapList.Count(); i++ ) - { - if ( V_stricmp( pszName, m_MapList[i] ) == 0 ) - { - return true; - } - } - - return false; - } - - void CMultiplayRules::ChangeLevel( void ) - { - char szNextMap[MAX_MAP_NAME]; - - if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) - { - Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) ); - } - else - { - GetNextLevelName( szNextMap, sizeof(szNextMap) ); - IncrementMapCycleIndex(); - } - - ChangeLevelToMap( szNextMap ); - } - - void CMultiplayRules::LoadMapCycleFile( void ) - { - char mapcfile[256]; - DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false ); - - FreeMapCycleFileVector( m_MapList ); - - // Repopulate map list from mapcycle file - LoapMapCycleFileIntoVector( mapcfile, m_MapList ); - - // Load server's mapcycle into network string table for client-side voting - if ( g_pStringTableServerMapCycle ) - { - CUtlString sFileList; - for ( int i = 0; i < m_MapList.Count(); i++ ) - { - sFileList += m_MapList[i]; - sFileList += '\n'; - } - - g_pStringTableServerMapCycle->AddString( CBaseEntity::IsServer(), "ServerMapCycle", sFileList.Length() + 1, sFileList.String() ); - } - -#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL ) - if ( g_pStringTableServerPopFiles ) - { - // Search for all pop files that are prefixed with the current map name - CUtlString sFileList; - - char szBaseName[_MAX_PATH]; - V_snprintf( szBaseName, sizeof( szBaseName ), "scripts/population/%s*.pop", STRING(gpGlobals->mapname) ); - - FileFindHandle_t popHandle; - const char *pPopFileName = filesystem->FindFirst( szBaseName, &popHandle ); - - while ( pPopFileName && pPopFileName[ 0 ] != '\0' ) - { - // Skip it if it's a directory or is the folder info - if ( filesystem->FindIsDirectory( popHandle ) ) - { - pPopFileName = filesystem->FindNext( popHandle ); - continue; - } - - const char *pchPopPostfix = StringAfterPrefix( pPopFileName, STRING(gpGlobals->mapname) ); - if ( pchPopPostfix ) - { - char szShortName[_MAX_PATH]; - V_strncpy( szShortName, ( ( pchPopPostfix[ 0 ] == '_' ) ? ( pchPopPostfix + 1 ) : "normal" ), sizeof( szShortName ) ); // skip the '_' - V_StripExtension( szShortName, szShortName, sizeof( szShortName ) ); - - sFileList += szShortName; - sFileList += '\n'; - } - - pPopFileName = filesystem->FindNext( popHandle ); - } - - filesystem->FindClose( popHandle ); - - if ( sFileList.Length() > 0 ) - { - g_pStringTableServerPopFiles->AddString( CBaseEntity::IsServer(), "ServerPopFiles", sFileList.Length() + 1, sFileList.String() ); - } - } - - if ( g_pStringTableServerMapCycleMvM ) - { - ConVarRef tf_mvm_missioncyclefile( "tf_mvm_missioncyclefile" ); - KeyValues *pKV = new KeyValues( tf_mvm_missioncyclefile.GetString() ); - if ( pKV->LoadFromFile( g_pFullFileSystem, tf_mvm_missioncyclefile.GetString(), "MOD" ) ) - { - CUtlVector mapList; - - // Parse the maps and send a list to each client for vote options - int iMaxCat = pKV->GetInt( "categories", 0 ); - for ( int iCat = 1; iCat <= iMaxCat; iCat++ ) - { - KeyValues *pCategory = pKV->FindKey( UTIL_VarArgs( "%d", iCat ), false ); - if ( pCategory ) - { - int iMapCount = pCategory->GetInt( "count", 0 ); - for ( int iMap = 1; iMap <= iMapCount; ++iMap ) - { - KeyValues *pMission = pCategory->FindKey( UTIL_VarArgs( "%d", iMap ), false ); - if ( pMission ) - { - const char *pszMap = pMission->GetString( "map", "" ); - int iIdx = mapList.Find( pszMap ); - if ( !mapList.IsValidIndex( iIdx ) ) - { - mapList.AddToTail( pszMap ); - } - } - } - } - } - - if ( mapList.Count() ) - { - CUtlString sFileList; - for ( int i = 0; i < mapList.Count(); i++ ) - { - sFileList += mapList[i]; - sFileList += '\n'; - } - - g_pStringTableServerMapCycleMvM->AddString( CBaseEntity::IsServer(), "ServerMapCycleMvM", sFileList.Length() + 1, sFileList.String() ); - } - - pKV->deleteThis(); - } - } -#endif - - // If the current map selection is in the list, set m_nMapCycleindex to the map that follows it. - for ( int i = 0; i < m_MapList.Count(); i++ ) - { - if ( V_strcmp( STRING( gpGlobals->mapname ), m_MapList[i] ) == 0 ) - { - m_nMapCycleindex = i; - IncrementMapCycleIndex(); - break; - } - } - } - - void CMultiplayRules::ChangeLevelToMap( const char *pszMap ) - { - g_fGameOver = true; - m_flTimeLastMapChangeOrPlayerWasConnected = 0.0f; - Msg( "CHANGE LEVEL: %s\n", pszMap ); - engine->ChangeLevel( pszMap, NULL ); - } - - -#endif - - - //----------------------------------------------------------------------------- - // Purpose: Shared script resource of voice menu commands and hud strings - //----------------------------------------------------------------------------- - void CMultiplayRules::LoadVoiceCommandScript( void ) - { - KeyValues *pKV = new KeyValues( "VoiceCommands" ); - - if ( pKV->LoadFromFile( filesystem, "scripts/voicecommands.txt", "GAME" ) ) - { - for ( KeyValues *menu = pKV->GetFirstSubKey(); menu != NULL; menu = menu->GetNextKey() ) - { - int iMenuIndex = m_VoiceCommandMenus.AddToTail(); - - int iNumItems = 0; - - // for each subkey of this menu, add a menu item - for ( KeyValues *menuitem = menu->GetFirstSubKey(); menuitem != NULL; menuitem = menuitem->GetNextKey() ) - { - iNumItems++; - - if ( iNumItems > 9 ) - { - Warning( "Trying to load more than 9 menu items in voicecommands.txt, extras ignored" ); - continue; - } - - VoiceCommandMenuItem_t item; - -#ifndef CLIENT_DLL - int iConcept = GetMPConceptIndexFromString( menuitem->GetString( "concept", "" ) ); - if ( iConcept == MP_CONCEPT_NONE ) - { - Warning( "Voicecommand script attempting to use unknown concept. Need to define new concepts in code. ( %s )\n", menuitem->GetString( "concept", "" ) ); - } - item.m_iConcept = iConcept; - - item.m_bShowSubtitle = ( menuitem->GetInt( "show_subtitle", 0 ) > 0 ); - item.m_bDistanceBasedSubtitle = ( menuitem->GetInt( "distance_check_subtitle", 0 ) > 0 ); - - Q_strncpy( item.m_szGestureActivity, menuitem->GetString( "activity", "" ), sizeof( item.m_szGestureActivity ) ); -#else - Q_strncpy( item.m_szSubtitle, menuitem->GetString( "subtitle", "" ), MAX_VOICE_COMMAND_SUBTITLE ); - Q_strncpy( item.m_szMenuLabel, menuitem->GetString( "menu_label", "" ), MAX_VOICE_COMMAND_SUBTITLE ); - -#endif - m_VoiceCommandMenus.Element( iMenuIndex ).AddToTail( item ); - } - } - } - - pKV->deleteThis(); - } - -#ifndef CLIENT_DLL - - void CMultiplayRules::SkipNextMapInCycle() - { - char szSkippedMap[MAX_MAP_NAME]; - char szNextMap[MAX_MAP_NAME]; - - GetNextLevelName( szSkippedMap, sizeof( szSkippedMap ) ); - IncrementMapCycleIndex(); - GetNextLevelName( szNextMap, sizeof( szNextMap ) ); - - Msg( "Skipping: %s\tNext map: %s\n", szSkippedMap, szNextMap ); - - if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) - { - Msg( "Warning! \"nextlevel\" is set to \"%s\" and will override the next map to be played.\n", nextlevel.GetString() ); - } - } - - void CMultiplayRules::IncrementMapCycleIndex() - { - // Reset index if we've passed the end of the map list - if ( ++m_nMapCycleindex >= m_MapList.Count() ) - { - m_nMapCycleindex = 0; - } - } - - bool CMultiplayRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) - { - CBasePlayer *pPlayer = ToBasePlayer( pEdict ); - - const char *pcmd = args[0]; - if ( FStrEq( pcmd, "voicemenu" ) ) - { - if ( args.ArgC() < 3 ) - return true; - - CBaseMultiplayerPlayer *pMultiPlayerPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( pPlayer ); - - if ( pMultiPlayerPlayer ) - { - int iMenu = atoi( args[1] ); - int iItem = atoi( args[2] ); - - VoiceCommand( pMultiPlayerPlayer, iMenu, iItem ); - } - - return true; - } - - return BaseClass::ClientCommand( pEdict, args ); - } - - void CMultiplayRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) - { - CBaseMultiplayerPlayer *pPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( CBaseEntity::Instance( pEntity ) ); - - if ( !pPlayer ) - return; - - char const *pszCommand = pKeyValues->GetName(); - if ( pszCommand && pszCommand[0] ) - { - if ( FStrEq( pszCommand, "AchievementEarned" ) ) - { - if ( pPlayer->ShouldAnnounceAchievement() ) - { - int nAchievementID = pKeyValues->GetInt( "achievementID" ); - - IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned" ); - if ( event ) - { - event->SetInt( "player", pPlayer->entindex() ); - event->SetInt( "achievement", nAchievementID ); - gameeventmanager->FireEvent( event ); - } - - pPlayer->OnAchievementEarned( nAchievementID ); - } - } - else if ( FStrEq( pszCommand, "SendServerMapCycle" ) ) - { - LoadMapCycleFile(); - } - } - } - - VoiceCommandMenuItem_t *CMultiplayRules::VoiceCommand( CBaseMultiplayerPlayer *pPlayer, int iMenu, int iItem ) - { - // have the player speak the concept that is in a particular menu slot - if ( !pPlayer ) - return NULL; - - if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) - return NULL; - - if ( iItem < 0 || iItem >= m_VoiceCommandMenus.Element( iMenu ).Count() ) - return NULL; - - VoiceCommandMenuItem_t *pItem = &m_VoiceCommandMenus.Element( iMenu ).Element( iItem ); - - Assert( pItem ); - - char szResponse[AI_Response::MAX_RESPONSE_NAME]; - - if ( pPlayer->CanSpeakVoiceCommand() ) - { - CMultiplayer_Expresser *pExpresser = pPlayer->GetMultiplayerExpresser(); - Assert( pExpresser ); - pExpresser->AllowMultipleScenes(); - - if ( pPlayer->SpeakConceptIfAllowed( pItem->m_iConcept, NULL, szResponse, AI_Response::MAX_RESPONSE_NAME ) ) - { - // show a subtitle if we need to - if ( pItem->m_bShowSubtitle ) - { - CRecipientFilter filter; - - if ( pItem->m_bDistanceBasedSubtitle ) - { - filter.AddRecipientsByPAS( pPlayer->WorldSpaceCenter() ); - - // further reduce the range to a certain radius - int i; - for ( i = filter.GetRecipientCount()-1; i >= 0; i-- ) - { - int index = filter.GetRecipientIndex(i); - - CBasePlayer *pListener = UTIL_PlayerByIndex( index ); - - if ( pListener && pListener != pPlayer ) - { - float flDist = ( pListener->WorldSpaceCenter() - pPlayer->WorldSpaceCenter() ).Length2D(); - - if ( flDist > VOICE_COMMAND_MAX_SUBTITLE_DIST ) - filter.RemoveRecipientByPlayerIndex( index ); - } - } - } - else - { - filter.AddAllPlayers(); - } - - // if we aren't a disguised spy - if ( !pPlayer->ShouldShowVoiceSubtitleToEnemy() ) - { - // remove players on other teams - filter.RemoveRecipientsNotOnTeam( pPlayer->GetTeam() ); - } - - // Register this event in the mod-specific usermessages .cpp file if you hit this assert - Assert( usermessages->LookupUserMessage( "VoiceSubtitle" ) != -1 ); - - // Send a subtitle to anyone in the PAS - UserMessageBegin( filter, "VoiceSubtitle" ); - WRITE_BYTE( pPlayer->entindex() ); - WRITE_BYTE( iMenu ); - WRITE_BYTE( iItem ); - MessageEnd(); - } - - pPlayer->NoteSpokeVoiceCommand( szResponse ); - -#ifdef NEXT_BOT - // let bots react to player's voice commands - CUtlVector< INextBot * > botVector; - TheNextBots().CollectAllBots( &botVector ); - - for( int i=0; iOnActorEmoted( pPlayer, pItem->m_iConcept ); - } -#endif - } - else - { - pItem = NULL; - } - - pExpresser->DisallowMultipleScenes(); - return pItem; - } - - return NULL; - } - - bool CMultiplayRules::IsLoadingBugBaitReport() - { - return ( !engine->IsDedicatedServer()&& CommandLine()->CheckParm( "-bugbait" ) && sv_cheats->GetBool() ); - } - - void CMultiplayRules::HaveAllPlayersSpeakConceptIfAllowed( int iConcept, int iTeam /* = TEAM_UNASSIGNED */, const char *modifiers /* = NULL */ ) - { - CBaseMultiplayerPlayer *pPlayer; - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) ); - - if ( !pPlayer ) - continue; - - if ( iTeam != TEAM_UNASSIGNED ) - { - if ( pPlayer->GetTeamNumber() != iTeam ) - continue; - } - - pPlayer->SpeakConceptIfAllowed( iConcept, modifiers ); - } - } - - void CMultiplayRules::ClientSettingsChanged( CBasePlayer *pPlayer ) - { - // NVNT see if this user is still or has began using a haptic device - const char *pszHH = engine->GetClientConVarValue( pPlayer->entindex(), "hap_HasDevice" ); - if( pszHH ) - { - int iHH = atoi( pszHH ); - pPlayer->SetHaptics( iHH != 0 ); - } - - } - void CMultiplayRules::GetTaggedConVarList( KeyValues *pCvarTagList ) - { - BaseClass::GetTaggedConVarList( pCvarTagList ); - - // sv_gravity - KeyValues *pGravity = new KeyValues( "sv_gravity" ); - pGravity->SetString( "convar", "sv_gravity" ); - pGravity->SetString( "tag", "gravity" ); - - pCvarTagList->AddSubKey( pGravity ); - - // sv_alltalk - KeyValues *pAllTalk = new KeyValues( "sv_alltalk" ); - pAllTalk->SetString( "convar", "sv_alltalk" ); - pAllTalk->SetString( "tag", "alltalk" ); - - pCvarTagList->AddSubKey( pAllTalk ); - } - -#else - - const char *CMultiplayRules::GetVoiceCommandSubtitle( int iMenu, int iItem ) - { - Assert( iMenu >= 0 && iMenu < m_VoiceCommandMenus.Count() ); - if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) - return ""; - - Assert( iItem >= 0 && iItem < m_VoiceCommandMenus.Element( iMenu ).Count() ); - if ( iItem < 0 || iItem >= m_VoiceCommandMenus.Element( iMenu ).Count() ) - return ""; - - VoiceCommandMenuItem_t *pItem = &m_VoiceCommandMenus.Element( iMenu ).Element( iItem ); - - Assert( pItem ); - - return pItem->m_szSubtitle; - } - - // Returns false if no such menu is declared or if it's an empty menu - bool CMultiplayRules::GetVoiceMenuLabels( int iMenu, KeyValues *pKV ) - { - Assert( iMenu >= 0 && iMenu < m_VoiceCommandMenus.Count() ); - if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) - return false; - - int iNumItems = m_VoiceCommandMenus.Element( iMenu ).Count(); - - for ( int i=0; im_szMenuLabel ); - - pKV->AddSubKey( pLabelKV ); - } - - return iNumItems > 0; - } - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Contains the implementation of game rules for multiplayer. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "cdll_int.h" +#include "multiplay_gamerules.h" +#include "viewport_panel_names.h" +#include "gameeventdefs.h" +#include +#include "filesystem.h" +#include "mp_shareddefs.h" +#include "utlbuffer.h" + +#ifdef CLIENT_DLL + +#else + + #include "eventqueue.h" + #include "player.h" + #include "basecombatweapon.h" + #include "gamerules.h" + #include "game.h" + #include "items.h" + #include "entitylist.h" + #include "in_buttons.h" + #include + #include "voice_gamemgr.h" + #include "iscorer.h" + #include "hltvdirector.h" + #include "AI_Criteria.h" + #include "sceneentity.h" + #include "basemultiplayerplayer.h" + #include "team.h" + #include "usermessages.h" + #include "tier0/icommandline.h" + +#ifdef NEXT_BOT + #include "NextBotManager.h" +#endif + +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +REGISTER_GAMERULES_CLASS( CMultiplayRules ); + +ConVar mp_chattime( + "mp_chattime", + "10", + FCVAR_REPLICATED, + "amount of time players can chat after the game is over", + true, 1, + true, 120 ); + +#ifdef GAME_DLL +void MPTimeLimitCallback( IConVar *var, const char *pOldString, float flOldValue ) +{ + if ( mp_timelimit.GetInt() < 0 ) + { + mp_timelimit.SetValue( 0 ); + } + + if ( MultiplayRules() ) + { + MultiplayRules()->HandleTimeLimitChange(); + } +} +#endif + +ConVar mp_timelimit( "mp_timelimit", "0", FCVAR_NOTIFY|FCVAR_REPLICATED, "game time per map in minutes" +#ifdef GAME_DLL + , MPTimeLimitCallback +#endif + ); + +ConVar fraglimit( "mp_fraglimit","0", FCVAR_NOTIFY|FCVAR_REPLICATED, "The number of kills at which the map ends"); + +ConVar mp_show_voice_icons( "mp_show_voice_icons", "1", FCVAR_REPLICATED, "Show overhead player voice icons when players are speaking.\n" ); + +#ifdef GAME_DLL + +ConVar tv_delaymapchange( "tv_delaymapchange", "0", 0, "Delays map change until broadcast is complete" ); + +ConVar mp_restartgame( "mp_restartgame", "0", FCVAR_GAMEDLL, "If non-zero, game will restart in the specified number of seconds" ); +ConVar mp_restartgame_immediate( "mp_restartgame_immediate", "0", FCVAR_GAMEDLL, "If non-zero, game will restart immediately" ); + +ConVar mp_mapcycle_empty_timeout_seconds( "mp_mapcycle_empty_timeout_seconds", "0", FCVAR_REPLICATED, "If nonzero, server will cycle to the next map if it has been empty on the current map for N seconds"); + +void cc_SkipNextMapInCycle() +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( MultiplayRules() ) + { + MultiplayRules()->SkipNextMapInCycle(); + } +} + +void cc_GotoNextMapInCycle() +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( MultiplayRules() ) + { + MultiplayRules()->ChangeLevel(); + } +} + +ConCommand skip_next_map( "skip_next_map", cc_SkipNextMapInCycle, "Skips the next map in the map rotation for the server." ); +ConCommand changelevel_next( "changelevel_next", cc_GotoNextMapInCycle, "Immediately changes to the next map in the map rotation for the server." ); + +#ifndef TF_DLL // TF overrides the default value of this convar +ConVar mp_waitingforplayers_time( "mp_waitingforplayers_time", "0", FCVAR_GAMEDLL, "WaitingForPlayers time length in seconds" ); +#endif + +ConVar mp_waitingforplayers_restart( "mp_waitingforplayers_restart", "0", FCVAR_GAMEDLL, "Set to 1 to start or restart the WaitingForPlayers period." ); +ConVar mp_waitingforplayers_cancel( "mp_waitingforplayers_cancel", "0", FCVAR_GAMEDLL, "Set to 1 to end the WaitingForPlayers period." ); +ConVar mp_clan_readyrestart( "mp_clan_readyrestart", "0", FCVAR_GAMEDLL, "If non-zero, game will restart once someone from each team gives the ready signal" ); +ConVar mp_clan_ready_signal( "mp_clan_ready_signal", "ready", FCVAR_GAMEDLL, "Text that team leader from each team must speak for the match to begin" ); + +ConVar nextlevel( "nextlevel", + "", + FCVAR_GAMEDLL | FCVAR_NOTIFY, +#if defined( CSTRIKE_DLL ) || defined( TF_DLL ) + "If set to a valid map name, will trigger a changelevel to the specified map at the end of the round" ); +#else + "If set to a valid map name, will change to this map during the next changelevel" ); +#endif // CSTRIKE_DLL || TF_DLL + +#endif + +#ifndef CLIENT_DLL +int CMultiplayRules::m_nMapCycleTimeStamp = 0; +int CMultiplayRules::m_nMapCycleindex = 0; +CUtlVector CMultiplayRules::m_MapList; +#endif + +//========================================================= +//========================================================= +bool CMultiplayRules::IsMultiplayer( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMultiplayRules::Damage_GetTimeBased( void ) +{ + int iDamage = ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ); + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMultiplayRules::Damage_GetShouldGibCorpse( void ) +{ + int iDamage = ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ); + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMultiplayRules::Damage_GetShowOnHud( void ) +{ + int iDamage = ( DMG_POISON | DMG_ACID | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK ); + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMultiplayRules::Damage_GetNoPhysicsForce( void ) +{ + int iTimeBasedDamage = Damage_GetTimeBased(); + int iDamage = ( DMG_FALL | DMG_BURN | DMG_PLASMA | DMG_DROWN | iTimeBasedDamage | DMG_CRUSH | DMG_PHYSGUN | DMG_PREVENT_PHYSICS_FORCE ); + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMultiplayRules::Damage_GetShouldNotBleed( void ) +{ + int iDamage = ( DMG_POISON | DMG_ACID ); + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iDmgType - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMultiplayRules::Damage_IsTimeBased( int iDmgType ) +{ + // Damage types that are time-based. + return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ) ) != 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iDmgType - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMultiplayRules::Damage_ShouldGibCorpse( int iDmgType ) +{ + // Damage types that gib the corpse. + return ( ( iDmgType & ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) ) != 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iDmgType - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMultiplayRules::Damage_ShowOnHUD( int iDmgType ) +{ + // Damage types that have client HUD art. + return ( ( iDmgType & ( DMG_POISON | DMG_ACID | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK ) ) != 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iDmgType - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMultiplayRules::Damage_NoPhysicsForce( int iDmgType ) +{ + // Damage types that don't have to supply a physics force & position. + int iTimeBasedDamage = Damage_GetTimeBased(); + return ( ( iDmgType & ( DMG_FALL | DMG_BURN | DMG_PLASMA | DMG_DROWN | iTimeBasedDamage | DMG_CRUSH | DMG_PHYSGUN | DMG_PREVENT_PHYSICS_FORCE ) ) != 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iDmgType - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMultiplayRules::Damage_ShouldNotBleed( int iDmgType ) +{ + // Damage types that don't make the player bleed. + return ( ( iDmgType & ( DMG_POISON | DMG_ACID ) ) != 0 ); +} + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* +CMultiplayRules::CMultiplayRules() +{ +#ifndef CLIENT_DLL + m_flTimeLastMapChangeOrPlayerWasConnected = 0.0f; + + RefreshSkillData( true ); + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( engine->IsDedicatedServer() ) + { + // dedicated server + const char *cfgfile = servercfgfile.GetString(); + + if ( cfgfile && cfgfile[0] ) + { + char szCommand[256]; + + Log( "Executing dedicated server config file %s\n", cfgfile ); + Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile ); + engine->ServerCommand( szCommand ); + } + } + else + { + // listen server + const char *cfgfile = lservercfgfile.GetString(); + + if ( cfgfile && cfgfile[0] ) + { + char szCommand[256]; + + Log( "Executing listen server config file %s\n", cfgfile ); + Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile ); + engine->ServerCommand( szCommand ); + } + } + + nextlevel.SetValue( "" ); + LoadMapCycleFile(); + +#endif + + LoadVoiceCommandScript(); +} + +bool CMultiplayRules::Init() +{ +#ifdef GAME_DLL + + // Initialize the custom response rule dictionaries. + InitCustomResponseRulesDicts(); + +#endif + + return BaseClass::Init(); +} + + +#ifdef CLIENT_DLL + + +#else + + extern bool g_fGameOver; + + #define ITEM_RESPAWN_TIME 30 + #define WEAPON_RESPAWN_TIME 20 + #define AMMO_RESPAWN_TIME 20 + + //========================================================= + //========================================================= + void CMultiplayRules::RefreshSkillData( bool forceUpdate ) + { + // load all default values + BaseClass::RefreshSkillData( forceUpdate ); + + // override some values for multiplay. + + // suitcharger +#ifndef TF_DLL +//============================================================================= +// HPE_BEGIN: +// [menglish] CS doesn't have the suitcharger either +//============================================================================= +#ifndef CSTRIKE_DLL +ConVarRef suitcharger( "sk_suitcharger" ); + suitcharger.SetValue( 30 ); + #endif +//============================================================================= +// HPE_END +//============================================================================= +#endif + } + + + //========================================================= + //========================================================= + void CMultiplayRules::Think ( void ) + { + BaseClass::Think(); + + ///// Check game rules ///// + + if ( g_fGameOver ) // someone else quit the game already + { + ChangeLevel(); // intermission is over + return; + } + + float flTimeLimit = mp_timelimit.GetFloat() * 60; + float flFragLimit = fraglimit.GetFloat(); + + if ( flTimeLimit != 0 && gpGlobals->curtime >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + } + } + + //========================================================= + //========================================================= + void CMultiplayRules::FrameUpdatePostEntityThink() + { + BaseClass::FrameUpdatePostEntityThink(); + + float flNow = Plat_FloatTime(); + + // Update time when client was last connected + if ( m_flTimeLastMapChangeOrPlayerWasConnected <= 0.0f ) + { + m_flTimeLastMapChangeOrPlayerWasConnected = flNow; + } + else + { + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + player_info_t pi; + if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) + continue; +#if defined( REPLAY_ENABLED ) + if ( pi.ishltv || pi.isreplay || pi.fakeplayer ) +#else + if ( pi.ishltv || pi.fakeplayer ) +#endif + continue; + + m_flTimeLastMapChangeOrPlayerWasConnected = flNow; + break; + } + } + + // Check if we should cycle the map because we've been empty + // for long enough + if ( mp_mapcycle_empty_timeout_seconds.GetInt() > 0 ) + { + int iIdleSeconds = (int)( flNow - m_flTimeLastMapChangeOrPlayerWasConnected ); + if ( iIdleSeconds >= mp_mapcycle_empty_timeout_seconds.GetInt() ) + { + + Log( "Server has been empty for %d seconds on this map, cycling map as per mp_mapcycle_empty_timeout_seconds\n", iIdleSeconds ); + ChangeLevel(); + } + } + } + + + //========================================================= + //========================================================= + bool CMultiplayRules::IsDeathmatch( void ) + { + return true; + } + + //========================================================= + //========================================================= + bool CMultiplayRules::IsCoOp( void ) + { + return false; + } + + //========================================================= + //========================================================= + bool CMultiplayRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) + { + if ( !pPlayer->Weapon_CanSwitchTo( pWeapon ) ) + { + // Can't switch weapons for some reason. + return false; + } + + if ( !pPlayer->GetActiveWeapon() ) + { + // Player doesn't have an active item, might as well switch. + return true; + } + + if ( !pWeapon->AllowsAutoSwitchTo() ) + { + // The given weapon should not be auto switched to from another weapon. + return false; + } + + if ( !pPlayer->GetActiveWeapon()->AllowsAutoSwitchFrom() ) + { + // The active weapon does not allow autoswitching away from it. + return false; + } + + if ( pWeapon->GetWeight() > pPlayer->GetActiveWeapon()->GetWeight() ) + { + return true; + } + + return false; + } + + //----------------------------------------------------------------------------- + // Purpose: Returns the weapon in the player's inventory that would be better than + // the given weapon. + //----------------------------------------------------------------------------- + CBaseCombatWeapon *CMultiplayRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) + { + CBaseCombatWeapon *pCheck; + CBaseCombatWeapon *pBest;// this will be used in the event that we don't find a weapon in the same category. + + int iCurrentWeight = -1; + int iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + // If I have a weapon, make sure I'm allowed to holster it + if ( pCurrentWeapon ) + { + if ( !pCurrentWeapon->AllowsAutoSwitchFrom() || !pCurrentWeapon->CanHolster() ) + { + // Either this weapon doesn't allow autoswitching away from it or I + // can't put this weapon away right now, so I can't switch. + return NULL; + } + + iCurrentWeight = pCurrentWeapon->GetWeight(); + } + + for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) + { + pCheck = pPlayer->GetWeapon( i ); + if ( !pCheck ) + continue; + + // If we have an active weapon and this weapon doesn't allow autoswitching away + // from another weapon, skip it. + if ( pCurrentWeapon && !pCheck->AllowsAutoSwitchTo() ) + continue; + + if ( pCheck->GetWeight() > -1 && pCheck->GetWeight() == iCurrentWeight && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->HasAnyAmmo() ) + { + if ( pPlayer->Weapon_CanSwitchTo( pCheck ) ) + { + return pCheck; + } + } + } + else if ( pCheck->GetWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //Msg( "Considering %s\n", STRING( pCheck->GetClassname() ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->HasAnyAmmo() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->GetWeight(); + pBest = pCheck; + } + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + return pBest; + } + + //----------------------------------------------------------------------------- + // Purpose: + // Output : Returns true on success, false on failure. + //----------------------------------------------------------------------------- + bool CMultiplayRules::SwitchToNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon ) + { + CBaseCombatWeapon *pWeapon = GetNextBestWeapon( pPlayer, pCurrentWeapon ); + + if ( pWeapon != NULL ) + return pPlayer->Weapon_Switch( pWeapon ); + + return false; + } + + //========================================================= + //========================================================= + bool CMultiplayRules::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) + { + GetVoiceGameMgr()->ClientConnected( pEntity ); + return true; + } + + void CMultiplayRules::InitHUD( CBasePlayer *pl ) + { + } + + //========================================================= + //========================================================= + void CMultiplayRules::ClientDisconnected( edict_t *pClient ) + { + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + pPlayer->RemoveAllItems( true );// destroy all of the players weapons and items + + // Kill off view model entities + pPlayer->DestroyViewModels(); + + pPlayer->SetConnected( PlayerDisconnected ); + } + } + } + + //========================================================= + //========================================================= + float CMultiplayRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) + { + int iFallDamage = (int)falldamage.GetFloat(); + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_Local.m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_Local.m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } + } + + //========================================================= + //========================================================= + bool CMultiplayRules::AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info ) + { + return true; + } + + //========================================================= + //========================================================= + bool CMultiplayRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) + { + return true; + } + + //========================================================= + //========================================================= + void CMultiplayRules::PlayerThink( CBasePlayer *pPlayer ) + { + if ( g_fGameOver ) + { + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->m_nButtons = 0; + pPlayer->m_afButtonReleased = 0; + } + } + + //========================================================= + //========================================================= + void CMultiplayRules::PlayerSpawn( CBasePlayer *pPlayer ) + { + bool addDefault; + CBaseEntity *pWeaponEntity = NULL; + + pPlayer->EquipSuit(); + + addDefault = true; + + while ( (pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL) + { + pWeaponEntity->Touch( pPlayer ); + addDefault = false; + } + } + + //========================================================= + //========================================================= + bool CMultiplayRules::FPlayerCanRespawn( CBasePlayer *pPlayer ) + { + return true; + } + + //========================================================= + //========================================================= + float CMultiplayRules::FlPlayerSpawnTime( CBasePlayer *pPlayer ) + { + return gpGlobals->curtime;//now! + } + + bool CMultiplayRules::AllowAutoTargetCrosshair( void ) + { + return ( aimcrosshair.GetInt() != 0 ); + } + + //========================================================= + // IPointsForKill - how many points awarded to anyone + // that kills this player? + //========================================================= + int CMultiplayRules::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) + { + return 1; + } + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor ) + { + if ( pKiller) + { + if ( pKiller->Classify() == CLASS_PLAYER ) + return (CBasePlayer*)pKiller; + + // Killing entity might be specifying a scorer player + IScorer *pScorerInterface = dynamic_cast( pKiller ); + if ( pScorerInterface ) + { + CBasePlayer *pPlayer = pScorerInterface->GetScorer(); + if ( pPlayer ) + return pPlayer; + } + + // Inflicting entity might be specifying a scoring player + pScorerInterface = dynamic_cast( pInflictor ); + if ( pScorerInterface ) + { + CBasePlayer *pPlayer = pScorerInterface->GetScorer(); + if ( pPlayer ) + return pPlayer; + } + } + + return NULL; + } + + //----------------------------------------------------------------------------- + // Purpose: Returns player who should receive credit for kill + //----------------------------------------------------------------------------- + CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor, CBaseEntity *pVictim ) + { + // if this method not overridden by subclass, just call our default implementation + return GetDeathScorer( pKiller, pInflictor ); + } + + //========================================================= + // PlayerKilled - someone/something killed this player + //========================================================= + void CMultiplayRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) + { + DeathNotice( pVictim, info ); + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim ); + + pVictim->IncrementDeathCount( 1 ); + + // 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 ); + + // Did the player kill himself? + if ( pVictim == pScorer ) + { + if ( UseSuicidePenalty() ) + { + // Players lose a frag for killing themselves + pVictim->IncrementFragCount( -1 ); + } + } + else if ( pScorer ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pScorer->IncrementFragCount( IPointsForKill( pScorer, pVictim ) ); + + // Allow the scorer to immediately paint a decal + pScorer->AllowImmediateDecalPainting(); + + // dvsents2: uncomment when removing all FireTargets + //variant_t value; + //g_EventQueue.AddEvent( "game_playerkill", "Use", value, 0, pScorer, pScorer ); + FireTargets( "game_playerkill", pScorer, pScorer, USE_TOGGLE, 0 ); + } + else + { + if ( UseSuicidePenalty() ) + { + // Players lose a frag for letting the world kill them + pVictim->IncrementFragCount( -1 ); + } + } + } + + //========================================================= + // Deathnotice. + //========================================================= + void CMultiplayRules::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, pVictim ); + + // Custom damage type? + if ( info.GetDamageCustom() ) + { + killer_weapon_name = GetDamageCustomString( info ); + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + } + } + else + { + // Is the killer a client? + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + + 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()->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; + } + } + + IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); + if ( event ) + { + event->SetInt("userid", pVictim->GetUserID() ); + event->SetInt("attacker", killer_ID ); + event->SetInt("customkill", info.GetDamageCustom() ); + event->SetInt("priority", 7 ); // HLTV event priority, not transmitted + + gameeventmanager->FireEvent( event ); + } + + } + + //========================================================= + // FlWeaponRespawnTime - what is the time in the future + // at which this weapon may spawn? + //========================================================= + float CMultiplayRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) + { + if ( weaponstay.GetInt() > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->curtime + 0; // weapon respawns almost instantly + } + } + + return gpGlobals->curtime + WEAPON_RESPAWN_TIME; + } + + // when we are within this close to running out of entities, items + // marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn + #define ENTITY_INTOLERANCE 100 + + //========================================================= + // FlWeaponRespawnTime - Returns 0 if the weapon can respawn + // now, otherwise it returns the time at which it can try + // to spawn again. + //========================================================= + float CMultiplayRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) + { + if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; + } + + //========================================================= + // VecWeaponRespawnSpot - where should this weapon spawn? + // Some game variations may choose to randomize spawn locations + //========================================================= + Vector CMultiplayRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) + { + return pWeapon->GetAbsOrigin(); + } + + //========================================================= + // WeaponShouldRespawn - any conditions inhibiting the + // respawning of this weapon? + //========================================================= + int CMultiplayRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) + { + if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; + } + + //========================================================= + // CanHaveWeapon - returns false if the player is not allowed + // to pick up this weapon + //========================================================= + bool CMultiplayRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) + { + if ( weaponstay.GetInt() > 0 ) + { + if ( pItem->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD ) + return BaseClass::CanHavePlayerItem( pPlayer, pItem ); + + // check if the player already has this weapon + for ( int i = 0 ; i < pPlayer->WeaponCount() ; i++ ) + { + if ( pPlayer->GetWeapon(i) == pItem ) + { + return false; + } + } + } + + return BaseClass::CanHavePlayerItem( pPlayer, pItem ); + } + + //========================================================= + //========================================================= + bool CMultiplayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) + { + return true; + } + + //========================================================= + //========================================================= + void CMultiplayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) + { + } + + //========================================================= + //========================================================= + int CMultiplayRules::ItemShouldRespawn( CItem *pItem ) + { + if ( pItem->HasSpawnFlags( SF_NORESPAWN ) ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; + } + + + //========================================================= + // At what time in the future may this Item respawn? + //========================================================= + float CMultiplayRules::FlItemRespawnTime( CItem *pItem ) + { + return gpGlobals->curtime + ITEM_RESPAWN_TIME; + } + + //========================================================= + // Where should this item respawn? + // Some game variations may choose to randomize spawn locations + //========================================================= + Vector CMultiplayRules::VecItemRespawnSpot( CItem *pItem ) + { + return pItem->GetAbsOrigin(); + } + + //========================================================= + // What angles should this item use to respawn? + //========================================================= + QAngle CMultiplayRules::VecItemRespawnAngles( CItem *pItem ) + { + return pItem->GetAbsAngles(); + } + + //========================================================= + //========================================================= + void CMultiplayRules::PlayerGotAmmo( CBaseCombatCharacter *pPlayer, char *szName, int iCount ) + { + } + + //========================================================= + //========================================================= + bool CMultiplayRules::IsAllowedToSpawn( CBaseEntity *pEntity ) + { + // if ( pEntity->GetFlags() & FL_NPC ) + // return false; + + return true; + } + + + //========================================================= + //========================================================= + float CMultiplayRules::FlHealthChargerRechargeTime( void ) + { + return 60; + } + + + float CMultiplayRules::FlHEVChargerRechargeTime( void ) + { + return 30; + } + + //========================================================= + //========================================================= + int CMultiplayRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) + { + return GR_PLR_DROP_GUN_ACTIVE; + } + + //========================================================= + //========================================================= + int CMultiplayRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) + { + return GR_PLR_DROP_AMMO_ACTIVE; + } + + CBaseEntity *CMultiplayRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) + { + CBaseEntity *pentSpawnSpot = BaseClass::GetPlayerSpawnSpot( pPlayer ); + + //!! replace this with an Event + /* + if ( IsMultiplayer() && pentSpawnSpot->m_target ) + { + FireTargets( STRING(pentSpawnSpot->m_target), pPlayer, pPlayer, USE_TOGGLE, 0 ); // dvsents2: what is this code supposed to do? + } + */ + + return pentSpawnSpot; + } + + + //========================================================= + //========================================================= + bool CMultiplayRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker ) + { + return ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE ); + } + + int CMultiplayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) + { + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; + } + + bool CMultiplayRules::PlayFootstepSounds( CBasePlayer *pl ) + { + if ( footsteps.GetInt() == 0 ) + return false; + + if ( pl->IsOnLadder() || pl->GetAbsVelocity().Length2D() > 220 ) + return true; // only make step sounds in multiplayer if the player is moving fast enough + + return false; + } + + bool CMultiplayRules::FAllowFlashlight( void ) + { + return flashlight.GetInt() != 0; + } + + //========================================================= + //========================================================= + bool CMultiplayRules::FAllowNPCs( void ) + { + return true; // E3 hack + return ( allowNPCs.GetInt() != 0 ); + } + + //========================================================= + //======== CMultiplayRules private functions =========== + + void CMultiplayRules::GoToIntermission( void ) + { + if ( g_fGameOver ) + return; + + g_fGameOver = true; + + float flWaitTime = mp_chattime.GetInt(); + + if ( tv_delaymapchange.GetBool() ) + { + if ( HLTVDirector()->IsActive() ) + flWaitTime = MAX( flWaitTime, HLTVDirector()->GetDelay() ); + } + + m_flIntermissionEndTime = gpGlobals->curtime + flWaitTime; + + for ( int i = 1; i <= MAX_PLAYERS; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); + } + } + + void StripChar(char *szBuffer, const char cWhiteSpace ) + { + + while ( char *pSpace = strchr( szBuffer, cWhiteSpace ) ) + { + char *pNextChar = pSpace + sizeof(char); + V_strcpy( pSpace, pNextChar ); + } + } + + void CMultiplayRules::GetNextLevelName( char *pszNextMap, int bufsize, bool bRandom /* = false */ ) + { + char mapcfile[256]; + DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false ); + + // Check the time of the mapcycle file and re-populate the list of level names if the file has been modified + const int nMapCycleTimeStamp = filesystem->GetPathTime( mapcfile, "GAME" ); + + if ( 0 == nMapCycleTimeStamp ) + { + // Map cycle file does not exist, make a list containing only the current map + char *szCurrentMapName = new char[MAX_MAP_NAME]; + Q_strncpy( szCurrentMapName, STRING(gpGlobals->mapname), MAX_MAP_NAME ); + m_MapList.AddToTail( szCurrentMapName ); + } + else + { + // If map cycle file has changed or this is the first time through ... + if ( m_nMapCycleTimeStamp != nMapCycleTimeStamp ) + { + // Reset map index and map cycle timestamp + m_nMapCycleTimeStamp = nMapCycleTimeStamp; + m_nMapCycleindex = 0; + + LoadMapCycleFile(); + } + } + + // If somehow we have no maps in the list then add the current one + if ( 0 == m_MapList.Count() ) + { + char *szDefaultMapName = new char[MAX_MAP_NAME]; + Q_strncpy( szDefaultMapName, STRING(gpGlobals->mapname), MAX_MAP_NAME ); + m_MapList.AddToTail( szDefaultMapName ); + } + + if ( bRandom ) + { + m_nMapCycleindex = RandomInt( 0, m_MapList.Count() - 1 ); + } + + // Here's the return value + Q_strncpy( pszNextMap, m_MapList[m_nMapCycleindex], bufsize); + } + + void CMultiplayRules::DetermineMapCycleFilename( char *pszResult, int nSizeResult, bool bForceSpew ) + { + static char szLastResult[ 256]; + + const char *pszVar = mapcyclefile.GetString(); + if ( *pszVar == '\0' ) + { + if ( bForceSpew || V_stricmp( szLastResult, "__novar") ) + { + Msg( "mapcyclefile convar not set.\n" ); + V_strcpy_safe( szLastResult, "__novar" ); + } + *pszResult = '\0'; + return; + } + + char szRecommendedName[ 256 ]; + V_sprintf_safe( szRecommendedName, "cfg/%s", pszVar ); + + // First, look for a mapcycle file in the cfg directory, which is preferred + V_strncpy( pszResult, szRecommendedName, nSizeResult ); + if ( filesystem->FileExists( pszResult, "GAME" ) ) + { + if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) + { + Msg( "Using map cycle file '%s'.\n", pszResult ); + V_strcpy_safe( szLastResult, pszResult ); + } + return; + } + + // Nope? Try the root. + V_strncpy( pszResult, pszVar, nSizeResult ); + if ( filesystem->FileExists( pszResult, "GAME" ) ) + { + if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) + { + Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); + V_strcpy_safe( szLastResult, pszResult ); + } + return; + } + + // Nope? Use the default. + if ( !V_stricmp( pszVar, "mapcycle.txt" ) ) + { + V_strncpy( pszResult, "cfg/mapcycle_default.txt", nSizeResult ); + if ( filesystem->FileExists( pszResult, "GAME" ) ) + { + if ( bForceSpew || V_stricmp( szLastResult, pszResult) ) + { + Msg( "Using map cycle file '%s'. ('%s' was not found.)\n", pszResult, szRecommendedName ); + V_strcpy_safe( szLastResult, pszResult ); + } + return; + } + } + + // Failed + *pszResult = '\0'; + if ( bForceSpew || V_stricmp( szLastResult, "__notfound") ) + { + Msg( "Map cycle file '%s' was not found.\n", szRecommendedName ); + V_strcpy_safe( szLastResult, "__notfound" ); + } + } + + void CMultiplayRules::LoapMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector &mapList ) + { + CUtlBuffer buf; + if ( !filesystem->ReadFile( pszMapCycleFile, "GAME", buf ) ) + return; + buf.PutChar( 0 ); + V_SplitString( (char*)buf.Base(), "\n", mapList ); + + for ( int i = 0; i < mapList.Count(); i++ ) + { + bool bIgnore = false; + + // Strip out the spaces in the name + StripChar( mapList[i] , '\r'); + StripChar( mapList[i] , ' '); + + if ( !Q_strncmp( mapList[i], "//", 2 ) || mapList[i][0] == '\0' ) + { + bIgnore = true; + } + else if ( !engine->IsMapValid( mapList[i] ) ) + { + bIgnore = true; + + // If the engine doesn't consider it a valid map remove it from the lists + Warning( "Invalid map '%s' included in map cycle file. Ignored.\n", mapList[i] ); + } + + if ( bIgnore ) + { + delete [] mapList[i]; + mapList.Remove( i ); + --i; + } + } + } + + void CMultiplayRules::FreeMapCycleFileVector( CUtlVector &mapList ) + { + // Clear out existing map list. Not using Purge() or PurgeAndDeleteAll() because they won't delete [] each element. + for ( int i = 0; i < mapList.Count(); i++ ) + { + delete [] mapList[i]; + } + + mapList.RemoveAll(); + } + + bool CMultiplayRules::IsMapInMapCycle( const char *pszName ) + { + for ( int i = 0; i < m_MapList.Count(); i++ ) + { + if ( V_stricmp( pszName, m_MapList[i] ) == 0 ) + { + return true; + } + } + + return false; + } + + void CMultiplayRules::ChangeLevel( void ) + { + char szNextMap[MAX_MAP_NAME]; + + if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) + { + Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) ); + } + else + { + GetNextLevelName( szNextMap, sizeof(szNextMap) ); + IncrementMapCycleIndex(); + } + + ChangeLevelToMap( szNextMap ); + } + + void CMultiplayRules::LoadMapCycleFile( void ) + { + char mapcfile[256]; + DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false ); + + FreeMapCycleFileVector( m_MapList ); + + // Repopulate map list from mapcycle file + LoapMapCycleFileIntoVector( mapcfile, m_MapList ); + + // Load server's mapcycle into network string table for client-side voting + if ( g_pStringTableServerMapCycle ) + { + CUtlString sFileList; + for ( int i = 0; i < m_MapList.Count(); i++ ) + { + sFileList += m_MapList[i]; + sFileList += '\n'; + } + + g_pStringTableServerMapCycle->AddString( CBaseEntity::IsServer(), "ServerMapCycle", sFileList.Length() + 1, sFileList.String() ); + } + +#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL ) + if ( g_pStringTableServerPopFiles ) + { + // Search for all pop files that are prefixed with the current map name + CUtlString sFileList; + + char szBaseName[_MAX_PATH]; + V_snprintf( szBaseName, sizeof( szBaseName ), "scripts/population/%s*.pop", STRING(gpGlobals->mapname) ); + + FileFindHandle_t popHandle; + const char *pPopFileName = filesystem->FindFirst( szBaseName, &popHandle ); + + while ( pPopFileName && pPopFileName[ 0 ] != '\0' ) + { + // Skip it if it's a directory or is the folder info + if ( filesystem->FindIsDirectory( popHandle ) ) + { + pPopFileName = filesystem->FindNext( popHandle ); + continue; + } + + const char *pchPopPostfix = StringAfterPrefix( pPopFileName, STRING(gpGlobals->mapname) ); + if ( pchPopPostfix ) + { + char szShortName[_MAX_PATH]; + V_strncpy( szShortName, ( ( pchPopPostfix[ 0 ] == '_' ) ? ( pchPopPostfix + 1 ) : "normal" ), sizeof( szShortName ) ); // skip the '_' + V_StripExtension( szShortName, szShortName, sizeof( szShortName ) ); + + sFileList += szShortName; + sFileList += '\n'; + } + + pPopFileName = filesystem->FindNext( popHandle ); + } + + filesystem->FindClose( popHandle ); + + if ( sFileList.Length() > 0 ) + { + g_pStringTableServerPopFiles->AddString( CBaseEntity::IsServer(), "ServerPopFiles", sFileList.Length() + 1, sFileList.String() ); + } + } + + if ( g_pStringTableServerMapCycleMvM ) + { + ConVarRef tf_mvm_missioncyclefile( "tf_mvm_missioncyclefile" ); + KeyValues *pKV = new KeyValues( tf_mvm_missioncyclefile.GetString() ); + if ( pKV->LoadFromFile( g_pFullFileSystem, tf_mvm_missioncyclefile.GetString(), "MOD" ) ) + { + CUtlVector mapList; + + // Parse the maps and send a list to each client for vote options + int iMaxCat = pKV->GetInt( "categories", 0 ); + for ( int iCat = 1; iCat <= iMaxCat; iCat++ ) + { + KeyValues *pCategory = pKV->FindKey( UTIL_VarArgs( "%d", iCat ), false ); + if ( pCategory ) + { + int iMapCount = pCategory->GetInt( "count", 0 ); + for ( int iMap = 1; iMap <= iMapCount; ++iMap ) + { + KeyValues *pMission = pCategory->FindKey( UTIL_VarArgs( "%d", iMap ), false ); + if ( pMission ) + { + const char *pszMap = pMission->GetString( "map", "" ); + int iIdx = mapList.Find( pszMap ); + if ( !mapList.IsValidIndex( iIdx ) ) + { + mapList.AddToTail( pszMap ); + } + } + } + } + } + + if ( mapList.Count() ) + { + CUtlString sFileList; + for ( int i = 0; i < mapList.Count(); i++ ) + { + sFileList += mapList[i]; + sFileList += '\n'; + } + + g_pStringTableServerMapCycleMvM->AddString( CBaseEntity::IsServer(), "ServerMapCycleMvM", sFileList.Length() + 1, sFileList.String() ); + } + + pKV->deleteThis(); + } + } +#endif + + // If the current map selection is in the list, set m_nMapCycleindex to the map that follows it. + for ( int i = 0; i < m_MapList.Count(); i++ ) + { + if ( V_strcmp( STRING( gpGlobals->mapname ), m_MapList[i] ) == 0 ) + { + m_nMapCycleindex = i; + IncrementMapCycleIndex(); + break; + } + } + } + + void CMultiplayRules::ChangeLevelToMap( const char *pszMap ) + { + g_fGameOver = true; + m_flTimeLastMapChangeOrPlayerWasConnected = 0.0f; + Msg( "CHANGE LEVEL: %s\n", pszMap ); + engine->ChangeLevel( pszMap, NULL ); + } + + +#endif + + + //----------------------------------------------------------------------------- + // Purpose: Shared script resource of voice menu commands and hud strings + //----------------------------------------------------------------------------- + void CMultiplayRules::LoadVoiceCommandScript( void ) + { + KeyValues *pKV = new KeyValues( "VoiceCommands" ); + + if ( pKV->LoadFromFile( filesystem, "scripts/voicecommands.txt", "GAME" ) ) + { + for ( KeyValues *menu = pKV->GetFirstSubKey(); menu != NULL; menu = menu->GetNextKey() ) + { + int iMenuIndex = m_VoiceCommandMenus.AddToTail(); + + int iNumItems = 0; + + // for each subkey of this menu, add a menu item + for ( KeyValues *menuitem = menu->GetFirstSubKey(); menuitem != NULL; menuitem = menuitem->GetNextKey() ) + { + iNumItems++; + + if ( iNumItems > 9 ) + { + Warning( "Trying to load more than 9 menu items in voicecommands.txt, extras ignored" ); + continue; + } + + VoiceCommandMenuItem_t item; + +#ifndef CLIENT_DLL + int iConcept = GetMPConceptIndexFromString( menuitem->GetString( "concept", "" ) ); + if ( iConcept == MP_CONCEPT_NONE ) + { + Warning( "Voicecommand script attempting to use unknown concept. Need to define new concepts in code. ( %s )\n", menuitem->GetString( "concept", "" ) ); + } + item.m_iConcept = iConcept; + + item.m_bShowSubtitle = ( menuitem->GetInt( "show_subtitle", 0 ) > 0 ); + item.m_bDistanceBasedSubtitle = ( menuitem->GetInt( "distance_check_subtitle", 0 ) > 0 ); + + Q_strncpy( item.m_szGestureActivity, menuitem->GetString( "activity", "" ), sizeof( item.m_szGestureActivity ) ); +#else + Q_strncpy( item.m_szSubtitle, menuitem->GetString( "subtitle", "" ), MAX_VOICE_COMMAND_SUBTITLE ); + Q_strncpy( item.m_szMenuLabel, menuitem->GetString( "menu_label", "" ), MAX_VOICE_COMMAND_SUBTITLE ); + +#endif + m_VoiceCommandMenus.Element( iMenuIndex ).AddToTail( item ); + } + } + } + + pKV->deleteThis(); + } + +#ifndef CLIENT_DLL + + void CMultiplayRules::SkipNextMapInCycle() + { + char szSkippedMap[MAX_MAP_NAME]; + char szNextMap[MAX_MAP_NAME]; + + GetNextLevelName( szSkippedMap, sizeof( szSkippedMap ) ); + IncrementMapCycleIndex(); + GetNextLevelName( szNextMap, sizeof( szNextMap ) ); + + Msg( "Skipping: %s\tNext map: %s\n", szSkippedMap, szNextMap ); + + if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) ) + { + Msg( "Warning! \"nextlevel\" is set to \"%s\" and will override the next map to be played.\n", nextlevel.GetString() ); + } + } + + void CMultiplayRules::IncrementMapCycleIndex() + { + // Reset index if we've passed the end of the map list + if ( ++m_nMapCycleindex >= m_MapList.Count() ) + { + m_nMapCycleindex = 0; + } + } + + bool CMultiplayRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) + { + CBasePlayer *pPlayer = ToBasePlayer( pEdict ); + + const char *pcmd = args[0]; + if ( FStrEq( pcmd, "voicemenu" ) ) + { + if ( args.ArgC() < 3 ) + return true; + + CBaseMultiplayerPlayer *pMultiPlayerPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( pPlayer ); + + if ( pMultiPlayerPlayer ) + { + int iMenu = atoi( args[1] ); + int iItem = atoi( args[2] ); + + VoiceCommand( pMultiPlayerPlayer, iMenu, iItem ); + } + + return true; + } + + return BaseClass::ClientCommand( pEdict, args ); + } + + void CMultiplayRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) + { + CBaseMultiplayerPlayer *pPlayer = dynamic_cast< CBaseMultiplayerPlayer * >( CBaseEntity::Instance( pEntity ) ); + + if ( !pPlayer ) + return; + + char const *pszCommand = pKeyValues->GetName(); + if ( pszCommand && pszCommand[0] ) + { + if ( FStrEq( pszCommand, "AchievementEarned" ) ) + { + if ( pPlayer->ShouldAnnounceAchievement() ) + { + int nAchievementID = pKeyValues->GetInt( "achievementID" ); + + IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned" ); + if ( event ) + { + event->SetInt( "player", pPlayer->entindex() ); + event->SetInt( "achievement", nAchievementID ); + gameeventmanager->FireEvent( event ); + } + + pPlayer->OnAchievementEarned( nAchievementID ); + } + } + else if ( FStrEq( pszCommand, "SendServerMapCycle" ) ) + { + LoadMapCycleFile(); + } + } + } + + VoiceCommandMenuItem_t *CMultiplayRules::VoiceCommand( CBaseMultiplayerPlayer *pPlayer, int iMenu, int iItem ) + { + // have the player speak the concept that is in a particular menu slot + if ( !pPlayer ) + return NULL; + + if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) + return NULL; + + if ( iItem < 0 || iItem >= m_VoiceCommandMenus.Element( iMenu ).Count() ) + return NULL; + + VoiceCommandMenuItem_t *pItem = &m_VoiceCommandMenus.Element( iMenu ).Element( iItem ); + + Assert( pItem ); + + char szResponse[AI_Response::MAX_RESPONSE_NAME]; + + if ( pPlayer->CanSpeakVoiceCommand() ) + { + CMultiplayer_Expresser *pExpresser = pPlayer->GetMultiplayerExpresser(); + Assert( pExpresser ); + pExpresser->AllowMultipleScenes(); + + if ( pPlayer->SpeakConceptIfAllowed( pItem->m_iConcept, NULL, szResponse, AI_Response::MAX_RESPONSE_NAME ) ) + { + // show a subtitle if we need to + if ( pItem->m_bShowSubtitle ) + { + CRecipientFilter filter; + + if ( pItem->m_bDistanceBasedSubtitle ) + { + filter.AddRecipientsByPAS( pPlayer->WorldSpaceCenter() ); + + // further reduce the range to a certain radius + int i; + for ( i = filter.GetRecipientCount()-1; i >= 0; i-- ) + { + int index = filter.GetRecipientIndex(i); + + CBasePlayer *pListener = UTIL_PlayerByIndex( index ); + + if ( pListener && pListener != pPlayer ) + { + float flDist = ( pListener->WorldSpaceCenter() - pPlayer->WorldSpaceCenter() ).Length2D(); + + if ( flDist > VOICE_COMMAND_MAX_SUBTITLE_DIST ) + filter.RemoveRecipientByPlayerIndex( index ); + } + } + } + else + { + filter.AddAllPlayers(); + } + + // if we aren't a disguised spy + if ( !pPlayer->ShouldShowVoiceSubtitleToEnemy() ) + { + // remove players on other teams + filter.RemoveRecipientsNotOnTeam( pPlayer->GetTeam() ); + } + + // Register this event in the mod-specific usermessages .cpp file if you hit this assert + Assert( usermessages->LookupUserMessage( "VoiceSubtitle" ) != -1 ); + + // Send a subtitle to anyone in the PAS + UserMessageBegin( filter, "VoiceSubtitle" ); + WRITE_BYTE( pPlayer->entindex() ); + WRITE_BYTE( iMenu ); + WRITE_BYTE( iItem ); + MessageEnd(); + } + + pPlayer->NoteSpokeVoiceCommand( szResponse ); + +#ifdef NEXT_BOT + // let bots react to player's voice commands + CUtlVector< INextBot * > botVector; + TheNextBots().CollectAllBots( &botVector ); + + for( int i=0; iOnActorEmoted( pPlayer, pItem->m_iConcept ); + } +#endif + } + else + { + pItem = NULL; + } + + pExpresser->DisallowMultipleScenes(); + return pItem; + } + + return NULL; + } + + bool CMultiplayRules::IsLoadingBugBaitReport() + { + return ( !engine->IsDedicatedServer()&& CommandLine()->CheckParm( "-bugbait" ) && sv_cheats->GetBool() ); + } + + void CMultiplayRules::HaveAllPlayersSpeakConceptIfAllowed( int iConcept, int iTeam /* = TEAM_UNASSIGNED */, const char *modifiers /* = NULL */ ) + { + CBaseMultiplayerPlayer *pPlayer; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) ); + + if ( !pPlayer ) + continue; + + if ( iTeam != TEAM_UNASSIGNED ) + { + if ( pPlayer->GetTeamNumber() != iTeam ) + continue; + } + + pPlayer->SpeakConceptIfAllowed( iConcept, modifiers ); + } + } + + void CMultiplayRules::ClientSettingsChanged( CBasePlayer *pPlayer ) + { + // NVNT see if this user is still or has began using a haptic device + const char *pszHH = engine->GetClientConVarValue( pPlayer->entindex(), "hap_HasDevice" ); + if( pszHH ) + { + int iHH = atoi( pszHH ); + pPlayer->SetHaptics( iHH != 0 ); + } + + } + void CMultiplayRules::GetTaggedConVarList( KeyValues *pCvarTagList ) + { + BaseClass::GetTaggedConVarList( pCvarTagList ); + + // sv_gravity + KeyValues *pGravity = new KeyValues( "sv_gravity" ); + pGravity->SetString( "convar", "sv_gravity" ); + pGravity->SetString( "tag", "gravity" ); + + pCvarTagList->AddSubKey( pGravity ); + + // sv_alltalk + KeyValues *pAllTalk = new KeyValues( "sv_alltalk" ); + pAllTalk->SetString( "convar", "sv_alltalk" ); + pAllTalk->SetString( "tag", "alltalk" ); + + pCvarTagList->AddSubKey( pAllTalk ); + } + +#else + + const char *CMultiplayRules::GetVoiceCommandSubtitle( int iMenu, int iItem ) + { + Assert( iMenu >= 0 && iMenu < m_VoiceCommandMenus.Count() ); + if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) + return ""; + + Assert( iItem >= 0 && iItem < m_VoiceCommandMenus.Element( iMenu ).Count() ); + if ( iItem < 0 || iItem >= m_VoiceCommandMenus.Element( iMenu ).Count() ) + return ""; + + VoiceCommandMenuItem_t *pItem = &m_VoiceCommandMenus.Element( iMenu ).Element( iItem ); + + Assert( pItem ); + + return pItem->m_szSubtitle; + } + + // Returns false if no such menu is declared or if it's an empty menu + bool CMultiplayRules::GetVoiceMenuLabels( int iMenu, KeyValues *pKV ) + { + Assert( iMenu >= 0 && iMenu < m_VoiceCommandMenus.Count() ); + if ( iMenu < 0 || iMenu >= m_VoiceCommandMenus.Count() ) + return false; + + int iNumItems = m_VoiceCommandMenus.Element( iMenu ).Count(); + + for ( int i=0; im_szMenuLabel ); + + pKV->AddSubKey( pLabelKV ); + } + + return iNumItems > 0; + } + +#endif -- cgit v1.2.3