mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-22 22:27:05 +00:00
1486 lines
43 KiB
C++
1486 lines
43 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Contains the implementation of game rules for multiplayer.
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "multiplay_gamerules.h"
|
|
#include "viewport_panel_names.h"
|
|
#include "gameeventdefs.h"
|
|
#include <KeyValues.h>
|
|
#include "filesystem.h"
|
|
#include "mp_shareddefs.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"
|
|
#if defined( REPLAY_ENABLED )
|
|
#include "replaydirector.h"
|
|
#endif
|
|
#include "AI_Criteria.h"
|
|
#include "sceneentity.h"
|
|
#include "team.h"
|
|
#include "usermessages.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "basemultiplayerplayer.h"
|
|
#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
|
|
);
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
ConVar tv_delaymapchange( "tv_delaymapchange", "0", 0, "Delays map change until broadcast is complete" );
|
|
|
|
ConVar mp_restartgame( "mp_restartgame", "0", FCVAR_GAMEDLL, "If non-zero, game will restart in the specified number of seconds" );
|
|
|
|
|
|
ConVar mp_waitingforplayers_time( "mp_waitingforplayers_time", "0", FCVAR_GAMEDLL, "WaitingForPlayers time length in seconds" );
|
|
|
|
|
|
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 set to a valid map name, will change to this map during the next changelevel" );
|
|
|
|
|
|
#endif
|
|
|
|
#ifndef CLIENT_DLL
|
|
int CMultiplayRules::m_nMapCycleTimeStamp = 0;
|
|
int CMultiplayRules::m_nMapCycleindex = 0;
|
|
CUtlStringList 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
|
|
RefreshSkillData( true );
|
|
|
|
// 11/8/98
|
|
// Modified by YWB: Server .cfg file is now a cvar, so that
|
|
// server ops can run multiple game servers, with different server .cfg files,
|
|
// from a single installed directory.
|
|
// Mapcyclefile is already a cvar.
|
|
|
|
// 3/31/99
|
|
// Added lservercfg file cvar, since listen and dedicated servers should not
|
|
// share a single config file. (sjb)
|
|
if ( engine->IsDedicatedServer() )
|
|
{
|
|
// dedicated server
|
|
const char *cfgfile = servercfgfile.GetString();
|
|
|
|
if ( cfgfile && cfgfile[0] )
|
|
{
|
|
char szCommand[256];
|
|
|
|
Msg( "Executing dedicated server config file\n" );
|
|
Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile );
|
|
engine->ServerCommand( szCommand );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// listen server
|
|
const char *cfgfile = lservercfgfile.GetString();
|
|
|
|
if ( cfgfile && cfgfile[0] )
|
|
{
|
|
char szCommand[256];
|
|
|
|
Msg( "Executing listen server config file\n" );
|
|
Q_snprintf( szCommand,sizeof(szCommand), "exec %s\n", cfgfile );
|
|
engine->ServerCommand( szCommand );
|
|
}
|
|
}
|
|
|
|
nextlevel.SetValue( "" );
|
|
#endif
|
|
|
|
LoadVoiceCommandScript();
|
|
}
|
|
|
|
|
|
#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
|
|
|
|
ConVarRef suitcharger( "sk_suitcharger" );
|
|
suitcharger.SetValue( 30 );
|
|
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
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 );
|
|
|
|
/*
|
|
CBasePlayer *pl = ToBasePlayer( GetContainingEntity( pEntity ) );
|
|
if ( pl && ( engine->IsSplitScreenPlayer( pl->entindex() ) )
|
|
{
|
|
Msg( "%s is a split screen player\n", pszName );
|
|
}
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
void CMultiplayRules::InitHUD( CBasePlayer *pl )
|
|
{
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMultiplayRules::ClientDisconnected( edict_t *pClient )
|
|
{
|
|
if ( pClient )
|
|
{
|
|
CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 );
|
|
|
|
pPlayer->RemoveAllItems( true );// destroy all of the players weapons and items
|
|
|
|
// Kill off view model entities
|
|
pPlayer->DestroyViewModels();
|
|
|
|
pPlayer->SetConnected( PlayerDisconnected );
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CMultiplayRules::FlPlayerFallDamage( CBasePlayer *pPlayer )
|
|
{
|
|
int iFallDamage = (int)falldamage.GetFloat();
|
|
|
|
switch ( iFallDamage )
|
|
{
|
|
case 1://progressive
|
|
pPlayer->m_Local.m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED;
|
|
return pPlayer->m_Local.m_flFallVelocity * DAMAGE_FOR_FALL_SPEED;
|
|
break;
|
|
default:
|
|
case 0:// fixed
|
|
return 10;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMultiplayRules::PlayerThink( CBasePlayer *pPlayer )
|
|
{
|
|
if ( g_fGameOver )
|
|
{
|
|
// clear attack/use commands from player
|
|
pPlayer->m_afButtonPressed = 0;
|
|
pPlayer->m_nButtons = 0;
|
|
pPlayer->m_afButtonReleased = 0;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMultiplayRules::PlayerSpawn( CBasePlayer *pPlayer )
|
|
{
|
|
bool addDefault;
|
|
CBaseEntity *pWeaponEntity = NULL;
|
|
|
|
pPlayer->EquipSuit();
|
|
|
|
addDefault = true;
|
|
|
|
while ( (pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL)
|
|
{
|
|
pWeaponEntity->Touch( pPlayer );
|
|
addDefault = false;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::FPlayerCanRespawn( CBasePlayer *pPlayer )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CMultiplayRules::FlPlayerSpawnTime( CBasePlayer *pPlayer )
|
|
{
|
|
return gpGlobals->curtime;//now!
|
|
}
|
|
|
|
bool CMultiplayRules::AllowAutoTargetCrosshair( void )
|
|
{
|
|
return ( aimcrosshair.GetInt() != 0 );
|
|
}
|
|
|
|
//=========================================================
|
|
// IPointsForKill - how many points awarded to anyone
|
|
// that kills this player?
|
|
//=========================================================
|
|
int CMultiplayRules::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor )
|
|
{
|
|
if ( pKiller)
|
|
{
|
|
if ( pKiller->Classify() == CLASS_PLAYER )
|
|
return (CBasePlayer*)pKiller;
|
|
|
|
// Killing entity might be specifying a scorer player
|
|
IScorer *pScorerInterface = dynamic_cast<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() )
|
|
{
|
|
killer_weapon_name = pScorer->GetActiveWeapon()->GetDeathNoticeName();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
killer_weapon_name = STRING( pInflictor->m_iClassname );
|
|
}
|
|
|
|
// strip the NPC_* or weapon_* from the inflictor's classname
|
|
if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 )
|
|
{
|
|
killer_weapon_name += 7;
|
|
}
|
|
else if ( strncmp( killer_weapon_name, "NPC_", 8 ) == 0 )
|
|
{
|
|
killer_weapon_name += 8;
|
|
}
|
|
else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 )
|
|
{
|
|
killer_weapon_name += 5;
|
|
}
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_death" );
|
|
if ( event )
|
|
{
|
|
event->SetInt("userid", pVictim->GetUserID() );
|
|
event->SetInt("attacker", killer_ID );
|
|
event->SetInt("customkill", info.GetDamageCustom() );
|
|
event->SetInt("priority", 7 ); // HLTV event priority, not transmitted
|
|
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
}
|
|
|
|
//=========================================================
|
|
// FlWeaponRespawnTime - what is the time in the future
|
|
// at which this weapon may spawn?
|
|
//=========================================================
|
|
float CMultiplayRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon )
|
|
{
|
|
if ( weaponstay.GetInt() > 0 )
|
|
{
|
|
// make sure it's only certain weapons
|
|
if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) )
|
|
{
|
|
return gpGlobals->curtime + 0; // weapon respawns almost instantly
|
|
}
|
|
}
|
|
|
|
return gpGlobals->curtime + WEAPON_RESPAWN_TIME;
|
|
}
|
|
|
|
// when we are within this close to running out of entities, items
|
|
// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn
|
|
#define ENTITY_INTOLERANCE 100
|
|
|
|
//=========================================================
|
|
// FlWeaponRespawnTime - Returns 0 if the weapon can respawn
|
|
// now, otherwise it returns the time at which it can try
|
|
// to spawn again.
|
|
//=========================================================
|
|
float CMultiplayRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon )
|
|
{
|
|
if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) )
|
|
{
|
|
if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) )
|
|
return 0;
|
|
|
|
// we're past the entity tolerance level, so delay the respawn
|
|
return FlWeaponRespawnTime( pWeapon );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//=========================================================
|
|
// VecWeaponRespawnSpot - where should this weapon spawn?
|
|
// Some game variations may choose to randomize spawn locations
|
|
//=========================================================
|
|
Vector CMultiplayRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon )
|
|
{
|
|
return pWeapon->GetAbsOrigin();
|
|
}
|
|
|
|
//=========================================================
|
|
// WeaponShouldRespawn - any conditions inhibiting the
|
|
// respawning of this weapon?
|
|
//=========================================================
|
|
int CMultiplayRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon )
|
|
{
|
|
if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) )
|
|
{
|
|
return GR_WEAPON_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_WEAPON_RESPAWN_YES;
|
|
}
|
|
|
|
//=========================================================
|
|
// CanHaveWeapon - returns false if the player is not allowed
|
|
// to pick up this weapon
|
|
//=========================================================
|
|
bool CMultiplayRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem )
|
|
{
|
|
if ( weaponstay.GetInt() > 0 )
|
|
{
|
|
if ( pItem->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD )
|
|
return BaseClass::CanHavePlayerItem( pPlayer, pItem );
|
|
|
|
// check if the player already has this weapon
|
|
for ( int i = 0 ; i < pPlayer->WeaponCount() ; i++ )
|
|
{
|
|
if ( pPlayer->GetWeapon(i) == pItem )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return BaseClass::CanHavePlayerItem( pPlayer, pItem );
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMultiplayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem )
|
|
{
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CMultiplayRules::ItemShouldRespawn( CItem *pItem )
|
|
{
|
|
if ( pItem->HasSpawnFlags( SF_NORESPAWN ) )
|
|
{
|
|
return GR_ITEM_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_ITEM_RESPAWN_YES;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// At what time in the future may this Item respawn?
|
|
//=========================================================
|
|
float CMultiplayRules::FlItemRespawnTime( CItem *pItem )
|
|
{
|
|
return gpGlobals->curtime + ITEM_RESPAWN_TIME;
|
|
}
|
|
|
|
//=========================================================
|
|
// Where should this item respawn?
|
|
// Some game variations may choose to randomize spawn locations
|
|
//=========================================================
|
|
Vector CMultiplayRules::VecItemRespawnSpot( CItem *pItem )
|
|
{
|
|
return pItem->GetAbsOrigin();
|
|
}
|
|
|
|
//=========================================================
|
|
// What angles should this item use to respawn?
|
|
//=========================================================
|
|
QAngle CMultiplayRules::VecItemRespawnAngles( CItem *pItem )
|
|
{
|
|
return pItem->GetAbsAngles();
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMultiplayRules::PlayerGotAmmo( CBaseCombatCharacter *pPlayer, char *szName, int iCount )
|
|
{
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::IsAllowedToSpawn( CBaseEntity *pEntity )
|
|
{
|
|
// if ( pEntity->GetFlags() & FL_NPC )
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CMultiplayRules::FlHealthChargerRechargeTime( void )
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
|
|
float CMultiplayRules::FlHEVChargerRechargeTime( void )
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CMultiplayRules::DeadPlayerWeapons( CBasePlayer *pPlayer )
|
|
{
|
|
return GR_PLR_DROP_GUN_ACTIVE;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CMultiplayRules::DeadPlayerAmmo( CBasePlayer *pPlayer )
|
|
{
|
|
return GR_PLR_DROP_AMMO_ACTIVE;
|
|
}
|
|
|
|
CBaseEntity *CMultiplayRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
|
|
{
|
|
CBaseEntity *pentSpawnSpot = BaseClass::GetPlayerSpawnSpot( pPlayer );
|
|
|
|
//!! replace this with an Event
|
|
/*
|
|
if ( IsMultiplayer() && pentSpawnSpot->m_target )
|
|
{
|
|
FireTargets( STRING(pentSpawnSpot->m_target), pPlayer, pPlayer, USE_TOGGLE, 0 ); // dvsents2: what is this code supposed to do?
|
|
}
|
|
*/
|
|
|
|
return pentSpawnSpot;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker )
|
|
{
|
|
return ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE );
|
|
}
|
|
|
|
int CMultiplayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget )
|
|
{
|
|
// half life deathmatch has only enemies
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
|
|
bool CMultiplayRules::PlayFootstepSounds( CBasePlayer *pl )
|
|
{
|
|
if ( footsteps.GetInt() == 0 )
|
|
return false;
|
|
|
|
if ( pl->IsOnLadder() || pl->GetAbsVelocity().Length2D() > 220 )
|
|
return true; // only make step sounds in multiplayer if the player is moving fast enough
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CMultiplayRules::FAllowFlashlight( void )
|
|
{
|
|
return flashlight.GetInt() != 0;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CMultiplayRules::FAllowNPCs( void )
|
|
{
|
|
return true; // E3 hack
|
|
return ( allowNPCs.GetInt() != 0 );
|
|
}
|
|
|
|
//=========================================================
|
|
//======== CMultiplayRules private functions ===========
|
|
|
|
void CMultiplayRules::GoToIntermission( void )
|
|
{
|
|
if ( g_fGameOver )
|
|
return;
|
|
|
|
g_fGameOver = true;
|
|
|
|
float flWaitTime = mp_chattime.GetInt();
|
|
|
|
if ( tv_delaymapchange.GetBool() )
|
|
{
|
|
if ( HLTVDirector()->IsActive() )
|
|
flWaitTime = MAX ( flWaitTime, HLTVDirector()->GetDelay() );
|
|
#if defined( REPLAY_ENABLED )
|
|
else if ( ReplayDirector()->IsActive() )
|
|
flWaitTime = MAX ( flWaitTime, ReplayDirector()->GetDelay() );
|
|
#endif
|
|
}
|
|
|
|
m_flIntermissionEndTime = gpGlobals->curtime + flWaitTime;
|
|
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD );
|
|
}
|
|
}
|
|
|
|
void StripChar(char *szBuffer, const char cWhiteSpace )
|
|
{
|
|
|
|
while ( char *pSpace = strchr( szBuffer, cWhiteSpace ) )
|
|
{
|
|
char *pNextChar = pSpace + sizeof(char);
|
|
V_strcpy( pSpace, pNextChar );
|
|
}
|
|
}
|
|
|
|
void CMultiplayRules::GetNextLevelName( char *pszNextMap, int bufsize, bool bRandom /* = false */ )
|
|
{
|
|
const char *mapcfile = mapcyclefile.GetString();
|
|
Assert( mapcfile != NULL );
|
|
|
|
// 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[32];
|
|
Q_strncpy( szCurrentMapName, STRING(gpGlobals->mapname), 32 );
|
|
m_MapList.AddToTail( szCurrentMapName );
|
|
}
|
|
else
|
|
{
|
|
// If map cycle file has changed or this is the first time through ...
|
|
if ( m_nMapCycleTimeStamp != nMapCycleTimeStamp )
|
|
{
|
|
// Reset map index and map cycle timestamp
|
|
m_nMapCycleTimeStamp = nMapCycleTimeStamp;
|
|
m_nMapCycleindex = 0;
|
|
|
|
// Clear out existing map list. Not using Purge() because I don't think that it will do a 'delete []'
|
|
for ( int i = 0; i < m_MapList.Count(); i++ )
|
|
{
|
|
delete [] m_MapList[i];
|
|
}
|
|
|
|
m_MapList.RemoveAll();
|
|
|
|
// Repopulate map list from mapcycle file
|
|
int nFileLength;
|
|
char *aFileList = (char*)UTIL_LoadFileForMe( mapcfile, &nFileLength );
|
|
if ( aFileList && nFileLength )
|
|
{
|
|
V_SplitString( aFileList, "\n", m_MapList );
|
|
|
|
for ( int i = 0; i < m_MapList.Count(); i++ )
|
|
{
|
|
bool bIgnore = false;
|
|
|
|
// Strip out the spaces in the name
|
|
StripChar( m_MapList[i] , '\r');
|
|
StripChar( m_MapList[i] , ' ');
|
|
|
|
if ( !engine->IsMapValid( m_MapList[i] ) )
|
|
{
|
|
bIgnore = true;
|
|
|
|
// If the engine doesn't consider it a valid map remove it from the lists
|
|
char szWarningMessage[MAX_PATH];
|
|
V_snprintf( szWarningMessage, MAX_PATH, "Invalid map '%s' included in map cycle file. Ignored.\n", m_MapList[i] );
|
|
Warning( szWarningMessage );
|
|
}
|
|
else if ( !Q_strncmp( m_MapList[i], "//", 2 ) )
|
|
{
|
|
bIgnore = true;
|
|
}
|
|
|
|
if ( bIgnore )
|
|
{
|
|
delete [] m_MapList[i];
|
|
m_MapList.Remove( i );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
UTIL_FreeFile( (byte *)aFileList );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If somehow we have no maps in the list then add the current one
|
|
if ( 0 == m_MapList.Count() )
|
|
{
|
|
char *szDefaultMapName = new char[32];
|
|
Q_strncpy( szDefaultMapName, STRING(gpGlobals->mapname), 32 );
|
|
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::ChangeLevel( void )
|
|
{
|
|
char szNextMap[32];
|
|
|
|
if ( nextlevel.GetString() && *nextlevel.GetString() && engine->IsMapValid( nextlevel.GetString() ) )
|
|
{
|
|
Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
|
|
}
|
|
else
|
|
{
|
|
GetNextLevelName( szNextMap, sizeof(szNextMap) );
|
|
IncrementMapCycleIndex();
|
|
}
|
|
|
|
g_fGameOver = true;
|
|
Msg( "CHANGE LEVEL: %s\n", szNextMap );
|
|
engine->ChangeLevel( szNextMap, 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::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;
|
|
}
|
|
else if ( FStrEq( pcmd, "achievement_earned" ) )
|
|
{
|
|
CBaseMultiplayerPlayer *pPlayer = static_cast<CBaseMultiplayerPlayer*>( pEdict );
|
|
if ( pPlayer && pPlayer->ShouldAnnounceAchievement() )
|
|
{
|
|
// let's check this came from the client .dll and not the console
|
|
unsigned short mask = UTIL_GetAchievementEventMask();
|
|
int iPlayerID = pPlayer->GetUserID();
|
|
|
|
int iAchievement = atoi( args[1] ) ^ mask;
|
|
int code = ( iPlayerID ^ iAchievement ) ^ mask;
|
|
|
|
if ( code == atoi( args[2] ) )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "player", pEdict->entindex() );
|
|
event->SetInt( "achievement", iAchievement );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
pPlayer->OnAchievementEarned( iAchievement );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::ClientCommand( pEdict, args );
|
|
|
|
}
|
|
|
|
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 );
|
|
}
|
|
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 )
|
|
{
|
|
CBaseMultiplayerPlayer *pPlayer;
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
pPlayer->SpeakConceptIfAllowed( iConcept );
|
|
}
|
|
}
|
|
|
|
void CMultiplayRules::GetTaggedConVarList( KeyValues *pCvarTagList )
|
|
{
|
|
BaseClass::GetTaggedConVarList( pCvarTagList );
|
|
|
|
KeyValues *pGravity = new KeyValues( "sv_gravity" );
|
|
pGravity->SetString( "convar", "sv_gravity" );
|
|
pGravity->SetString( "tag", "gravity" );
|
|
|
|
pCvarTagList->AddSubKey( pGravity );
|
|
}
|
|
|
|
#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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sort function for sorting players by time spent connected ( user ID )
|
|
//-----------------------------------------------------------------------------
|
|
bool CSameTeamGroup::Less( const CSameTeamGroup &p1, const CSameTeamGroup &p2 )
|
|
{
|
|
// sort by score
|
|
return ( p1.Score() > p2.Score() );
|
|
}
|
|
|
|
CSameTeamGroup::CSameTeamGroup() :
|
|
m_nScore( INT_MIN )
|
|
{
|
|
}
|
|
|
|
CSameTeamGroup::CSameTeamGroup( const CSameTeamGroup &src )
|
|
{
|
|
m_nScore = src.m_nScore;
|
|
m_Players = src.m_Players;
|
|
}
|
|
|
|
int CSameTeamGroup::Score() const
|
|
{
|
|
return m_nScore;
|
|
}
|
|
|
|
CBasePlayer *CSameTeamGroup::GetPlayer( int idx )
|
|
{
|
|
return m_Players[ idx ];
|
|
}
|
|
|
|
int CSameTeamGroup::Count() const
|
|
{
|
|
return m_Players.Count();
|
|
}
|