Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1876 lines
55 KiB

5 years ago
//========= 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 <KeyValues.h>
#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 <ctype.h>
#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
// TODO Why did we add this to the base class guys.
#if defined ( TF_DLL ) || defined ( TF_CLIENT_DLL )
#include "player_vs_environment/tf_population_manager.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", FCVAR_NONE, "Delays map change until broadcast is complete" );
ConVar tv_delaymapchange_protect( "tv_delaymapchange_protect", "1", FCVAR_NONE, "Protect against doing a manual map change if HLTV is broadcasting and has not caught up with a major game event such as round_end" );
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<char*> 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[MAX_PATH];
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[MAX_PATH];
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, const CTakeDamageInfo &info )
{
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<IScorer*>( pKiller );
if ( pScorerInterface )
{
CBasePlayer *pPlayer = pScorerInterface->GetScorer();
if ( pPlayer )
return pPlayer;
}
// Inflicting entity might be specifying a scoring player
pScorerInterface = dynamic_cast<IScorer*>( 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() )
{
#ifdef HL1MP_DLL
killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname();
#else
killer_weapon_name = pScorer->GetActiveWeapon()->GetDeathNoticeName();
#endif
}
}
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_", 4 ) == 0 )
{
killer_weapon_name += 4;
}
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
#ifdef HL1MP_DLL
event->SetString("weapon", killer_weapon_name );
#endif
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 );
}
}
// Strip ' ' and '\n' characters from string.
static void StripWhitespaceChars( char *szBuffer )
{
char *szOut = szBuffer;
for ( char *szIn = szOut; *szIn; szIn++ )
{
if ( *szIn != ' ' && *szIn != '\r' )
*szOut++ = *szIn;
}
*szOut = '\0';
}
void CMultiplayRules::GetNextLevelName( char *pszNextMap, int bufsize, bool bRandom /* = false */ )
{
char mapcfile[MAX_PATH];
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 )
{
// Reload
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[ MAX_PATH ];
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[ MAX_PATH ];
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::LoadMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector<char *> &mapList )
{
CMultiplayRules::RawLoadMapCycleFileIntoVector( pszMapCycleFile, mapList );
}
void CMultiplayRules::RawLoadMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector<char *> &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 ' ' and '\r' chars.
StripWhitespaceChars( mapList[i] );
if ( !Q_strncmp( mapList[i], "//", 2 ) || mapList[i][0] == '\0' )
{
bIgnore = true;
}
if ( bIgnore )
{
delete [] mapList[i];
mapList.Remove( i );
--i;
}
}
}
void CMultiplayRules::FreeMapCycleFileVector( CUtlVector<char *> &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::IsManualMapChangeOkay( const char **pszReason )
{
if ( HLTVDirector()->IsActive() && ( HLTVDirector()->GetDelay() >= HLTV_MIN_DIRECTOR_DELAY ) )
{
if ( tv_delaymapchange.GetBool() && tv_delaymapchange_protect.GetBool() )
{
float flLastEvent = GetLastMajorEventTime();
if ( flLastEvent > -1 )
{
if ( flLastEvent > ( gpGlobals->curtime - ( HLTVDirector()->GetDelay() + 3 ) ) ) // +3 second delay to prevent instant change after a major event
{
*pszReason = "\n***WARNING*** Map change blocked. HLTV is broadcasting and has not caught up to the last major game event yet.\nYou can disable this check by setting the value of the server convar \"tv_delaymapchange_protect\" to 0.\n";
return false;
}
}
}
}
return true;
}
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() )
{
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
}
else
{
GetNextLevelName( szNextMap, sizeof(szNextMap) );
IncrementMapCycleIndex();
}
ChangeLevelToMap( szNextMap );
}
void CMultiplayRules::LoadMapCycleFile( void )
{
int nOldCycleIndex = m_nMapCycleindex;
m_nMapCycleindex = 0;
char mapcfile[MAX_PATH];
DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false );
FreeMapCycleFileVector( m_MapList );
const int nMapCycleTimeStamp = filesystem->GetPathTime( mapcfile, "GAME" );
m_nMapCycleTimeStamp = nMapCycleTimeStamp;
// Repopulate map list from mapcycle file
LoadMapCycleFileIntoVector( 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;
CUtlVector< CUtlString > defaultPopFiles;
CPopulationManager::FindDefaultPopulationFileShortNames( defaultPopFiles );
FOR_EACH_VEC( defaultPopFiles, idx )
{
sFileList += defaultPopFiles[ idx ];
sFileList += "\n";
}
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<CUtlString> 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 is in the same location in the new map cycle, keep that index. This gives better behavior
// when reloading a map cycle that has the current map in it multiple times.
int nOldPreviousMap = ( nOldCycleIndex == 0 ) ? ( m_MapList.Count() - 1 ) : ( nOldCycleIndex - 1 );
if ( nOldCycleIndex >= 0 && nOldCycleIndex < m_MapList.Count() &&
nOldPreviousMap >= 0 && nOldPreviousMap < m_MapList.Count() &&
V_strcmp( STRING( gpGlobals->mapname ), m_MapList[ nOldPreviousMap ] ) == 0 )
{
// The old index is still valid, and falls after our current map in the new cycle, use it
m_nMapCycleindex = nOldCycleIndex;
}
else
{
// Otherwise, 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() )
{
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 );
}
}
}
}
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; i<botVector.Count(); ++i )
{
botVector[i]->OnActorEmoted( 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::RandomPlayersSpeakConceptIfAllowed( int iConcept, int iNumRandomPlayer /*= 1*/, int iTeam /*= TEAM_UNASSIGNED*/, const char *modifiers /*= NULL*/ )
{
CUtlVector< CBaseMultiplayerPlayer* > speakCandidates;
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;
}
speakCandidates.AddToTail( pPlayer );
}
int iSpeaker = iNumRandomPlayer;
while ( iSpeaker > 0 && speakCandidates.Count() > 0 )
{
int iRandomSpeaker = RandomInt( 0, speakCandidates.Count() - 1 );
speakCandidates[ iRandomSpeaker ]->SpeakConceptIfAllowed( iConcept, modifiers );
speakCandidates.FastRemove( iRandomSpeaker );
iSpeaker--;
}
}
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; i<iNumItems; i++ )
{
VoiceCommandMenuItem_t *pItem = &m_VoiceCommandMenus.Element( iMenu ).Element( i );
KeyValues *pLabelKV = new KeyValues( pItem->m_szMenuLabel );
pKV->AddSubKey( pLabelKV );
}
return iNumItems > 0;
}
#endif