source-engine/game/shared/multiplay_gamerules.cpp

1859 lines
54 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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
#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
2022-03-01 20:00:42 +00:00
ConVar tv_delaymapchange( "tv_delaymapchange", "0", 0, "Delays map change until broadcast is complete" );
2020-04-22 16:56:21 +00:00
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] )
{
2022-03-01 20:00:42 +00:00
char szCommand[256];
2020-04-22 16:56:21 +00:00
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] )
{
2022-03-01 20:00:42 +00:00
char szCommand[256];
2020-04-22 16:56:21 +00:00
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 );
}
}
2022-03-01 20:00:42 +00:00
void StripChar(char *szBuffer, const char cWhiteSpace )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
while ( char *pSpace = strchr( szBuffer, cWhiteSpace ) )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
char *pNextChar = pSpace + sizeof(char);
V_strcpy( pSpace, pNextChar );
2020-04-22 16:56:21 +00:00
}
}
void CMultiplayRules::GetNextLevelName( char *pszNextMap, int bufsize, bool bRandom /* = false */ )
{
2022-03-01 20:00:42 +00:00
char mapcfile[256];
2020-04-22 16:56:21 +00:00
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 )
{
2022-03-01 20:00:42 +00:00
// Reset map index and map cycle timestamp
m_nMapCycleTimeStamp = nMapCycleTimeStamp;
m_nMapCycleindex = 0;
2020-04-22 16:56:21 +00:00
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 )
{
2022-03-01 20:00:42 +00:00
static char szLastResult[ 256];
2020-04-22 16:56:21 +00:00
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;
}
2022-03-01 20:00:42 +00:00
char szRecommendedName[ 256 ];
2020-04-22 16:56:21 +00:00
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" );
}
}
2022-03-01 20:00:42 +00:00
void CMultiplayRules::LoapMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector<char *> &mapList )
2020-04-22 16:56:21 +00:00
{
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;
2022-03-01 20:00:42 +00:00
// Strip out the spaces in the name
StripChar( mapList[i] , '\r');
StripChar( mapList[i] , ' ');
2020-04-22 16:56:21 +00:00
if ( !Q_strncmp( mapList[i], "//", 2 ) || mapList[i][0] == '\0' )
{
bIgnore = true;
}
2022-03-01 20:00:42 +00:00
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] );
}
2020-04-22 16:56:21 +00:00
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::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];
2022-03-01 20:00:42 +00:00
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
2020-04-22 16:56:21 +00:00
{
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
}
else
{
GetNextLevelName( szNextMap, sizeof(szNextMap) );
IncrementMapCycleIndex();
}
ChangeLevelToMap( szNextMap );
}
void CMultiplayRules::LoadMapCycleFile( void )
{
2022-03-01 20:00:42 +00:00
char mapcfile[256];
2020-04-22 16:56:21 +00:00
DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), false );
FreeMapCycleFileVector( m_MapList );
// Repopulate map list from mapcycle file
2022-03-01 20:00:42 +00:00
LoapMapCycleFileIntoVector( mapcfile, m_MapList );
2020-04-22 16:56:21 +00:00
// 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;
2022-03-01 20:00:42 +00:00
char szBaseName[_MAX_PATH];
V_snprintf( szBaseName, sizeof( szBaseName ), "scripts/population/%s*.pop", STRING(gpGlobals->mapname) );
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
FileFindHandle_t popHandle;
const char *pPopFileName = filesystem->FindFirst( szBaseName, &popHandle );
while ( pPopFileName && pPopFileName[ 0 ] != '\0' )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
// 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 );
2020-04-22 16:56:21 +00:00
}
2022-03-01 20:00:42 +00:00
filesystem->FindClose( popHandle );
2020-04-22 16:56:21 +00:00
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
2022-03-01 20:00:42 +00:00
// 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++ )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
if ( V_strcmp( STRING( gpGlobals->mapname ), m_MapList[i] ) == 0 )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
m_nMapCycleindex = i;
IncrementMapCycleIndex();
break;
2020-04-22 16:56:21 +00:00
}
2022-03-01 20:00:42 +00:00
}
2020-04-22 16:56:21 +00:00
}
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 );
2022-03-01 20:00:42 +00:00
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
2020-04-22 16:56:21 +00:00
{
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 );
}
}
2022-03-01 20:00:42 +00:00
else if ( FStrEq( pszCommand, "SendServerMapCycle" ) )
{
LoadMapCycleFile();
}
2020-04-22 16:56:21 +00:00
}
}
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