//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: The Half-Life 2 game rules, such as the relationship tables and ammo // damage cvars. // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "portal_mp_gamerules.h" #include "viewport_panel_names.h" #include "gameeventdefs.h" #include #include "ammodef.h" #include "tier1/fmtstr.h" #include "hl2_shareddefs.h" #include "portal_shareddefs.h" #include "matchmaking/imatchframework.h" #include "matchmaking/mm_helpers.h" #ifndef CLIENT_DLL #include "player_voice_listener.h" #endif // CLIENT_DLL #ifdef CLIENT_DLL #include "c_portal_player.h" #include "c_user_message_register.h" #include "c_keyvalue_saver.h" #include "radialmenu_taunt.h" #include "c_portal_mp_stats.h" #else #include "eventqueue.h" #include "player.h" #include "gamerules.h" #include "game.h" #include "items.h" #include "entitylist.h" #include "mapentities.h" #include "in_buttons.h" #include #include "voice_gamemgr.h" #include "iscorer.h" #include "portal_player.h" #include "team.h" #include "voice_gamemgr.h" #include "globalstate.h" #include "portal_mp_stats.h" #include "portal_ui_controller.h" #endif // CLIENT_DLL #ifndef CLIENT_DLL extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); ConVar sv_hl2mp_weapon_respawn_time( "sv_hl2mp_weapon_respawn_time", "20", FCVAR_GAMEDLL | FCVAR_NOTIFY ); ConVar sv_hl2mp_item_respawn_time( "sv_hl2mp_item_respawn_time", "30", FCVAR_GAMEDLL | FCVAR_NOTIFY ); ConVar sv_report_client_settings("sv_report_client_settings", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); extern ConVar mp_dev_wait_for_other_player; extern ConVar mp_chattime; #define WEAPON_MAX_DISTANCE_FROM_SPAWN 64 #endif // #ifndef CLIENT_DLL #ifdef CLIENT_DLL extern ConVar locator_lerp_rest; extern ConVar locator_start_at_crosshair; extern ConVar locator_topdown_style; extern ConVar locator_background_style; extern ConVar locator_background_color; extern ConVar locator_background_thickness_x; extern ConVar locator_background_thickness_y; extern ConVar locator_target_offset_x; extern ConVar locator_target_offset_y; extern ConVar locator_background_shift_x; extern ConVar locator_background_shift_y; extern ConVar locator_background_border_color; extern ConVar locator_icon_min_size_non_ss; extern ConVar locator_icon_max_size_non_ss; extern ConVar voice_icons_use_particles; #endif // CLIENT_DLL #define PropStringArrayArrayElement( _sendOrRecv, _structName, _arrayName, _firstElementNum, _secondElementNum, _secondArraySize, _stringSize ) \ _sendOrRecv##PropString( #_arrayName"["#_firstElementNum"]["#_secondElementNum"]", offsetof( _structName, _arrayName ) + _firstElementNum * _secondArraySize * _stringSize + _secondElementNum * _stringSize, _stringSize ) #define PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, _innerNum ) \ PropStringArrayArrayElement( _sendOrRecv, CPortalMPGameRules, _arrayName, _outterNum, _innerNum, MAX_PORTAL2_COOP_LEVELS_PER_BRANCH, MAX_PORTAL2_COOP_LEVEL_NAME_SIZE ) #define PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, _outterNum ) \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 0 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 1 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 2 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 3 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 4 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 5 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 6 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 7 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 8 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 9 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 10 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 11 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 12 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 13 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 14 ), \ PropStringArrayArray( _arrayName, _sendOrRecv, _outterNum, 15 ) #define PropStringArrayArrayOuterList( _arrayName, _sendOrRecv ) \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 0 ), \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 1 ), \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 2 ), \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 3 ), \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 4 ), \ PropStringArrayArrayInnerList( _arrayName, _sendOrRecv, 5 ) #define RecvPropStringArrayArray( _arrayName ) PropStringArrayArrayOuterList( _arrayName, Recv ) #define SendPropStringArrayArray( _arrayName ) PropStringArrayArrayOuterList( _arrayName, Send ) REGISTER_GAMERULES_CLASS( CPortalMPGameRules ); BEGIN_NETWORK_TABLE_NOBASE( CPortalMPGameRules, DT_PortalMPGameRules ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bTeamPlayEnabled ) ), RecvPropInt( RECVINFO( m_nCoopSectionIndex ) ), RecvPropArray3( RECVINFO_ARRAY( m_nCoopBranchIndex ), RecvPropInt( RECVINFO( m_nCoopBranchIndex[0] ) ) ), RecvPropInt( RECVINFO( m_nSelectedDLCCourse ) ), RecvPropInt( RECVINFO( m_nNumPortalsPlaced ) ), RecvPropBool( RECVINFO( m_bMapNamesLoaded ) ), RecvPropStringArrayArray( m_szLevelNames ), RecvPropArray3( RECVINFO_ARRAY( m_nLevelCount ), RecvPropInt( RECVINFO( m_nLevelCount[0] ) ) ), // CREDITS RecvPropString( RECVINFO( m_szCoopCreditsNameSingle ) ), RecvPropString( RECVINFO( m_szCoopCreditsJobTitle ) ), RecvPropInt( RECVINFO( m_nCoopCreditsIndex ) ), RecvPropBool( RECVINFO( m_bCoopCreditsLoaded ) ), RecvPropInt( RECVINFO( m_nCoopCreditsState ) ), RecvPropInt( RECVINFO( m_nCoopCreditsScanState ) ), RecvPropBool( RECVINFO( m_bCoopFadeCreditsState ) ), #else SendPropBool( SENDINFO( m_bTeamPlayEnabled ) ), SendPropInt( SENDINFO( m_nCoopSectionIndex ) ), SendPropArray3( SENDINFO_ARRAY3( m_nCoopBranchIndex ), SendPropInt( SENDINFO_ARRAY( m_nCoopBranchIndex ) ) ), SendPropInt( SENDINFO( m_nSelectedDLCCourse ) ), SendPropInt( SENDINFO( m_nNumPortalsPlaced ) ), SendPropBool( SENDINFO( m_bMapNamesLoaded ) ), SendPropStringArrayArray( m_szLevelNames ), SendPropArray3( SENDINFO_ARRAY3( m_nLevelCount ), SendPropInt( SENDINFO_ARRAY( m_nLevelCount ) ) ), // CREDITS SendPropString( SENDINFO( m_szCoopCreditsNameSingle ) ), SendPropString( SENDINFO( m_szCoopCreditsJobTitle ) ), SendPropInt( SENDINFO( m_nCoopCreditsIndex ) ), SendPropBool( SENDINFO( m_bCoopCreditsLoaded ) ), SendPropInt( SENDINFO( m_nCoopCreditsState ) ), SendPropInt( SENDINFO( m_nCoopCreditsScanState ) ), SendPropBool( SENDINFO( m_bCoopFadeCreditsState ) ), #endif END_NETWORK_TABLE() IMPLEMENT_NETWORKCLASS_ALIASED( PortalMPGameRulesProxy, DT_PortalMPGameRulesProxy ) LINK_ENTITY_TO_CLASS_ALIASED( portalmp_gamerules, PortalMPGameRulesProxy ); static PortalMPViewVectors g_PortalMPViewVectors( Vector( 0, 0, 64 ), //VEC_VIEW (m_vView) Vector(-16, -16, 0 ), //VEC_HULL_MIN (m_vHullMin) Vector( 16, 16, 72 ), //VEC_HULL_MAX (m_vHullMax) Vector(-16, -16, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin) Vector( 16, 16, 36 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax) Vector( 0, 0, 28 ), //VEC_DUCK_VIEW (m_vDuckView) Vector(-10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin) Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax) Vector( 0, 0, 60 ), //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight) // previously 14 Vector(-16, -16, 0 ), //VEC_CROUCH_TRACE_MIN (m_vCrouchTraceMin) Vector( 16, 16, 60 ) //VEC_CROUCH_TRACE_MAX (m_vCrouchTraceMax) ); static const char *s_PreserveEnts[] = { "ai_network", "ai_hint", "hl2mp_gamerules", "team_manager", "player_manager", "env_soundscape", "env_soundscape_proxy", "env_soundscape_triggerable", "env_sun", "env_wind", "env_fog_controller", "func_brush", "func_wall", "func_buyzone", "func_illusionary", "infodecal", "info_projecteddecal", "info_node", "info_target", "info_node_hint", "info_player_deathmatch", "info_player_combine", "info_player_rebel", "info_map_parameters", "keyframe_rope", "move_rope", "info_ladder", "player", "point_viewcontrol", "scene_manager", "shadow_control", "sky_camera", "soundent", "trigger_soundscape", "viewmodel", "predicted_viewmodel", "worldspawn", "point_devshot_camera", "", // END Marker }; CPortalMPGameRules *g_pPortalMPGameRules = NULL; ConVar cm_is_current_community_map_coop( "cm_is_current_community_map_coop", "0", FCVAR_HIDDEN | FCVAR_REPLICATED ); #ifdef CLIENT_DLL void RecvProxy_PortalMPRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) { CPortalMPGameRules *pRules = PortalMPGameRules(); Assert( pRules ); *pOut = pRules; } BEGIN_RECV_TABLE( CPortalMPGameRulesProxy, DT_PortalMPGameRulesProxy ) RecvPropDataTable( "portalmp_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_PortalMPGameRules ), RecvProxy_PortalMPRules ) END_RECV_TABLE() #else void* SendProxy_PortalMPRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) { CPortalMPGameRules *pRules = PortalMPGameRules(); Assert( pRules ); return pRules; } BEGIN_SEND_TABLE( CPortalMPGameRulesProxy, DT_PortalMPGameRulesProxy ) SendPropDataTable( "portalmp_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_PortalMPGameRules ), SendProxy_PortalMPRules ) END_SEND_TABLE() #endif #if defined ( GAME_DLL ) BEGIN_DATADESC( CPortalMPGameRulesProxy ) // Inputs. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddRedTeamScore", InputAddRedTeamScore ), DEFINE_INPUTFUNC( FIELD_INTEGER, "AddBlueTeamScore", InputAddBlueTeamScore ), END_DATADESC() // TEMP: This sucks long term... probably want some class to handle all team effecting operations. void UTIL_Portal2MP_AddTeamScore( int iTeamNum, int iScoreIncrement ) { bool bSuccess = false; if ( g_Teams.IsValidIndex( iTeamNum ) ) { CTeam *pTeam = g_Teams[ iTeamNum ]; if ( pTeam ) { pTeam->AddScore( iScoreIncrement ); bSuccess = true; } } if ( !bSuccess ) { Warning( "AddTeamScore failed to add score. Invalid team index?\n" ); Assert ( 0 ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CPortalMPGameRulesProxy::InputAddRedTeamScore( inputdata_t &inputdata ) { UTIL_Portal2MP_AddTeamScore( TEAM_RED, inputdata.value.Int() ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CPortalMPGameRulesProxy::InputAddBlueTeamScore( inputdata_t &inputdata ) { UTIL_Portal2MP_AddTeamScore( TEAM_BLUE, inputdata.value.Int() ); } #endif // GAME_DLL // NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. char *sTeamNames[] = { "Unassigned", "Spectator", "Combine", "Rebels", }; CPortalMPGameRules::CPortalMPGameRules() { g_pPortalMPGameRules = this; #ifndef CLIENT_DLL // Create the team managers for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) { CTeam *pTeam = static_cast(CreateEntityByName( "team_manager" )); pTeam->Init( sTeamNames[i], i ); g_Teams.AddToTail( pTeam ); } m_bTeamPlayEnabled = teamplay.GetBool(); m_flIntermissionEndTime = 0.0f; m_flGameStartTime = 0; m_hRespawnableItemsAndWeapons.RemoveAll(); m_tmNextPeriodicThink = 0; m_flRestartGameTime = 0; m_bCompleteReset = false; m_bHeardAllPlayersReady = false; m_bAwaitingReadyRestart = false; m_bGladosJustBlewUp = false; m_fNextDLCSelectTime = 0.0f; m_nCoopSectionIndex = 0; for (int i=0; iFindVar( "sv_maxreplay" )->SetValue( "1.5" ); //Set health regeneration to enabled if ( !GlobalEntity_IsInTable( "player_regenerates_health" ) ) { GlobalEntity_Add( MAKE_STRING("player_regenerates_health"), gpGlobals->mapname, GLOBAL_ON ); } else { GlobalEntity_SetState( MAKE_STRING("player_regenerates_health"), GLOBAL_ON ); } if ( !GlobalEntity_IsInTable( "player_blue_deaths" ) ) { GlobalEntity_Add( MAKE_STRING( "player_blue_deaths" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetCounter( MAKE_STRING( "player_blue_deaths" ), 0 ); } if ( !GlobalEntity_IsInTable( "player_orange_deaths" ) ) { GlobalEntity_Add( MAKE_STRING( "player_orange_deaths" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetCounter( MAKE_STRING( "player_orange_deaths" ), 0 ); } // Need a bunch of flags for Chet to hold global state per session if ( !GlobalEntity_IsInTable( "glados_spoken_flags0" ) ) { GlobalEntity_Add( MAKE_STRING( "glados_spoken_flags0" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "glados_spoken_flags0" ), 0 ); } if ( !GlobalEntity_IsInTable( "glados_spoken_flags1" ) ) { GlobalEntity_Add( MAKE_STRING( "glados_spoken_flags1" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "glados_spoken_flags1" ), 0 ); } if ( !GlobalEntity_IsInTable( "glados_spoken_flags2" ) ) { GlobalEntity_Add( MAKE_STRING( "glados_spoken_flags2" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "glados_spoken_flags2" ), 0 ); } if ( !GlobalEntity_IsInTable( "glados_spoken_flags3" ) ) { GlobalEntity_Add( MAKE_STRING( "glados_spoken_flags3" ), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "glados_spoken_flags3" ), 0 ); } if ( !GlobalEntity_IsInTable( "current_branch" ) ) { GlobalEntity_Add( MAKE_STRING( "current_branch"), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetCounter( MAKE_STRING( "current_branch" ), 0 ); } if ( !GlobalEntity_IsInTable( "levels_completed_this_branch" ) ) { GlobalEntity_Add( MAKE_STRING( "levels_completed_this_branch"), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetCounter( MAKE_STRING( "levels_completed_this_branch" ), 0 ); } if ( !GlobalEntity_IsInTable( "came_from_last_dlc_map" ) ) { GlobalEntity_Add( MAKE_STRING( "came_from_last_dlc_map"), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "came_from_last_dlc_map" ), 0 ); } if ( !GlobalEntity_IsInTable( "have_seen_dlc_tubes_reveal" ) ) { GlobalEntity_Add( MAKE_STRING( "have_seen_dlc_tubes_reveal"), gpGlobals->mapname, GLOBAL_ON ); GlobalEntity_SetFlags( MAKE_STRING( "have_seen_dlc_tubes_reveal" ), 0 ); } } m_bDataReceived[ 0 ] = m_bDataReceived[ 1 ] = false; m_nRPSWinCount[ 0 ] = m_nRPSWinCount[ 1 ] = 0; static ConVarRef flashlightbrightness( "r_flashlightbrightness" ); if ( flashlightbrightness.IsValid() ) { // All MP maps use this brightness and we can't set it in the map because there's no cheating in MP! flashlightbrightness.SetValue( 0.25f ); } #else locator_lerp_rest.SetValue( 0.0f ); locator_start_at_crosshair.SetValue( 1 ); locator_topdown_style.SetValue( 0 ); locator_background_style.SetValue( 0 ); locator_background_color.SetValue( "0 0 0 128"); locator_target_offset_x.SetValue( 0 ); locator_target_offset_y.SetValue( 0 ); locator_background_thickness_x.SetValue( 12 ); locator_background_thickness_y.SetValue( 12 ); locator_background_shift_x.SetValue( 0 ); locator_background_shift_y.SetValue( 0 ); locator_background_border_color.SetValue( "32 32 32 64" ); locator_icon_min_size_non_ss.SetValue( 1.0f ); locator_icon_max_size_non_ss.SetValue( 1.15f ); voice_icons_use_particles.SetValue( 1 ); #endif m_bMapNamesLoaded = false; m_bCoopCreditsLoaded = false; m_nCoopCreditsState = LIST_NAMES; m_nCoopCreditsScanState = 0; m_bCoopFadeCreditsState = false; memset( m_bLevelCompletions, 0, sizeof( m_bLevelCompletions ) ); } const CViewVectors* CPortalMPGameRules::GetViewVectors()const { return &g_PortalMPViewVectors; } const PortalMPViewVectors* CPortalMPGameRules::GetPortalMPViewVectors()const { return &g_PortalMPViewVectors; } void CPortalMPGameRules::LevelInitPreEntity() { m_bIsCoopInMapName = (V_stristr( MapName(), "coop" ) != NULL); m_bIs2GunsInMapName = (V_stristr( MapName(), "2guns" ) != NULL); m_bIsVSInMapName = (V_stristr( MapName(), "vs" ) != NULL); #ifdef GAME_DLL m_nRPSWinCount[ 0 ] = m_nRPSWinCount[ 1 ] = 0; m_bGladosJustBlewUp = false; CPortalMPStats *pStats = GetPortalMPStats(); if ( pStats ) { pStats->ClearPerMapStats(); } static ConVarRef flashlightbrightness( "r_flashlightbrightness" ); if ( flashlightbrightness.IsValid() ) { // All MP maps use this brightness and we can't set it in the map because there's no cheating in MP! flashlightbrightness.SetValue( 0.25f ); } if ( IsLobbyMap() ) { // For achievement STAYING_ALIVE: // reset number of levels completed when coming to the lobby GlobalEntity_SetCounter( MAKE_STRING( "levels_completed_this_branch" ), 0 ); GlobalEntity_SetCounter( MAKE_STRING( "current_branch" ), -1 ); } #else m_bIsClientCrossplayingPCvsPC = IsPC() && !ClientIsCrossplayingWithConsole(); #endif } bool CPortalMPGameRules::IsCoOp( void ) { static ConVarRef coop_ref( "coop" ); return m_bIsCoopInMapName || coop_ref.GetBool() || IsCommunityCoop(); } bool CPortalMPGameRules::Is2GunsCoOp( void ) { return m_bIs2GunsInMapName; } bool CPortalMPGameRules::IsVS( void ) { return m_bIsVSInMapName; } #ifdef CLIENT_DLL bool CPortalMPGameRules::IsChallengeMode() { CBasePlayer* pPlayer = UTIL_PlayerByIndex( 1 ); if ( pPlayer ) return pPlayer->GetBonusChallenge() != 0; return false; } #endif CPortalMPGameRules::~CPortalMPGameRules( void ) { #ifdef CLIENT_DLL KeyValueSaver().WriteDirtyKeyValues( PORTAL2_MP_SAVE_FILE ); #endif g_pPortalMPGameRules = NULL; #ifndef CLIENT_DLL // Note, don't delete each team since they are in the gEntList and will // automatically be deleted from there, instead. g_Teams.Purge(); #endif } void CPortalMPGameRules::CreateStandardEntities( void ) { #ifndef CLIENT_DLL // Create the entity that will send our data to the client. BaseClass::CreateStandardEntities(); #ifdef _DEBUG CBaseEntity *pEnt = #endif CBaseEntity::Create( "portalmp_gamerules", vec3_origin, vec3_angle ); Assert( pEnt ); // Create stats entities CPortalMPStats::InitPortalMPStats(); #endif } //========================================================= // FlWeaponRespawnTime - what is the time in the future // at which this weapon may spawn? //========================================================= float CPortalMPGameRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) { #ifndef CLIENT_DLL if ( weaponstay.GetInt() > 0 ) { // make sure it's only certain weapons if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) { return 0; // weapon respawns almost instantly } } return sv_hl2mp_weapon_respawn_time.GetFloat(); #endif return 0; // weapon respawns almost instantly } //Runs think for all player's conditions //Need to do this here instead of the player so players that crash still run their important thinks void CPortalMPGameRules::RunPlayerConditionThink( void ) { for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CPortal_Player *pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->m_Shared.ConditionGameRulesThink(); } } } void CPortalMPGameRules::FrameUpdatePostEntityThink( void ) { RunPlayerConditionThink(); #ifndef CLIENT_DLL BaseClass::FrameUpdatePostEntityThink(); #endif } bool CPortalMPGameRules::IsIntermission( void ) { #ifndef CLIENT_DLL return m_flIntermissionEndTime > gpGlobals->curtime; #endif return false; } void CPortalMPGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { #ifndef CLIENT_DLL if ( IsIntermission() ) return; BaseClass::PlayerKilled( pVictim, info ); #endif } void CPortalMPGameRules::Think( void ) { #ifndef CLIENT_DLL CGameRules::Think(); #endif } void CPortalMPGameRules::GoToIntermission( void ) { #ifndef CLIENT_DLL if ( g_fGameOver ) return; g_fGameOver = true; m_flIntermissionEndTime = gpGlobals->curtime + mp_chattime.GetInt(); for ( int i = 0; i < MAX_PLAYERS; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); pPlayer->AddFlag( FL_FROZEN ); } #endif } bool CPortalMPGameRules::CheckGameOver() { #ifndef CLIENT_DLL if ( g_fGameOver ) // someone else quit the game already { // check to see if we should change levels now if ( m_flIntermissionEndTime < gpGlobals->curtime ) { ChangeLevel(); // intermission is over } return true; } #endif return false; } // 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 CPortalMPGameRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) { #ifndef CLIENT_DLL 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 ); } #endif return 0; } //========================================================= // VecWeaponRespawnSpot - where should this weapon spawn? // Some game variations may choose to randomize spawn locations //========================================================= Vector CPortalMPGameRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) { //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled weapon respawn location code" ) #if 0 #ifndef CLIENT_DLL CWeaponHL2MPBase *pHL2Weapon = dynamic_cast< CWeaponHL2MPBase*>( pWeapon ); if ( pHL2Weapon ) { return pHL2Weapon->GetOriginalSpawnOrigin(); } #endif #endif return pWeapon->GetAbsOrigin(); } #ifndef CLIENT_DLL CItem* IsManagedObjectAnItem( CBaseEntity *pObject ) { return dynamic_cast< CItem*>( pObject ); } CWeaponPortalBase* IsManagedObjectAWeapon( CBaseEntity *pObject ) { return dynamic_cast( pObject ); } bool GetObjectsOriginalParameters( CBaseEntity *pObject, Vector &vOriginalOrigin, QAngle &vOriginalAngles ) { if ( CItem *pItem = IsManagedObjectAnItem( pObject ) ) { //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled rest time code" ) #if 0 if ( pItem->m_flNextResetCheckTime > gpGlobals->curtime ) return false; #endif vOriginalOrigin = pItem->GetOriginalSpawnOrigin(); vOriginalAngles = pItem->GetOriginalSpawnAngles(); #if 0 pItem->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_item_respawn_time.GetFloat(); #endif return true; } else if ( CWeaponPortalBase *pWeapon = IsManagedObjectAWeapon( pObject )) { if ( pWeapon->m_flNextResetCheckTime > gpGlobals->curtime ) return false; vOriginalOrigin = pWeapon->GetOriginalSpawnOrigin(); vOriginalAngles = pWeapon->GetOriginalSpawnAngles(); pWeapon->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_weapon_respawn_time.GetFloat(); return true; } return false; } void CPortalMPGameRules::ManageObjectRelocation( void ) { int iTotal = m_hRespawnableItemsAndWeapons.Count(); if ( iTotal > 0 ) { for ( int i = 0; i < iTotal; i++ ) { CBaseEntity *pObject = m_hRespawnableItemsAndWeapons[i].Get(); if ( pObject ) { Vector vSpawOrigin; QAngle vSpawnAngles; if ( GetObjectsOriginalParameters( pObject, vSpawOrigin, vSpawnAngles ) == true ) { float flDistanceFromSpawn = (pObject->GetAbsOrigin() - vSpawOrigin ).Length(); if ( flDistanceFromSpawn > WEAPON_MAX_DISTANCE_FROM_SPAWN ) { bool shouldReset = false; IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); if ( pPhysics ) { shouldReset = pPhysics->IsAsleep(); } else { shouldReset = (pObject->GetFlags() & FL_ONGROUND) ? true : false; } if ( shouldReset ) { pObject->Teleport( &vSpawOrigin, &vSpawnAngles, NULL ); pObject->EmitSound( "AlyxEmp.Charge" ); IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); if ( pPhys ) { pPhys->Wake(); } } } } } } } } //========================================================= //AddLevelDesignerPlacedWeapon //========================================================= void CPortalMPGameRules::AddLevelDesignerPlacedObject( CBaseEntity *pEntity ) { if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) == -1 ) { m_hRespawnableItemsAndWeapons.AddToTail( pEntity ); } } //========================================================= //RemoveLevelDesignerPlacedWeapon //========================================================= void CPortalMPGameRules::RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ) { if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) != -1 ) { m_hRespawnableItemsAndWeapons.FindAndRemove( pEntity ); } } //========================================================= // Where should this item respawn? // Some game variations may choose to randomize spawn locations //========================================================= Vector CPortalMPGameRules::VecItemRespawnSpot( CItem *pItem ) { return pItem->GetOriginalSpawnOrigin(); } //========================================================= // What angles should this item use to respawn? //========================================================= QAngle CPortalMPGameRules::VecItemRespawnAngles( CItem *pItem ) { return pItem->GetOriginalSpawnAngles(); } //========================================================= // At what time in the future may this Item respawn? //========================================================= float CPortalMPGameRules::FlItemRespawnTime( CItem *pItem ) { return sv_hl2mp_item_respawn_time.GetFloat(); } //========================================================= // CanHaveWeapon - returns false if the player is not allowed // to pick up this weapon //========================================================= bool CPortalMPGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) { if ( weaponstay.GetInt() > 0 ) { if ( pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) ) return false; } return BaseClass::CanHavePlayerItem( pPlayer, pItem ); } #endif //========================================================= // WeaponShouldRespawn - any conditions inhibiting the // respawning of this weapon? //========================================================= int CPortalMPGameRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) { #ifndef CLIENT_DLL if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) { return GR_WEAPON_RESPAWN_NO; } #endif return GR_WEAPON_RESPAWN_YES; } //========================================================= // Deathnotice. //========================================================= void CPortalMPGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { #ifndef CLIENT_DLL // 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 ); // Custom kill 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()->GetClassname(); } } else { killer_weapon_name = pInflictor->GetClassname(); // it's just that easy } } } else { killer_weapon_name = pInflictor->GetClassname(); } // 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; } else if ( strstr( killer_weapon_name, "physics" ) ) { killer_weapon_name = "physics"; } if ( strcmp( killer_weapon_name, "prop_combine_ball" ) == 0 ) { killer_weapon_name = "combine_ball"; } else if ( strcmp( killer_weapon_name, "grenade_ar2" ) == 0 ) { killer_weapon_name = "smg1_grenade"; } else if ( strcmp( killer_weapon_name, "satchel" ) == 0 || strcmp( killer_weapon_name, "tripmine" ) == 0) { killer_weapon_name = "slam"; } } IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); if( event ) { event->SetInt("userid", pVictim->GetUserID() ); event->SetInt("attacker", killer_ID ); event->SetString("weapon", killer_weapon_name ); event->SetInt( "priority", 7 ); gameeventmanager->FireEvent( event ); } bool bIsBlue = pVictim->GetTeamNumber() == TEAM_BLUE; if ( bIsBlue ) { GlobalEntity_AddToCounter( MAKE_STRING( "player_blue_deaths" ), 1 ); engine->ClientCommand( pVictim->edict(), "signify death_blue -1 0 %.2f %.2f %.2f 0 0 1", pVictim->GetAbsOrigin().x, pVictim->GetAbsOrigin().y, pVictim->GetAbsOrigin().z + 32.0f ); } else { GlobalEntity_AddToCounter( MAKE_STRING( "player_orange_deaths" ), 1 ); engine->ClientCommand( pVictim->edict(), "signify death_orange -1 0 %.2f %.2f %.2f 0 0 1", pVictim->GetAbsOrigin().x, pVictim->GetAbsOrigin().y, pVictim->GetAbsOrigin().z + 32.0f ); } #endif } void CPortalMPGameRules::ClientSettingsChanged( CBasePlayer *pPlayer ) { #ifndef CLIENT_DLL if ( sv_report_client_settings.GetInt() == 1 ) { UTIL_LogPrintf( "\"%s\" cl_cmdrate = \"%s\"\n", pPlayer->GetPlayerName(), engine->GetClientConVarValue( pPlayer->entindex(), "cl_cmdrate" )); } BaseClass::ClientSettingsChanged( pPlayer ); #endif } int CPortalMPGameRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) { #ifndef CLIENT_DLL // half life multiplay has a simple concept of Player Relationships. // you are either on another player's team, or you are not. if ( !pPlayer || !pTarget || !pTarget->IsPlayer() || IsTeamplay() == false ) return GR_NOTTEAMMATE; if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) { return GR_TEAMMATE; } #endif return GR_NOTTEAMMATE; } const char *CPortalMPGameRules::GetGameDescription( void ) { return "Portal 2 Coop"; } float CPortalMPGameRules::GetMapRemainingTime() { // if timelimit is disabled, return 0 if ( mp_timelimit.GetInt() <= 0 ) return 0; // timelimit is in minutes float timeleft = (m_flGameStartTime + mp_timelimit.GetInt() * 60.0f ) - gpGlobals->curtime; return timeleft; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPortalMPGameRules::Precache( void ) { #ifndef CLIENT_DLL BaseClass::Precache(); #endif CBaseEntity::PrecacheScriptSound( "AlyxEmp.Charge" ); } bool CPortalMPGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) { if ( collisionGroup0 > collisionGroup1 ) { // swap so that lowest is always first V_swap(collisionGroup0,collisionGroup1); } if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && collisionGroup1 == COLLISION_GROUP_WEAPON ) { return false; } // Cubes shouldn't collide with debris but should otherwise act like COLLISION_GROUP_NONE if( collisionGroup1 == COLLISION_GROUP_WEIGHTED_CUBE && collisionGroup0 == COLLISION_GROUP_DEBRIS ) return false; if( collisionGroup0 == COLLISION_GROUP_WEIGHTED_CUBE ) collisionGroup0 = COLLISION_GROUP_NONE; if( collisionGroup1 == COLLISION_GROUP_WEIGHTED_CUBE ) collisionGroup1 = COLLISION_GROUP_NONE; return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); } #if !defined ( CLIENT_DLL ) const char *CPortalMPGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer ) { return ""; } #endif bool CPortalMPGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) { const char *pcmd = args[0]; if ( FStrEq( pcmd, "lobby_select_day" ) ) { if ( args.ArgC() < 2 ) return true; int nDay = atoi( args[1] ); // Msg("Selecting day %d\n", nDay ); m_nCoopSectionIndex = nDay; return true; } else if ( FStrEq( pcmd, "coop_set_credits_jobtitle" ) ) { if ( args.ArgC() < 2 ) return true; //int nIndex = atoi( args[1] ); V_strcpy( m_szCoopCreditsJobTitle.GetForModify(), args[1] ); return true; } else if ( FStrEq( pcmd, "coop_set_credits_index" ) ) { if ( args.ArgC() < 3 ) return true; int nIndex = atoi( args[1] ); int nScan = atoi( args[2] ); //Msg("Selecting index %d\n", nIndex ); //Msg("Scanning type %d\n", nScan ); if ( nIndex < m_szCoopCreditsNames.Count() ) { V_strcpy( m_szCoopCreditsNameSingle.GetForModify(), m_szCoopCreditsNames[ nIndex ] ); m_nCoopCreditsIndex = nIndex; } m_nCoopCreditsScanState = nScan; return true; } else if ( FStrEq( pcmd, "coop_set_credits_state" ) ) { if ( args.ArgC() < 2 ) return true; int nIndex = atoi( args[1] ); //Msg("Setting credits state to %d\n", nIndex ); m_nCoopCreditsState = nIndex; m_bCoopFadeCreditsState = !m_bCoopFadeCreditsState; return true; } else if ( FStrEq( pcmd, "coop_lobby_select_level" ) ) { if ( args.ArgC() < 3 ) { Msg("Not enough arguments for coop_lobby_select_level. Format should be: coop_lobby_select_level \n" ); return true; } int nBranch = atoi( args[1] ); nBranch--; if ( nBranch < 0 || nBranch >= MAX_PORTAL2_COOP_BRANCHES ) { Msg("Branch argument is out of range for coop_lobby_select_level. It needs to be a positive number less than %d.\n", MAX_PORTAL2_COOP_BRANCHES ); return true; } int nLevel = atoi( args[2] ); int nSkipRequirement = atoi( args[3] ); // Start at the back an loop toward the start int nMaxLevel; for ( nMaxLevel = MAX_PORTAL2_COOP_LEVELS_PER_BRANCH - 1; nMaxLevel >= 0; --nMaxLevel ) { // Not a valid level, keep going if ( m_szLevelNames[ nBranch ][ nMaxLevel ][ 0 ] == '\0' ) continue; // First valid level? We're done if skipping requirement if ( nSkipRequirement ) break; // First completed level for either player? This is the max if ( m_bLevelCompletions[ 0 ][ nBranch ][ nMaxLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nMaxLevel ] ) break; } // Scoot back up to the last level not completed by either player nMaxLevel++; nLevel = MIN( nLevel, MIN( nMaxLevel + 1, m_nLevelCount[ nBranch ] ) ); m_nCoopBranchIndex.Set( nBranch, nLevel ); //Msg("Selecting branch %d, level %d\n", nBranch+1, nLevel ); #ifdef GAME_DLL if ( !IsLobbyMap() ) { if ( nLevel >= 0 ) { if ( PortalMPGameRules()->IsPlayerLevelInBranchComplete( 0, nBranch, nLevel ) && PortalMPGameRules()->IsPlayerLevelInBranchComplete( 1, nBranch, nLevel ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "map_already_completed" ); if ( event ) { gameeventmanager->FireEvent( event ); } } } int nCurrentBranch = GlobalEntity_GetCounter( MAKE_STRING( "current_branch" ) ); if ( nCurrentBranch != ( nBranch + 1 ) || nLevel <= 1 ) { // For achievement STAYING_ALIVE: // Reset both players' death counts and level tracking on day change. GlobalEntity_SetCounter( MAKE_STRING( "player_blue_deaths" ), 0 ); GlobalEntity_SetCounter( MAKE_STRING( "player_orange_deaths" ), 0 ); GlobalEntity_SetCounter( MAKE_STRING( "levels_completed_this_branch" ), 0 ); GlobalEntity_SetCounter( MAKE_STRING( "current_branch" ), ( nBranch + 1 ) ); } } #endif return true; } else if ( FStrEq( pcmd, "coop_lobby_select_course" ) ) { if ( args.ArgC() < 2 ) { Msg("Not enough arguments for coop_lobby_select_course. Format should be: coop_lobby_select_course \n" ); return true; } // HACK HACK: For some reason this command is fired 3 times every time I press the button // The commands above have this same problem, but they aren't relative if ( m_fNextDLCSelectTime < gpGlobals->curtime ) { m_fNextDLCSelectTime = gpGlobals->curtime + 0.01f; m_nSelectedDLCCourse += atoi( args[ 1 ] ); } } #ifndef CLIENT_DLL if( BaseClass::ClientCommand( pEdict, args ) ) return true; CBasePlayer *pPlayer = (CBasePlayer *) pEdict; if ( pPlayer->ClientCommand( args ) ) return true; #endif return false; } // shared ammo definition // JAY: Trying to make a more physical bullet response #define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) #define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) // exaggerate all of the forces, but use real numbers to keep them consistent #define BULLET_IMPULSE_EXAGGERATION 3.5 // convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s #define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) #ifdef CLIENT_DLL ConVar cl_autowepswitch( "cl_autowepswitch", "1", FCVAR_ARCHIVE | FCVAR_USERINFO, "Automatically switch to picked up weapons (if more powerful)" ); #else bool CPortalMPGameRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) { if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() ) { // Player has an active item, so let's check cl_autowepswitch. const char *cl_autowepswitch = engine->GetClientConVarValue( pPlayer->entindex(), "cl_autowepswitch" ); if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) { return false; } } return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); } #endif #ifndef CLIENT_DLL void CPortalMPGameRules::PlayerSpawn( CBasePlayer *pPlayer ) { bool addDefault; CBaseEntity *pWeaponEntity = NULL; //don't equip the suit //pPlayer->EquipSuit(); addDefault = true; while ( (pWeaponEntity = gEntList.FindEntityByClassname( pWeaponEntity, "game_player_equip" )) != NULL) { pWeaponEntity->Touch( pPlayer ); addDefault = false; } } bool CPortalMPGameRules::FPlayerCanRespawn( CBasePlayer *pPlayer ) { if ( m_bGladosJustBlewUp == true ) return false; return true; } void CPortalMPGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) { if ( !pKeyValues || !pEntity || !pEntity->GetIServerEntity() ) return; CPortal_Player *pPlayer = assert_cast< CPortal_Player* >( pEntity->GetIServerEntity()->GetBaseEntity() ); if ( !pPlayer ) return; char const *szCommand = pKeyValues->GetName(); if ( FStrEq( szCommand, "read_stats" ) ) { int nPlayer = pPlayer->GetTeamNumber() == TEAM_BLUE ? 0 : 1; m_bDataReceived[ nPlayer ] = true; int nStrLen = V_strlen( "MP.complete." ); for ( KeyValues *kvValue = pKeyValues->GetFirstValue(); kvValue; kvValue = kvValue->GetNextValue() ) { char const *pchName = kvValue->GetName(); if ( StringHasPrefix( pchName, "MP.complete." ) ) { pchName += nStrLen; } else { continue; } // Request key values for all the levels if ( kvValue->GetInt( "" ) != 0 ) { SetMapCompleteSimple( nPlayer, pchName, kvValue->GetInt() != 0 ); } } if ( ( m_bDataReceived[ 0 ] && m_bDataReceived[ 1 ] ) || (!mp_dev_wait_for_other_player.GetBool() || (IsLocalSplitScreen() && IsCreditsMap()) ) || IsCommunityCoopHub() ) { SendAllMapCompleteData(); StartPlayerTransitionThinks(); } } else if ( FStrEq( szCommand, "read_awards" ) ) { // TODO } else if ( FStrEq( szCommand, "read_leaderboard" ) ) { KeyValuesDumpAsDevMsg( pKeyValues, 1 ); } } //----------------------------------------------------------------------------- // Purpose: Damage (applied per second) value of the npc_laser_turret //----------------------------------------------------------------------------- float CPortalMPGameRules::GetLaserTurretDamage( void ) { switch( GetSkillLevel() ) { case SKILL_EASY: return 120.0f; case SKILL_MEDIUM: return 200.0f; case SKILL_HARD: return 200.0f; default: return 100.0f; } } //----------------------------------------------------------------------------- // Purpose: Movement speed of the turret. //----------------------------------------------------------------------------- float CPortalMPGameRules::GetLaserTurretMoveSpeed( void ) { switch( GetSkillLevel() ) { case SKILL_EASY: return 0.6f; case SKILL_MEDIUM: return 0.8f; case SKILL_HARD: return 1.0f; default: return 0.7f; } } //----------------------------------------------------------------------------- // Purpose: Damage value of the npc_rocket_turret //----------------------------------------------------------------------------- float CPortalMPGameRules::GetRocketTurretDamage( void ) { switch( GetSkillLevel() ) { case SKILL_EASY: return 120.0f; case SKILL_MEDIUM: return 200.0f; case SKILL_HARD: return 200.0f; default: return 100.0f; } } float CPortalMPGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) { // No fall damage in Portal! return 0.0f; } // Stealing a chunk of HL2 Gamerules for portal2 coop // This is just to make turrets shoot at the players under multiplayer to // facilitate an experiment... We might need our own separate gamerules for coop depending // on what the game ends up being like. void CPortalMPGameRules::InitDefaultAIRelationships() { int i,j; // Allocate memory for default relationships CBaseCombatCharacter::AllocateDefaultRelationships(); // -------------------------------------------------------------- // First initialize table so we can report missing relationships // -------------------------------------------------------------- int iNumClasses = GameRules() ? GameRules()->NumEntityClasses() : LAST_SHARED_ENTITY_CLASS; for (i=0;i CLASS_COMBINE // ------------------------------------------------------------ CBaseCombatCharacter::SetDefaultRelationship(CLASS_COMBINE, CLASS_NONE, D_NU, 0); CBaseCombatCharacter::SetDefaultRelationship(CLASS_COMBINE, CLASS_PLAYER, D_HT, 0); } void CPortalMPGameRules::SetMapCompleteData( int nPlayer ) { if ( m_bDataReceived[ nPlayer ] ) { // Already got data for this player! Don't ask for it again return; } // Get the player CPortal_Player *pPlayer = NULL; for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CPortal_Player *pPlayerTemp = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); //If the other player does not exist or if the other player is the local player if ( pPlayerTemp == NULL ) continue; if ( pPlayerTemp->GetTeamNumber() == ( nPlayer == 0 ? TEAM_BLUE : TEAM_RED ) ) { pPlayer = pPlayerTemp; break; } } if ( !pPlayer ) return; // Request key values for all the levels KeyValues *kvClientRequest = new KeyValues( "read_stats" ); for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { if ( m_szLevelNames[ nBranch ][ nLevel ][ 0 ] == '\0' ) continue; kvClientRequest->SetInt( CFmtStr( "MP.complete.%s", m_szLevelNames[ nBranch ][ nLevel ] ), 0 ); } } engine->ClientCommandKeyValues( pPlayer->edict(), kvClientRequest ); } void CPortalMPGameRules::StartPlayerTransitionThinks( void ) { // Turn off video for all players once they've all connected for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *pToPlayer = UTIL_PlayerByIndex( i ); if ( pToPlayer ) { // Respawn other players who were waiting pToPlayer->SetThink( &CPortal_Player::PlayerTransitionCompleteThink ); pToPlayer->SetNextThink( gpGlobals->curtime + 1.0f ); if ( !pToPlayer->HasAttachedSplitScreenPlayers() && !pToPlayer->IsSplitScreenPlayer() ) { CBasePlayer *pOtherPlayer = UTIL_OtherPlayer( pToPlayer ); if ( pOtherPlayer ) { pOtherPlayer->AddPictureInPicturePlayer( pToPlayer ); pToPlayer->AddPictureInPicturePlayer( pOtherPlayer ); } } else if ( IsPC() && ( pToPlayer->HasAttachedSplitScreenPlayers() || pToPlayer->IsSplitScreenPlayer() ) ) { SetAllMapsComplete(); } } } ResetAllPlayersStats(); g_portal_ui_controller.OnLevelStart(); } //----------------------------------------------------------------------------- // Purpose: Player has just left the game //----------------------------------------------------------------------------- void CPortalMPGameRules::ClientDisconnected( edict_t *pClient ) { // Msg( "CLIENT DISCONNECTED, REMOVING FROM TEAM.\n" ); CPortal_Player::ClientDisconnected( pClient ); CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); if ( pPlayer ) { // Remove the player from his team if ( pPlayer->GetTeam() ) { pPlayer->GetTeam()->RemovePlayer( pPlayer ); } } bool bIsSSCredits = (IsLocalSplitScreen() && IsCreditsMap()); if ( ( m_bDataReceived[ 0 ] && m_bDataReceived[ 1 ] && mp_dev_wait_for_other_player.GetBool() && !bIsSSCredits ) || IsCommunityCoop() ) { for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CPortal_Player *pOtherPlayer = static_cast(UTIL_PlayerByIndex( i )); if ( !pOtherPlayer ) continue; if ( pOtherPlayer == pPlayer ) continue; DevMsg( "Client disconnected and we're left with no partner!\n" ); engine->ClientCommand( pOtherPlayer->edict(), "disconnect \"Partner disconnected\"" ); } } BaseClass::ClientDisconnected( pClient ); } bool FindInList( const char **pStrings, const char *pToFind ); void CPortalMPGameRules::RestartGame() { // bounds check if ( mp_timelimit.GetInt() < 0 ) { mp_timelimit.SetValue( 0 ); } m_flGameStartTime = gpGlobals->curtime; if ( !IsFinite( m_flGameStartTime.Get() ) ) { Warning( "Trying to set a NaN game start time\n" ); m_flGameStartTime.GetForModify() = 0.0f; } CleanUpMap(); // now respawn all players for (int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; if ( pPlayer->GetActiveWeapon() ) { pPlayer->GetActiveWeapon()->Holster(); } pPlayer->RemoveAllItems( true ); respawn( pPlayer, false ); //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled player reset" ) #if 0 pPlayer->Reset(); #endif } // Respawn entities (glass, doors, etc..) CTeam *pBlue = GetGlobalTeam( TEAM_BLUE ); CTeam *pRed = GetGlobalTeam( TEAM_RED ); if ( pBlue ) { pBlue->SetScore( 0 ); } if ( pRed ) { pRed->SetScore( 0 ); } m_nNumPortalsPlaced = 0; m_flIntermissionEndTime = 0; m_flRestartGameTime = 0.0; m_bCompleteReset = false; IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); if ( event ) { event->SetInt("fraglimit", 0 ); event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted event->SetString("objective","DEATHMATCH"); gameeventmanager->FireEvent( event ); } } // Utility function bool FindInList( const char **pStrings, const char *pToFind ) { int i = 0; while ( pStrings[i][0] != 0 ) { if ( Q_stricmp( pStrings[i], pToFind ) == 0 ) return true; i++; } return false; } void CPortalMPGameRules::CleanUpMap() { // Recreate all the map entities from the map data (preserving their indices), // then remove everything else except the players. // Get rid of all entities except players. CBaseEntity *pCur = gEntList.FirstEnt(); while ( pCur ) { CBaseCombatWeapon *pWeapon = dynamic_cast< CBaseCombatWeapon* >( pCur ); // Weapons with owners don't want to be removed.. if ( pWeapon ) { if ( !pWeapon->GetOwner() || !pWeapon->GetOwner()->IsPlayer() ) { UTIL_Remove( pCur ); } } // remove entities that has to be restored on roundrestart (breakables etc) else if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) { UTIL_Remove( pCur ); } pCur = gEntList.NextEnt( pCur ); } // Really remove the entities so we can have access to their slots below. gEntList.CleanupDeleteList(); // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that // could kill respawning CTs g_EventQueue.Clear(); //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled entity parsing" ) #if 0 // Now reload the map entities. class CHL2MPMapEntityFilter : public IMapEntityFilter { public: virtual bool ShouldCreateEntity( const char *pClassname ) { // Don't recreate the preserved entities. if ( !FindInList( s_PreserveEnts, pClassname ) ) { return true; } else { // Increment our iterator since it's not going to call CreateNextEntity for this ent. if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) m_iIterator = g_MapEntityRefs.Next( m_iIterator ); return false; } } virtual CBaseEntity* CreateNextEntity( const char *pClassname ) { if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) { // This shouldn't be possible. When we loaded the map, it should have used // CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list // with the same list of entities we're referring to here. Assert( false ); return NULL; } else { CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. if ( ref.m_iEdict == -1 || INDEXENT( ref.m_iEdict ) ) { // Doh! The entity was delete and its slot was reused. // Just use any old edict slot. This case sucks because we lose the baseline. return CreateEntityByName( pClassname ); } else { // Cool, the slot where this entity was is free again (most likely, the entity was // freed above). Now create an entity with this specific index. return CreateEntityByName( pClassname, ref.m_iEdict ); } } } public: int m_iIterator; // Iterator into g_MapEntityRefs. }; CHL2MPMapEntityFilter filter; filter.m_iIterator = g_MapEntityRefs.Head(); // DO NOT CALL SPAWN ON info_node ENTITIES! MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); #endif } void CPortalMPGameRules::CheckRestartGame( void ) { // Restart the game if specified by the server int iRestartDelay = mp_restartgame.GetInt(); if ( iRestartDelay > 0 ) { if ( iRestartDelay > 60 ) iRestartDelay = 60; // let the players know char strRestartDelay[64]; Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); UTIL_ClientPrintAll( HUD_PRINTCENTER, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); m_flRestartGameTime = gpGlobals->curtime + iRestartDelay; m_bCompleteReset = true; mp_restartgame.SetValue( 0 ); } //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled ready restart" ) #if 0 if( mp_readyrestart.GetBool() ) { m_bAwaitingReadyRestart = true; m_bHeardAllPlayersReady = false; const char *pszReadyString = mp_ready_signal.GetString(); // Don't let them put anything malicious in there if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 ) { pszReadyString = "ready"; } IGameEvent *event = gameeventmanager->CreateEvent( "hl2mp_ready_restart" ); if ( event ) gameeventmanager->FireEvent( event ); mp_readyrestart.SetValue( 0 ); // cancel any restart round in progress m_flRestartGameTime = -1; } #endif } void CPortalMPGameRules::CheckAllPlayersReady( void ) { //#pragma message( __FILE__ "(" __LINE__AS_STRING ") : warning custom: Disabled ready restart" ) #if 0 for (int i = 1; i <= gpGlobals->maxClients; i++ ) { CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); if ( !pPlayer ) continue; if ( !pPlayer->IsReady() ) return; } #endif m_bHeardAllPlayersReady = true; } void CPortalMPGameRules::AddBranchLevel( int nBranch, const char *pchName ) { if ( V_strcmp( pchName, "CLEAR ALL" ) == 0 ) { for ( int i = 0; i < MAX_PORTAL2_COOP_BRANCHES; ++i ) { m_nLevelCount.Set( i, 0 ); } m_bMapNamesLoaded = false; return; } if ( nBranch < 0 || nBranch >= MAX_PORTAL2_COOP_BRANCHES ) return; if ( m_nLevelCount[ nBranch ] >= MAX_PORTAL2_COOP_LEVELS_PER_BRANCH ) return; V_strcpy( m_szLevelNames[ nBranch ][ m_nLevelCount[ nBranch ] ], pchName ); m_nLevelCount.Set( nBranch, m_nLevelCount[ nBranch ] + 1 ); NetworkStateChanged(); m_bMapNamesLoaded = true; } void CPortalMPGameRules::SaveMPStats( void ) { // Mark it in storage for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CPortal_Player *pPlayerTemp = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayerTemp == NULL ) continue; if ( GetPortalMPStats() ) { GetPortalMPStats()->SaveStats( pPlayerTemp ); } } } void CPortalMPGameRules::AddCreditsName( const char *pchName ) { if ( V_strcmp( pchName, "CLEAR ALL" ) == 0 ) { m_bCoopCreditsLoaded = false; m_nCoopCreditsIndex = -1; return; } m_szCoopCreditsNames.AddToTail( CUtlString( pchName ) ); NetworkStateChanged(); m_bCoopCreditsLoaded = true; } void CPortalMPGameRules::SetAllMapsComplete( bool bComplete /*= true*/, int nPlayer /*= -1*/ ) { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { SetBranchComplete( nBranch, bComplete ); } // Also mark first level in storage for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CPortal_Player *pPlayerTemp = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayerTemp == NULL ) continue; if ( nPlayer != -1 && ( nPlayer == 0 && pPlayerTemp->GetTeamNumber() != TEAM_BLUE || nPlayer == 1 && pPlayerTemp->GetTeamNumber() != TEAM_RED ) ) { continue; } // We have to build these key values for each player because they are destroyed in ClientCommandKeyValues KeyValues *kvClientRequest = new KeyValues( "write_stats" ); kvClientRequest->SetInt( "MP.complete.mp_coop_start", bComplete ? 1 : 0 ); engine->ClientCommandKeyValues( pPlayerTemp->edict(), kvClientRequest ); } } void CPortalMPGameRules::SetBranchComplete( int nBranch, bool bComplete /*= true*/ ) { if ( nBranch < 0 || nBranch >= MAX_PORTAL2_COOP_BRANCHES ) return; for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( ( bComplete && ( !m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || !m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) ) || ( !bComplete && ( m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) ) ) { // One of the players hasn't completed it CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); UserMessageBegin( player, bComplete ? "MPMapCompleted" : "MPMapIncomplete" ); WRITE_CHAR( nBranch ); WRITE_CHAR( nLevel ); MessageEnd(); m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] = bComplete; m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] = bComplete; } } // TODO unlock taunts per branch here switch ( nBranch ) { case 0: break; case 1: break; case 2: break; case 3: break; case 4: break; } // Mark it in storage for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CPortal_Player *pPlayerTemp = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayerTemp == NULL ) continue; // We have to build these key values for each player because they are destroyed in ClientCommandKeyValues KeyValues *kvClientRequest = new KeyValues( "write_stats" ); for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { kvClientRequest->SetInt( CFmtStr( "MP.complete.%s", m_szLevelNames[ nBranch ][ nLevel ] ), bComplete ? 1 : 0 ); } engine->ClientCommandKeyValues( pPlayerTemp->edict(), kvClientRequest ); } } void CPortalMPGameRules::SetMapComplete( const char *pchName, bool bComplete /*= true*/ ) { // Mark it in storage for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CPortal_Player *pPlayerTemp = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayerTemp == NULL ) continue; // tell the gamestats to send off our per map stats data if ( GetPortalMPStats() ) { GetPortalMPStats()->SavePerMapStats( pPlayerTemp, pchName ); #if !defined ( CLIENT_DLL ) && !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) GetPortalMPStats()->SubmitOGSEndOfMapStatsForPlayer( pPlayerTemp, pchName ); #endif } // We have to build these key values for each player because they are destroyed in ClientCommandKeyValues KeyValues *kvClientRequest = new KeyValues( "write_stats" ); kvClientRequest->SetInt( CFmtStr( "MP.complete.%s", pchName ), bComplete ? 1 : 0 ); engine->ClientCommandKeyValues( pPlayerTemp->edict(), kvClientRequest ); } GetPortalMPStats()->IncrementMapsCompleted(); // Mark it in memory for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( V_strcmp( m_szLevelNames[ nBranch ][ nLevel ], pchName ) == 0 ) { // One of the players hasn't completed it if ( ( bComplete && ( !m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || !m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) ) || ( !bComplete && ( m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) ) ) { CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); UserMessageBegin( player, bComplete ? "MPMapCompleted" : "MPMapIncomplete" ); WRITE_CHAR( nBranch ); WRITE_CHAR( nLevel ); MessageEnd(); m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] = bComplete; m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] = bComplete; } // Increment our number of completed levels so far in the current run of the branch. GlobalEntity_AddToCounter( MAKE_STRING( "levels_completed_this_branch" ), 1 ); return; } } } } void CPortalMPGameRules::SetMapCompleteSimple( int nPlayer, const char *pchName, bool bComplete ) { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( V_strcmp( m_szLevelNames[ nBranch ][ nLevel ], pchName ) == 0 ) { m_bLevelCompletions[ nPlayer ][ nBranch ][ nLevel ] = bComplete; return; } } } } void CPortalMPGameRules::SendAllMapCompleteData( void ) { CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); const int nNumBits = 2 * MAX_PORTAL2_COOP_BRANCHES * MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; byte buff[ sizeof( byte ) * 8 + nNumBits / ( sizeof( byte ) * 8 ) ]; memset( buff, 0, sizeof(buff) ); byte *pCurrent = buff; int nMask = 0x01; for ( int nPlayer = 0; nPlayer < 2; ++nPlayer ) { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { if ( m_bLevelCompletions[ nPlayer ][ nBranch ][ nLevel ] ) { *pCurrent |= nMask; } nMask <<= 1; if ( nMask >= 0x0100 ) { pCurrent++; nMask = 0x01; } } } } UserMessageBegin( player, "MPMapCompletedData" ); WRITE_BITS( buff, nNumBits ); MessageEnd(); } bool CPortalMPGameRules::SupressSpawnPortalgun( int nTeam ) { // Using globals for this is risky because it would carry across levels // Using level names isn't possible because the player might die after picking up a portal gun partway through // So level designers just place an info target with this name and delete it once the player gets a gun if ( nTeam == TEAM_BLUE ) { return gEntList.FindEntityByName( NULL, "supress_blue_portalgun_spawn" ) != NULL; } else if ( nTeam == TEAM_RED ) { return gEntList.FindEntityByName( NULL, "supress_orange_portalgun_spawn" ) != NULL; } return false; } CEG_NOINLINE void CPortalMPGameRules::PlayerWinRPS( CBasePlayer* pWinnerPlayer ) { bool bIsBlueTeam = ( pWinnerPlayer->GetTeamNumber() == TEAM_BLUE ); int nWinnerSlot = bIsBlueTeam ? 0 : 1; int nLoserSlot = bIsBlueTeam ? 1 : 0; #if defined CLIENT_DLL CEG_PROTECT_MEMBER_FUNCTION( CPortalMPGameRules_PlayerWinRPS ); #endif ++m_nRPSWinCount[ nWinnerSlot ]; m_nRPSWinCount[ nLoserSlot ] = 0; if ( m_nRPSWinCount[ nWinnerSlot ] == 3 ) { UTIL_RecordAchievementEvent( "ACH.ROCK_CRUSHES_ROBOT", pWinnerPlayer ); } } #endif // #ifndef CLIENT_DLL int CPortalMPGameRules::GetActiveBranches( void ) { bool bEverythingComplete = true; int nActiveBranches = 1; // The first one is always active // HACK HACK: We don't care about anything after course 5! Completing maps in the dlc shouldn't affect how the rest unlock!!! const int nMaxBranchesWeCareAbout = 5; //MAX_PORTAL2_COOP_BRANCHES; for ( int nBranch = 0; nBranch < nMaxBranchesWeCareAbout; ++nBranch ) { if ( m_nLevelCount[ nBranch ] > 1 ) // Don't include branches that only have 1 map (credits)... they don't count { bool bAnyComplete = false; bool bAllComplete = true; for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( !m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] && !m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) { bAllComplete = false; bEverythingComplete = false; } if ( m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) { bAnyComplete = true; } if ( !bAllComplete && bAnyComplete ) { // We're not going to learn anything else by checking the remaining levels in this branch break; } } if ( bAllComplete ) { // This one and the next one are active nActiveBranches = nBranch + 2; } else if ( bAnyComplete ) { // This one is active nActiveBranches = nBranch + 1; } } } if ( bEverythingComplete ) { nActiveBranches++; } //DevMsg( "\n============\nNUM ACTIVE BRANCHES %i\n============\n", nActiveBranches ); return nActiveBranches; } int CPortalMPGameRules::GetSelectedDLCCourse( void ) { return m_nSelectedDLCCourse; } bool CPortalMPGameRules::IsAnyLevelComplete( void ) { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { if ( m_szLevelNames[ nBranch ][ nLevel ][ 0 ] == '\0' ) continue; if ( m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) { // If any level is completed, return true return true; } } } return false; } bool CPortalMPGameRules::IsFullBranchComplete( int nBranch ) { bool bAllComplete = true; for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( !m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] && !m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ] ) { bAllComplete = false; break; } } return bAllComplete; } bool CPortalMPGameRules::IsPlayerFullBranchComplete( int nPlayer, int nBranch ) { bool bAllComplete = true; for ( int nLevel = 0; nLevel < m_nLevelCount[ nBranch ]; ++nLevel ) { if ( !m_bLevelCompletions[ nPlayer ][ nBranch ][ nLevel ] ) { bAllComplete = false; break; } } return bAllComplete; } #ifndef CLIENT_DLL int CPortalMPGameRules::GetLevelsCompletedThisBranch( void ) { return GlobalEntity_GetCounter( MAKE_STRING( "levels_completed_this_branch" ) ); } #endif bool CPortalMPGameRules::IsLevelInBranchComplete( int nBranch, int nLevel ) { if ( nLevel < 0 || nLevel >= MAX_PORTAL2_COOP_LEVELS_PER_BRANCH || nBranch < 0 || nBranch >= MAX_PORTAL2_COOP_BRANCHES ) return true; return m_bLevelCompletions[ 0 ][ nBranch ][ nLevel ] || m_bLevelCompletions[ 1 ][ nBranch ][ nLevel ]; } void CPortalMPGameRules::SetMapComplete( int nPlayer, int nBranch, int nLevel, bool bComplete /*= true*/ ) { m_bLevelCompletions[ nPlayer ][ nBranch ][ nLevel ] = bComplete; } bool CPortalMPGameRules::IsLobbyMap( void ) { #ifdef CLIENT_DLL return StringHasPrefix( engine->GetLevelNameShort(), "mp_coop_lobby" ); #else return StringHasPrefix( gpGlobals->mapname.ToCStr(), "mp_coop_lobby" ); #endif } bool CPortalMPGameRules::IsStartMap( void ) { #ifdef CLIENT_DLL return V_strcmp( engine->GetLevelNameShort(), "mp_coop_start" ) == 0; #else return V_strcmp( gpGlobals->mapname.ToCStr(), "mp_coop_start" ) == 0; #endif } bool CPortalMPGameRules::IsCreditsMap( void ) { #ifdef CLIENT_DLL return V_strcmp( engine->GetLevelNameShort(), "mp_coop_credits" ) == 0; #else return V_strcmp( gpGlobals->mapname.ToCStr(), "mp_coop_credits" ) == 0; #endif } bool CPortalMPGameRules::IsCommunityCoopHub( void ) { #ifdef CLIENT_DLL return V_strcmp( engine->GetLevelNameShort(), "mp_coop_community_hub" ) == 0; #else return V_strcmp( gpGlobals->mapname.ToCStr(), "mp_coop_community_hub" ) == 0; #endif } bool CPortalMPGameRules::IsCommunityCoop( void ) { return cm_is_current_community_map_coop.GetBool(); } #ifdef CLIENT_DLL static void __MsgFunc_MPMapCompleted( bf_read &msg ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; int nBranch = msg.ReadChar(); int nLevel = msg.ReadChar(); // Both players pRules->SetMapComplete( 0, nBranch, nLevel ); pRules->SetMapComplete( 1, nBranch, nLevel ); } USER_MESSAGE_REGISTER( MPMapCompleted ); static void __MsgFunc_MPMapIncomplete( bf_read &msg ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; int nBranch = msg.ReadChar(); int nLevel = msg.ReadChar(); // Both players pRules->SetMapComplete( 0, nBranch, nLevel, false ); pRules->SetMapComplete( 1, nBranch, nLevel, false ); } USER_MESSAGE_REGISTER( MPMapIncomplete ); static void __MsgFunc_MPMapCompletedData( bf_read &msg ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; const int nNumBits = 2 * MAX_PORTAL2_COOP_BRANCHES * MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; byte buff[ sizeof( byte ) * 8 + nNumBits / ( sizeof( byte ) * 8 ) ]; memset( buff, 0, sizeof(buff) ); msg.ReadBits( buff, nNumBits ); byte *pCurrent = buff; int nMask = 0x01; for ( int nPlayer = 0; nPlayer < 2; ++nPlayer ) { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { if ( ( *pCurrent & nMask ) != 0 ) { pRules->SetMapComplete( nPlayer, nBranch, nLevel ); } nMask <<= 1; if ( nMask >= 0x0100 ) { pCurrent++; nMask = 0x01; } } } } } USER_MESSAGE_REGISTER( MPMapCompletedData ); void CPortalMPGameRules::LoadMapCompleteData( void ) { if ( !m_bMapNamesLoaded ) return; CPortal_Player *pLocalPlayer = CPortal_Player::GetLocalPlayer(); int nPlayer = 0; if ( pLocalPlayer ) { nPlayer = pLocalPlayer->GetTeamNumber() == TEAM_BLUE ? 0 : 1; } char szCommand[ 32 ]; V_snprintf( szCommand, sizeof( szCommand ), "level_complete_data %i", nPlayer ); IPlayerLocal *pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetActiveUserId() ); if ( !pPlayer ) { // We don't support multiple logins on this platform! Just use the primary player pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetUserId( 0 ) ); if ( !pPlayer ) { // Let the server know that we at least attempted to load completion data on the client for this player engine->ClientCmd( szCommand ); return; } } TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); // Process completed maps: { for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { if ( m_szLevelNames[ nBranch ][ nLevel ][ 0 ] == '\0' ) continue; CFmtStr tdKey( "MP.complete.%s", m_szLevelNames[ nBranch ][ nLevel ] ); TitleDataFieldsDescription_t const *fdKey = TitleDataFieldsDescriptionFindByString( fields, tdKey ); int nComplete = 0; if ( fdKey ) { nComplete = TitleDataFieldsDescriptionGetBit( fdKey, pPlayer ) ? 1 : 0; } else { Warning( "CPortalMPGameRules::LoadMapCompleteData failed to load %s\n", tdKey.Access() ); } m_bLevelCompletions[ nPlayer ][ nBranch ][ nLevel ] = ( nComplete != 0 ); } } } GetClientMenuManagerTaunt().KeyValueProcessor( pPlayer ); if ( GetPortalMPStats() ) { GetPortalMPStats()->RefreshStats( pPlayer, pLocalPlayer ); } // Let the server know that we loaded completion data on the client for this player engine->ClientCmd( szCommand ); } static void __MsgFunc_MPTauntEarned( bf_read &msg ) { char szTaunt[ 32 ]; msg.ReadString( szTaunt, sizeof( szTaunt ) ); bool bAwardSilently = !!msg.ReadByte(); GetClientMenuManagerTaunt().SetTauntOwned( szTaunt, bAwardSilently ); //if ( bAwardSilently ) // DevMsg( "Awarding %s, but doing it silently.\n", szTaunt ); // Send event for everything but small wave if ( V_strcmp( szTaunt, "smallWave" ) != 0 ) { IGameEvent *event = gameeventmanager->CreateEvent( "gesture_earned" ); if ( event ) { event->SetInt( "userid", C_BasePlayer::GetLocalPlayer()->GetUserID() ); event->SetBool( "teamtaunt", GetClientMenuManagerTaunt().IsTauntTeam( szTaunt ) ); gameeventmanager->FireEventClientSide( event ); } } } USER_MESSAGE_REGISTER( MPTauntEarned ); static void __MsgFunc_MPTauntLocked( bf_read &msg ) { char szTaunt[ 32 ]; msg.ReadString( szTaunt, sizeof( szTaunt ) ); GetClientMenuManagerTaunt().SetTauntLocked( szTaunt ); } USER_MESSAGE_REGISTER( MPTauntLocked ); static void __MsgFunc_MPAllTauntsLocked( bf_read& /*msg*/ ) { GetClientMenuManagerTaunt().SetAllTauntsLocked(); } USER_MESSAGE_REGISTER( MPAllTauntsLocked ); #endif // #ifdef CLIENT_DLL /////////////////////////////////////////////////////////////////////// // Portal multiplayer specific global vscript functions /////////////////////////////////////////////////////////////////////// void CC_DumpCompletionData( const CCommand &args ) { if ( !PortalMPGameRules() ) return; #if !defined ( CLIENT_DLL ) DevMsg( "Dump Server Completion Data\n"); #else DevMsg( "Dump Client Completion Data\n"); #endif DevMsg( "B\tL\tP1\tP2\n"); for ( int nBranch = 0; nBranch < MAX_PORTAL2_COOP_BRANCHES; ++nBranch ) { DevMsg( "------------\n"); for ( int nLevel = 0; nLevel < MAX_PORTAL2_COOP_LEVELS_PER_BRANCH; ++nLevel ) { DevMsg( "%i\t%i\t%i\t%i\n", nBranch, nLevel, ( PortalMPGameRules()->IsPlayerLevelInBranchComplete( 0, nBranch, nLevel ) ? 1 : 0 ), ( PortalMPGameRules()->IsPlayerLevelInBranchComplete( 1, nBranch, nLevel ) ? 1 : 0 ) ); } } DevMsg( "------------\n\n"); } ConCommand mp_dump_completion_data( #if !defined ( CLIENT_DLL ) "mp_dump_server_completion_data", #else "mp_dump_client_completion_data", #endif CC_DumpCompletionData, "Prints player completion data for all maps.", 0 ); #if !defined ( CLIENT_DLL ) void CC_EarnTaunt( const CCommand &args ) { bool bAwardSilently = false; const char *pNewTaunt = "new"; if ( args.ArgC() >= 2 ) { pNewTaunt = args[ 1 ]; } if ( args.ArgC() >= 3 ) { int nSilent = Q_atoi( args[ 2 ] ); if ( nSilent >= 1 ) { bAwardSilently = true; //DevMsg( "Awarding taunt %s silently ------------\n", pNewTaunt); } } CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); UserMessageBegin( player, "MPTauntEarned" ); WRITE_STRING( pNewTaunt ); WRITE_BOOL( bAwardSilently ); MessageEnd(); } ConCommand mp_earn_taunt( "mp_earn_taunt", CC_EarnTaunt, "Unlocks, owns, and puts a taunt in the gesture wheel.", 0 ); void CC_LockTaunt( const CCommand &args ) { if ( args.ArgC() < 2 ) { return; } CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); UserMessageBegin( player, "MPTauntLocked" ); WRITE_STRING( args[ 1 ] ); MessageEnd(); } ConCommand mp_lock_taunt( "mp_lock_taunt", CC_LockTaunt, "Locks a taunt and removes it from the gesture wheel.", 0 ); void CC_LockAllTaunts( const CCommand &args ) { CReliableBroadcastRecipientFilter player; player.AddAllPlayers(); UserMessageBegin( player, "MPAllTauntsLocked" ); MessageEnd(); } ConCommand mp_lock_all_taunts( "mp_lock_all_taunts", CC_LockAllTaunts, "Locks all available taunts and removes them from the gesture wheel.", 0 ); /* void CC_MP_Gib_All_Bots( void ) { if ( !GameRules()->IsMultiplayer() ) { Warning( "This command only works in multiplayer coop." ); return; } for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CPortal_Player *pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer && pPlayer->IsAlive() ) { pPlayer->TakeDamage( CTakeDamageInfo( pPlayer, pPlayer, 1000, DMG_CRUSH ) ); } } } // TODO: make this a cheat once the maps all use the script function call static ConCommand mp_gib_all_bots("mp_gib_all_bots", CC_MP_Gib_All_Bots, "Kills all bots doing CRUSH damage which kills them.", 0 ); */ static bool ScriptIsMultiplayer( void ) { return true; //g_pGameRules->IsMultiplayer(); } float GetPlayerSilenceDuration( int nPlayer ) { return PlayerVoiceListener().GetPlayerSilenceDuration( nPlayer ); } int GetTeamPlayerByIndex( int nTeamNum ) { CTeam *pTeam = GetGlobalTeam( nTeamNum ); Assert( pTeam ); if ( pTeam == NULL ) return -1; for ( int i = 0; i < pTeam->GetNumPlayers(); i++ ) { CBasePlayer *player = pTeam->GetPlayer( i ); if ( player == NULL ) continue; return player->entindex(); } return -1; } int GetOrangePlayerIndex( void ) { return GetTeamPlayerByIndex( TEAM_RED ); } int GetBluePlayerIndex( void ) { return GetTeamPlayerByIndex( TEAM_BLUE ); } int GetCoopSectionIndex( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetCoopSection(); } int GetCoopBranchLevelIndex( int nBranch ) { //int nBranch = 0; CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetCoopBranchLevel( nBranch - 1 ); } int GetHighestActiveBranch( void ) { //int nBranch = 0; CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetActiveBranches(); } void AddBranchLevelName( int nBranch, const char *pchName ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->AddBranchLevel( nBranch - 1, pchName ); } void SaveMPStatsData( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->SaveMPStats(); } void MarkMapComplete( const char *pchName ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->SetMapComplete( pchName ); } bool IsBranchComplete( int nBranch ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->IsFullBranchComplete( nBranch ); } bool IsPlayerBranchComplete( int nPlayer, int nBranch ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->IsPlayerFullBranchComplete( nPlayer, nBranch ); } int CoopGetLevelsCompletedThisBranch() { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetLevelsCompletedThisBranch(); } int CoopGetBranchTotalLevelCount( int nBranch ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetBranchTotalLevelCount( nBranch ); } bool IsLevelComplete( int nBranch, int nLevel ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->IsLevelInBranchComplete( nBranch, nLevel ); } bool IsPlayerLevelComplete( int nPlayer, int nBranch, int nLevel ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->IsPlayerLevelInBranchComplete( nPlayer, nBranch, nLevel ); } void AddCoopCreditsName( const char *pchName ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->AddCreditsName( pchName ); } int GetPlayerDeathCount( int nPlayer ) { return ( nPlayer == 1 ? GlobalEntity_GetCounter( MAKE_STRING( "player_orange_deaths" ) ) : GlobalEntity_GetCounter( MAKE_STRING( "player_blue_deaths" ) ) ); } int GetGladosSpokenFlags( int nBatch ) { if ( nBatch < 0 || nBatch >= 4 ) { DevWarning( "GetGLaDOSSpoteFlags out of range!\n" ); return 0; } char szName[ 32 ]; V_snprintf( szName, sizeof( szName ), "glados_spoken_flags%i", nBatch ); return GlobalEntity_GetFlags( MAKE_STRING( szName ) ); } void AddGladosSpokenFlags( int nBatch, int nFlags ) { if ( nBatch < 0 || nBatch >= 4 ) { DevWarning( "AddGLaDOSSpoteFlags out of range!\n" ); return; } char szName[ 32 ]; V_snprintf( szName, sizeof( szName ), "glados_spoken_flags%i", nBatch ); GlobalEntity_AddFlags( MAKE_STRING( szName ), nFlags ); } bool IsLocalSplitScreen( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; CPortal_Player *pPlayer = NULL; for( int i = 1; i <= gpGlobals->maxClients; ++i ) { pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); //If the other player does not exist or if the other player is the local player if ( pPlayer ) return pPlayer->GetSplitScreenPlayers().Count() > 0; } return 0; } int GetNumPlayersConnected( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; CPortal_Player *pPlayer = NULL; int nNum = 0; for( int i = 1; i <= gpGlobals->maxClients; ++i ) { pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); //count this player if ( pPlayer ) nNum++; } return nNum; } void CoopGladosBlowUpBots( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) { Warning( "This command only works in multiplayer coop." ); return; } for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CPortal_Player *pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer && pPlayer->IsAlive() ) { pPlayer->TakeDamage( CTakeDamageInfo( pPlayer, pPlayer, 1000, DMG_CRUSH ) ); } } pRules->SetGladosJustBlewUpBots(); } int CoopGetNumPortalsPlaced( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return 0; return pRules->GetNumPortalsPlaced(); } void CoopSetMapRunTime( float flRunLength ) { CPortalMPStats *pStats = GetPortalMPStats(); if ( pStats ) { pStats->SetTimeToCompleteMap( flRunLength ); } } void NotifySpeedRunSuccess( int iRunLength, const char* mapname ) { KeyValues *kvNotifySpeedRunCoop = new KeyValues( "OnSpeedRunCoopEvent" ); kvNotifySpeedRunCoop->SetString( "map", mapname ); UTIL_SendClientCommandKVToPlayer( kvNotifySpeedRunCoop ); } void CoopSetCameFromLastDLCMap( bool bComingFromLastDLCMap ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; if( bComingFromLastDLCMap ) GlobalEntity_SetFlags( "came_from_last_dlc_map", 1 ); else GlobalEntity_SetFlags( "came_from_last_dlc_map", 0 ); } bool GetCameFromLastDLCMap() { if( (GlobalEntity_GetFlags( "came_from_last_dlc_map" ) & 1) != 0 ) return true; return false; } void SetHaveSeenDLCTubesReveal( void ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; GlobalEntity_SetFlags( "have_seen_dlc_tubes_reveal", 1 ); } bool GetHaveSeenDLCTubesReveal() { if( (GlobalEntity_GetFlags( "have_seen_dlc_tubes_reveal" ) & 1) != 0 ) return true; return false; } void CC_MarkAllMapsComplete( const CCommand &args ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->SetAllMapsComplete(); DevMsg( "All Maps Unlocked!\n" ); } ConCommand mp_mark_all_maps_complete( "mp_mark_all_maps_complete", CC_MarkAllMapsComplete, "Marks all levels as complete in the save file.", 0 ); void CC_MarkAllMapsIncomplete( const CCommand &args ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; pRules->SetAllMapsComplete( false ); DevMsg( "All Maps Locked!\n" ); } ConCommand mp_mark_all_maps_incomplete( "mp_mark_all_maps_incomplete", CC_MarkAllMapsIncomplete, "Marks all levels as incomplete in the save file.", 0 ); void CC_MarkBranchComplete( const CCommand &args ) { CPortalMPGameRules *pRules = PortalMPGameRules(); if ( !pRules ) return; if ( args.ArgC() <= 1 ) return; pRules->SetBranchComplete( atoi(args[ 1 ]), true ); DevMsg( "Branch Unlocked!\n" ); } ConCommand mp_mark_course_complete( "mp_mark_course_complete", CC_MarkBranchComplete, "Marks all levels in a branch as complete in the save file.", 0 ); void CPortalMPGameRules::RegisterScriptFunctions( void ) { ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsMultiplayer, "IsMultiplayer", "Is this a multiplayer game?" ); ScriptRegisterFunction( g_pScriptVM, GetPlayerSilenceDuration, "Time that the specified player has been silent on the mic." ); ScriptRegisterFunction( g_pScriptVM, GetOrangePlayerIndex, "Player index of the orange player." ); ScriptRegisterFunction( g_pScriptVM, GetBluePlayerIndex, "Player index of the blue player." ); ScriptRegisterFunction( g_pScriptVM, GetCoopSectionIndex, "Section that the coop players have selected to load." ); ScriptRegisterFunction( g_pScriptVM, GetCoopBranchLevelIndex, "Given the 'branch' argument, returns the current chosen level." ); ScriptRegisterFunction( g_pScriptVM, GetHighestActiveBranch, "Returns which branches should be available in the hub." ); ScriptRegisterFunction( g_pScriptVM, AddBranchLevelName, "Adds a level to the specified branche's list." ); ScriptRegisterFunction( g_pScriptVM, SaveMPStatsData, "Save the multiplayer stats for the score board." ); ScriptRegisterFunction( g_pScriptVM, MarkMapComplete, "Marks a maps a complete for both players." ); ScriptRegisterFunction( g_pScriptVM, IsBranchComplete, "Returns true if every level in the branch has been completed by either." ); ScriptRegisterFunction( g_pScriptVM, IsPlayerBranchComplete, "Returns true if every level in the branch has been completed by the specified player." ); ScriptRegisterFunction( g_pScriptVM, IsLevelComplete, "Returns true if the level in the specified branch is completed by either player." ); ScriptRegisterFunction( g_pScriptVM, IsPlayerLevelComplete, "Returns true if the level in the specified branch is completed by a specific player." ); ScriptRegisterFunction( g_pScriptVM, GetPlayerDeathCount, "Returns the number of times that a specific player has died in the session." ); ScriptRegisterFunction( g_pScriptVM, GetGladosSpokenFlags, "Returns bit flags for specific lines that we want to track per session." ); ScriptRegisterFunction( g_pScriptVM, AddGladosSpokenFlags, "Adds bit flags for specific lines that we want to track per session." ); ScriptRegisterFunction( g_pScriptVM, IsLocalSplitScreen, "Are these players playing in Splitscreen?" ); ScriptRegisterFunction( g_pScriptVM, GetNumPlayersConnected, "Returns how many players are connected" ); ScriptRegisterFunction( g_pScriptVM, AddCoopCreditsName, "Adds a name to the coop credit's list." ); ScriptRegisterFunction( g_pScriptVM, CoopGladosBlowUpBots, "Call this to blow up both robots and prevent respawning!" ); ScriptRegisterFunction( g_pScriptVM, CoopGetNumPortalsPlaced, "Returns the number of portals the players have placed so far." ); ScriptRegisterFunction( g_pScriptVM, CoopGetLevelsCompletedThisBranch, "Returns the number of levels the players have completed in their run of the current branch." ); ScriptRegisterFunction( g_pScriptVM, CoopGetBranchTotalLevelCount, "Returns the number of levels in the current branch." ); ScriptRegisterFunction( g_pScriptVM, NotifySpeedRunSuccess, "Tells the client that a successful speed run has been completed." ); ScriptRegisterFunction( g_pScriptVM, CoopSetMapRunTime, "Sets the time to complete a coop map from spawn to completing the puzzle." ); ScriptRegisterFunction( g_pScriptVM, CoopSetCameFromLastDLCMap, "Set whether we came from the last coop DLC map or not" ); ScriptRegisterFunction( g_pScriptVM, GetCameFromLastDLCMap, "Returns true if coming from the last DLC coop map." ); ScriptRegisterFunction( g_pScriptVM, SetHaveSeenDLCTubesReveal, "Set that we have seen the DLC tubes reveal this session." ); ScriptRegisterFunction( g_pScriptVM, GetHaveSeenDLCTubesReveal, "Get whether we have seen the DLC tubes reveal this session." ); g_pScriptVM->RegisterInstance( &PlayerVoiceListener(), "PlayerVoiceListener" ); } #endif // !CLIENT_DLL #ifdef CLIENT_DLL bool ClientIsCrossplayingWithConsole( void ) { IMatchSession *pSession = g_pMatchFramework->GetMatchSession(); if ( !pSession ) return false; KeyValues *kvSettings = pSession->GetSessionSettings(); int numMachines = kvSettings->GetInt( "members/numMachines" ); for ( int iMachine = 0; iMachine < numMachines; ++ iMachine ) { uint64 uiFlags = kvSettings->GetUint64( CFmtStr( "members/machine%d/flags", iMachine ) ); static const uint64 kFlagMachinePS3 = 1ull; if ( uiFlags & kFlagMachinePS3 ) return true; } return false; } #endif