//========= 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 #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 #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( pKiller ); if ( pScorerInterface ) { CBasePlayer *pPlayer = pScorerInterface->GetScorer(); if ( pPlayer ) return pPlayer; } // Inflicting entity might be specifying a scoring player pScorerInterface = dynamic_cast( pInflictor ); if ( pScorerInterface ) { CBasePlayer *pPlayer = pScorerInterface->GetScorer(); if ( pPlayer ) return pPlayer; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns player who should receive credit for kill //----------------------------------------------------------------------------- CBasePlayer *CMultiplayRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor, CBaseEntity *pVictim ) { // if this method not overridden by subclass, just call our default implementation return GetDeathScorer( pKiller, pInflictor ); } //========================================================= // PlayerKilled - someone/something killed this player //========================================================= void CMultiplayRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { DeathNotice( pVictim, info ); // Find the killer & the scorer CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim ); pVictim->IncrementDeathCount( 1 ); // dvsents2: uncomment when removing all FireTargets // variant_t value; // g_EventQueue.AddEvent( "game_playerdie", "Use", value, 0, pVictim, pVictim ); FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); // Did the player kill himself? if ( pVictim == pScorer ) { if ( UseSuicidePenalty() ) { // Players lose a frag for killing themselves pVictim->IncrementFragCount( -1 ); } } else if ( pScorer ) { // if a player dies in a deathmatch game and the killer is a client, award the killer some points pScorer->IncrementFragCount( IPointsForKill( pScorer, pVictim ) ); // Allow the scorer to immediately paint a decal pScorer->AllowImmediateDecalPainting(); // dvsents2: uncomment when removing all FireTargets //variant_t value; //g_EventQueue.AddEvent( "game_playerkill", "Use", value, 0, pScorer, pScorer ); FireTargets( "game_playerkill", pScorer, pScorer, USE_TOGGLE, 0 ); } else { if ( UseSuicidePenalty() ) { // Players lose a frag for letting the world kill them pVictim->IncrementFragCount( -1 ); } } } //========================================================= // Deathnotice. //========================================================= void CMultiplayRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { // Work out what killed the player, and send a message to all clients about it const char *killer_weapon_name = "world"; // by default, the player is killed by the world int killer_ID = 0; // Find the killer & the scorer CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim ); // Custom damage type? if ( info.GetDamageCustom() ) { killer_weapon_name = GetDamageCustomString( info ); if ( pScorer ) { killer_ID = pScorer->GetUserID(); } } else { // Is the killer a client? if ( pScorer ) { killer_ID = pScorer->GetUserID(); if ( pInflictor ) { if ( pInflictor == pScorer ) { // If the inflictor is the killer, then it must be their current weapon doing the damage if ( pScorer->GetActiveWeapon() ) { killer_weapon_name = pScorer->GetActiveWeapon()->GetDeathNoticeName(); } } else { killer_weapon_name = STRING( pInflictor->m_iClassname ); // it's just that easy } } } else { killer_weapon_name = STRING( pInflictor->m_iClassname ); } // strip the NPC_* or weapon_* from the inflictor's classname if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) { killer_weapon_name += 7; } else if ( strncmp( killer_weapon_name, "NPC_", 8 ) == 0 ) { killer_weapon_name += 8; } else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) { killer_weapon_name += 5; } } IGameEvent * event = gameeventmanager->CreateEvent( "player_death" ); if ( event ) { event->SetInt("userid", pVictim->GetUserID() ); event->SetInt("attacker", killer_ID ); event->SetInt("customkill", info.GetDamageCustom() ); event->SetInt("priority", 7 ); // HLTV event priority, not transmitted gameeventmanager->FireEvent( event ); } } //========================================================= // FlWeaponRespawnTime - what is the time in the future // at which this weapon may spawn? //========================================================= float CMultiplayRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) { if ( weaponstay.GetInt() > 0 ) { // make sure it's only certain weapons if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) { return gpGlobals->curtime + 0; // weapon respawns almost instantly } } return gpGlobals->curtime + WEAPON_RESPAWN_TIME; } // when we are within this close to running out of entities, items // marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn #define ENTITY_INTOLERANCE 100 //========================================================= // FlWeaponRespawnTime - Returns 0 if the weapon can respawn // now, otherwise it returns the time at which it can try // to spawn again. //========================================================= float CMultiplayRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) { if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) { if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) return 0; // we're past the entity tolerance level, so delay the respawn return FlWeaponRespawnTime( pWeapon ); } return 0; } //========================================================= // VecWeaponRespawnSpot - where should this weapon spawn? // Some game variations may choose to randomize spawn locations //========================================================= Vector CMultiplayRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) { return pWeapon->GetAbsOrigin(); } //========================================================= // WeaponShouldRespawn - any conditions inhibiting the // respawning of this weapon? //========================================================= int CMultiplayRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) { if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) { return GR_WEAPON_RESPAWN_NO; } return GR_WEAPON_RESPAWN_YES; } //========================================================= // CanHaveWeapon - returns false if the player is not allowed // to pick up this weapon //========================================================= bool CMultiplayRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) { if ( weaponstay.GetInt() > 0 ) { if ( pItem->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD ) return BaseClass::CanHavePlayerItem( pPlayer, pItem ); // check if the player already has this weapon for ( int i = 0 ; i < pPlayer->WeaponCount() ; i++ ) { if ( pPlayer->GetWeapon(i) == pItem ) { return false; } } } return BaseClass::CanHavePlayerItem( pPlayer, pItem ); } //========================================================= //========================================================= bool CMultiplayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) { return true; } //========================================================= //========================================================= void CMultiplayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) { } //========================================================= //========================================================= int CMultiplayRules::ItemShouldRespawn( CItem *pItem ) { if ( pItem->HasSpawnFlags( SF_NORESPAWN ) ) { return GR_ITEM_RESPAWN_NO; } return GR_ITEM_RESPAWN_YES; } //========================================================= // At what time in the future may this Item respawn? //========================================================= float CMultiplayRules::FlItemRespawnTime( CItem *pItem ) { return gpGlobals->curtime + ITEM_RESPAWN_TIME; } //========================================================= // Where should this item respawn? // Some game variations may choose to randomize spawn locations //========================================================= Vector CMultiplayRules::VecItemRespawnSpot( CItem *pItem ) { return pItem->GetAbsOrigin(); } //========================================================= // What angles should this item use to respawn? //========================================================= QAngle CMultiplayRules::VecItemRespawnAngles( CItem *pItem ) { return pItem->GetAbsAngles(); } //========================================================= //========================================================= void CMultiplayRules::PlayerGotAmmo( CBaseCombatCharacter *pPlayer, char *szName, int iCount ) { } //========================================================= //========================================================= bool CMultiplayRules::IsAllowedToSpawn( CBaseEntity *pEntity ) { // if ( pEntity->GetFlags() & FL_NPC ) // return false; return true; } //========================================================= //========================================================= float CMultiplayRules::FlHealthChargerRechargeTime( void ) { return 60; } float CMultiplayRules::FlHEVChargerRechargeTime( void ) { return 30; } //========================================================= //========================================================= int CMultiplayRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) { return GR_PLR_DROP_GUN_ACTIVE; } //========================================================= //========================================================= int CMultiplayRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) { return GR_PLR_DROP_AMMO_ACTIVE; } CBaseEntity *CMultiplayRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) { CBaseEntity *pentSpawnSpot = BaseClass::GetPlayerSpawnSpot( pPlayer ); //!! replace this with an Event /* if ( IsMultiplayer() && pentSpawnSpot->m_target ) { FireTargets( STRING(pentSpawnSpot->m_target), pPlayer, pPlayer, USE_TOGGLE, 0 ); // dvsents2: what is this code supposed to do? } */ return pentSpawnSpot; } //========================================================= //========================================================= bool CMultiplayRules::PlayerCanHearChat( CBasePlayer *pListener, CBasePlayer *pSpeaker ) { return ( PlayerRelationship( pListener, pSpeaker ) == GR_TEAMMATE ); } int CMultiplayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) { // half life deathmatch has only enemies return GR_NOTTEAMMATE; } bool CMultiplayRules::PlayFootstepSounds( CBasePlayer *pl ) { if ( footsteps.GetInt() == 0 ) return false; if ( pl->IsOnLadder() || pl->GetAbsVelocity().Length2D() > 220 ) return true; // only make step sounds in multiplayer if the player is moving fast enough return false; } bool CMultiplayRules::FAllowFlashlight( void ) { return flashlight.GetInt() != 0; } //========================================================= //========================================================= bool CMultiplayRules::FAllowNPCs( void ) { return true; // E3 hack return ( allowNPCs.GetInt() != 0 ); } //========================================================= //======== CMultiplayRules private functions =========== void CMultiplayRules::GoToIntermission( void ) { if ( g_fGameOver ) return; g_fGameOver = true; float flWaitTime = mp_chattime.GetInt(); if ( tv_delaymapchange.GetBool() ) { if ( HLTVDirector()->IsActive() ) flWaitTime = MAX ( flWaitTime, HLTVDirector()->GetDelay() ); #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( 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; im_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(); }