summaryrefslogtreecommitdiff
path: root/game/server/portal
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/portal')
-rw-r--r--game/server/portal/PhysicsCloneArea.cpp252
-rw-r--r--game/server/portal/PhysicsCloneArea.h51
-rw-r--r--game/server/portal/Portal_CustomStatsVisualizer.cpp302
-rw-r--r--game/server/portal/cbaseanimatingprojectile.cpp83
-rw-r--r--game/server/portal/cbaseanimatingprojectile.h43
-rw-r--r--game/server/portal/env_lightrail_endpoint.cpp190
-rw-r--r--game/server/portal/env_portal_credits.cpp186
-rw-r--r--game/server/portal/env_portal_path_track.cpp218
-rw-r--r--game/server/portal/func_liquidportal.cpp344
-rw-r--r--game/server/portal/func_liquidportal.h65
-rw-r--r--game/server/portal/func_noportal_volume.cpp130
-rw-r--r--game/server/portal/func_noportal_volume.h56
-rw-r--r--game/server/portal/func_portal_bumper.cpp103
-rw-r--r--game/server/portal/func_portal_detector.cpp139
-rw-r--r--game/server/portal/func_portal_detector.h49
-rw-r--r--game/server/portal/func_portal_orientation.cpp167
-rw-r--r--game/server/portal/func_portal_orientation.h51
-rw-r--r--game/server/portal/neurotoxin_countdown.cpp306
-rw-r--r--game/server/portal/npc_portal_turret_floor.cpp1532
-rw-r--r--game/server/portal/npc_portal_turret_ground.cpp255
-rw-r--r--game/server/portal/npc_rocket_turret.cpp1413
-rw-r--r--game/server/portal/npc_security_camera.cpp1167
-rw-r--r--game/server/portal/physicsshadowclone.cpp1091
-rw-r--r--game/server/portal/physicsshadowclone.h138
-rw-r--r--game/server/portal/portal_client.cpp176
-rw-r--r--game/server/portal/portal_gamestats.cpp683
-rw-r--r--game/server/portal/portal_gamestats.h139
-rw-r--r--game/server/portal/portal_mp_client.cpp199
-rw-r--r--game/server/portal/portal_physics_collisionevent.cpp486
-rw-r--r--game/server/portal/portal_physics_collisionevent.h32
-rw-r--r--game/server/portal/portal_placement.cpp1337
-rw-r--r--game/server/portal/portal_placement.h25
-rw-r--r--game/server/portal/portal_player.cpp2333
-rw-r--r--game/server/portal/portal_player.h253
-rw-r--r--game/server/portal/portal_radio.cpp618
-rw-r--r--game/server/portal/prop_energy_ball.cpp620
-rw-r--r--game/server/portal/prop_glados_core.cpp480
-rw-r--r--game/server/portal/prop_portal.cpp2315
-rw-r--r--game/server/portal/prop_portal.h166
-rw-r--r--game/server/portal/prop_portal_stats_display.cpp478
-rw-r--r--game/server/portal/prop_telescopic_arm.cpp538
-rw-r--r--game/server/portal/trigger_portal_cleanser.cpp269
-rw-r--r--game/server/portal/weapon_physcannon.cpp4934
-rw-r--r--game/server/portal/weapon_physcannon.h41
-rw-r--r--game/server/portal/weapon_portalgun.cpp736
-rw-r--r--game/server/portal/weapon_portalgun.h141
46 files changed, 25330 insertions, 0 deletions
diff --git a/game/server/portal/PhysicsCloneArea.cpp b/game/server/portal/PhysicsCloneArea.cpp
new file mode 100644
index 0000000..620bd8e
--- /dev/null
+++ b/game/server/portal/PhysicsCloneArea.cpp
@@ -0,0 +1,252 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Instead of cloning all physics objects in a level to get proper
+// near-portal reactions, only clone from a larger area near portals.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "PhysicsCloneArea.h"
+#include "prop_portal.h"
+#include "portal_shareddefs.h"
+#include "collisionutils.h"
+#include "env_debughistory.h"
+
+LINK_ENTITY_TO_CLASS( physicsclonearea, CPhysicsCloneArea );
+
+
+#define PHYSICSCLONEAREASCALE 4.0f
+
+const Vector CPhysicsCloneArea::vLocalMins( 3.0f,
+ -PORTAL_HALF_WIDTH * PHYSICSCLONEAREASCALE,
+ -PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE );
+const Vector CPhysicsCloneArea::vLocalMaxs( PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE, //x is the forward which is fairly thin for portals, replacing with halfheight
+ PORTAL_HALF_WIDTH * PHYSICSCLONEAREASCALE,
+ PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE );
+
+extern ConVar sv_portal_debug_touch;
+
+void CPhysicsCloneArea::StartTouch( CBaseEntity *pOther )
+{
+ if( !m_bActive )
+ return;
+
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "PortalCloneArea %i Start Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
+ }
+#if !defined( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PortalCloneArea %i Start Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
+ }
+#endif
+
+ m_pAttachedSimulator->StartCloningEntity( pOther );
+}
+
+void CPhysicsCloneArea::Touch( CBaseEntity *pOther )
+{
+ if( !m_bActive )
+ return;
+
+ //TODO: Planar checks to see if it's a better idea to reclone/unclone
+
+}
+
+void CPhysicsCloneArea::EndTouch( CBaseEntity *pOther )
+{
+ if( !m_bActive )
+ return;
+
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "PortalCloneArea %i End Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
+ }
+#if !defined( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PortalCloneArea %i End Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
+ }
+#endif
+
+ m_pAttachedSimulator->StopCloningEntity( pOther );
+}
+
+void CPhysicsCloneArea::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ Assert( m_pAttachedPortal );
+
+ AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW | EF_NODRAW );
+
+ SetSolid( SOLID_OBB );
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ SetCollisionGroup( COLLISION_GROUP_PLAYER );
+
+ SetSize( vLocalMins, vLocalMaxs );
+}
+
+void CPhysicsCloneArea::Activate( void )
+{
+ BaseClass::Activate();
+}
+
+int CPhysicsCloneArea::ObjectCaps( void )
+{
+ return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; //don't save this entity in any way, we naively recreate them
+}
+
+
+void CPhysicsCloneArea::UpdatePosition( void )
+{
+ Assert( m_pAttachedPortal );
+
+ //untouch everything we're touching
+ touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
+ if( root )
+ {
+ //don't want to risk list corruption while untouching
+ CUtlVector<CBaseEntity *> TouchingEnts;
+ for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
+ TouchingEnts.AddToTail( link->entityTouched );
+
+
+ for( int i = TouchingEnts.Count(); --i >= 0; )
+ {
+ CBaseEntity *pTouch = TouchingEnts[i];
+
+ pTouch->PhysicsNotifyOtherOfUntouch( pTouch, this );
+ PhysicsNotifyOtherOfUntouch( this, pTouch );
+ }
+ }
+
+ SetAbsOrigin( m_pAttachedPortal->GetAbsOrigin() );
+ SetAbsAngles( m_pAttachedPortal->GetAbsAngles() );
+ m_bActive = m_pAttachedPortal->m_bActivated;
+
+ //NDebugOverlay::EntityBounds( this, 0, 0, 255, 25, 5.0f );
+
+ //RemoveFlag( FL_DONTTOUCH );
+ CloneNearbyEntities(); //wake new objects so they can figure out that they touch
+}
+
+void CPhysicsCloneArea::CloneNearbyEntities( void )
+{
+ CBaseEntity* pList[ 1024 ];
+
+ Vector vForward, vUp, vRight;
+ GetVectors( &vForward, &vRight, &vUp );
+
+ Vector ptOrigin = GetAbsOrigin();
+ QAngle qAngles = GetAbsAngles();
+
+ Vector ptOBBStart = ptOrigin;
+ ptOBBStart += vForward * vLocalMins.x;
+ ptOBBStart += vRight * vLocalMins.y;
+ ptOBBStart += vUp * vLocalMins.z;
+
+
+ vForward *= vLocalMaxs.x - vLocalMins.x;
+ vRight *= vLocalMaxs.y - vLocalMins.y;
+ vUp *= vLocalMaxs.z - vLocalMins.z;
+
+
+ Vector vAABBMins, vAABBMaxs;
+ vAABBMins = vAABBMaxs = ptOBBStart;
+
+ for( int i = 1; i != 8; ++i )
+ {
+ Vector ptTest = ptOBBStart;
+ if( i & (1 << 0) ) ptTest += vForward;
+ if( i & (1 << 1) ) ptTest += vRight;
+ if( i & (1 << 2) ) ptTest += vUp;
+
+ if( ptTest.x < vAABBMins.x ) vAABBMins.x = ptTest.x;
+ if( ptTest.y < vAABBMins.y ) vAABBMins.y = ptTest.y;
+ if( ptTest.z < vAABBMins.z ) vAABBMins.z = ptTest.z;
+ if( ptTest.x > vAABBMaxs.x ) vAABBMaxs.x = ptTest.x;
+ if( ptTest.y > vAABBMaxs.y ) vAABBMaxs.y = ptTest.y;
+ if( ptTest.z > vAABBMaxs.z ) vAABBMaxs.z = ptTest.z;
+ }
+
+
+ /*{
+ Vector ptAABBCenter = (vAABBMins + vAABBMaxs) * 0.5f;
+ Vector vAABBExtent = (vAABBMaxs - vAABBMins) * 0.5f;
+ NDebugOverlay::Box( ptAABBCenter, -vAABBExtent, vAABBExtent, 0, 0, 255, 128, 10.0f );
+ }*/
+
+
+ int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
+ trace_t tr;
+ UTIL_ClearTrace( tr );
+
+
+ //Iterate over all the possible targets
+ for ( int i = 0; i < count; i++ )
+ {
+ CBaseEntity *pEntity = pList[i];
+
+ if ( pEntity && (pEntity != this) )
+ {
+ IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
+
+ if( pPhysicsObject )
+ {
+ CCollisionProperty *pEntCollision = pEntity->CollisionProp();
+ Vector ptEntityCenter = pEntCollision->GetCollisionOrigin();
+
+ //double check intersection at the OBB vs OBB level, we don't want to affect large piles of physics objects if we don't have to, it gets slow
+ if( IsOBBIntersectingOBB( ptOrigin, qAngles, vLocalMins, vLocalMaxs,
+ ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
+ {
+ tr.endpos = (ptOrigin + ptEntityCenter) * 0.5;
+ PhysicsMarkEntitiesAsTouching( pEntity, tr );
+ //StartTouch( pEntity );
+
+ //pEntity->WakeRestingObjects();
+ //pPhysicsObject->Wake();
+ }
+ }
+ }
+ }
+}
+
+void CPhysicsCloneArea::CloneTouchingEntities( void )
+{
+ if( m_pAttachedPortal && m_pAttachedPortal->m_bActivated )
+ {
+ touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
+ if( root )
+ {
+ for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
+ m_pAttachedSimulator->StartCloningEntity( link->entityTouched );
+ }
+ }
+}
+
+
+
+
+
+CPhysicsCloneArea *CPhysicsCloneArea::CreatePhysicsCloneArea( CProp_Portal *pFollowPortal )
+{
+ if( !pFollowPortal )
+ return NULL;
+
+ CPhysicsCloneArea *pCloneArea = (CPhysicsCloneArea *)CreateEntityByName( "physicsclonearea" );
+
+ pCloneArea->m_pAttachedPortal = pFollowPortal;
+ pCloneArea->m_pAttachedSimulator = &pFollowPortal->m_PortalSimulator;
+
+ DispatchSpawn( pCloneArea );
+
+ pCloneArea->UpdatePosition();
+
+ return pCloneArea;
+}
+
diff --git a/game/server/portal/PhysicsCloneArea.h b/game/server/portal/PhysicsCloneArea.h
new file mode 100644
index 0000000..e09b5c2
--- /dev/null
+++ b/game/server/portal/PhysicsCloneArea.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef PHYSICSCLONEAREA_H
+#define PHYSICSCLONEAREA_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseentity.h"
+
+class CProp_Portal;
+class CPortalSimulator;
+
+class CPhysicsCloneArea : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CPhysicsCloneArea, CBaseEntity );
+
+ static const Vector vLocalMins;
+ static const Vector vLocalMaxs;
+
+ virtual void StartTouch( CBaseEntity *pOther );
+ virtual void Touch( CBaseEntity *pOther );
+ virtual void EndTouch( CBaseEntity *pOther );
+
+ virtual void Spawn( void );
+ virtual void Activate( void );
+
+ virtual int ObjectCaps( void );
+ void UpdatePosition( void );
+
+ void CloneTouchingEntities( void );
+ void CloneNearbyEntities( void );
+ static CPhysicsCloneArea *CreatePhysicsCloneArea( CProp_Portal *pFollowPortal );
+private:
+
+ CProp_Portal *m_pAttachedPortal;
+ CPortalSimulator *m_pAttachedSimulator;
+ bool m_bActive;
+
+
+};
+
+#endif //#ifndef PHYSICSCLONEAREA_H
+
diff --git a/game/server/portal/Portal_CustomStatsVisualizer.cpp b/game/server/portal/Portal_CustomStatsVisualizer.cpp
new file mode 100644
index 0000000..b2bbe40
--- /dev/null
+++ b/game/server/portal/Portal_CustomStatsVisualizer.cpp
@@ -0,0 +1,302 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "filesystem.h"
+#include "portal_gamestats.h"
+#include "util_shared.h"
+#include "prop_portal_shared.h"
+#include "utlstring.h"
+
+
+#ifdef PORTAL_GAMESTATS_VERBOSE //this shouldn't be in release versions, and most loading/saving code is only enabled with this defined
+
+static ConVar portalstats_visdeaths( "portalstats_visdeaths", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
+static ConVar portalstats_visjumps( "portalstats_visjumps", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
+static ConVar portalstats_visusages( "portalstats_visusages", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
+static ConVar portalstats_visstucks( "portalstats_visstucks", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
+static ConVar portalstats_visplacement( "portalstats_visplacement", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+class CPortalGameStatsVisualizer : public CPortalGameStats, public CAutoGameSystemPerFrame
+{
+public:
+ float m_fRefreshTimer;
+ void FrameUpdatePostEntityThink();
+ void LevelInitPreEntity();
+ void PrepareLevelData( void );
+
+ CUtlVector<QAngle> m_PortalPlacementAngles;
+
+ CPortalGameStatsVisualizer( void )
+ : m_fRefreshTimer( 0.0f )
+ { };
+};
+CPortalGameStatsVisualizer s_GameStatsVisualizer;
+
+void CPortalGameStatsVisualizer::LevelInitPreEntity()
+{
+ m_fRefreshTimer = gpGlobals->curtime;
+ PrepareLevelData();
+}
+
+void CPortalGameStatsVisualizer::PrepareLevelData( void )
+{
+ m_pCurrentMapStats = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
+
+ m_PortalPlacementAngles.SetSize( m_pCurrentMapStats->m_pPlacements->Count() );
+
+ for( int i = m_pCurrentMapStats->m_pPlacements->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::PortalPlacement_t &PlacementStat = m_pCurrentMapStats->m_pPlacements->Element( i );
+
+ trace_t tr;
+
+ // Trace to see where the portal hit
+ Vector vDirection = PlacementStat.ptPlacementPosition - PlacementStat.ptPlayerFiredFrom;
+
+ UTIL_TraceLine( PlacementStat.ptPlayerFiredFrom, PlacementStat.ptPlayerFiredFrom + (vDirection * 100000.0f), MASK_SHOT_PORTAL, NULL, &tr );
+
+ Vector vUp( 0.0f, 0.0f, 1.0f );
+ if( ( tr.plane.normal.x > -0.001f && tr.plane.normal.x < 0.001f ) && ( tr.plane.normal.y > -0.001f && tr.plane.normal.y < 0.001f ) )
+ {
+ //plane is a level floor/ceiling
+ vUp = vDirection;
+ }
+
+ VectorAngles( tr.plane.normal, vUp, m_PortalPlacementAngles[i] );
+ }
+}
+
+#define PORTALSTATSVISUALIZER_REFRESHTIME 1.0f
+#define PORTALSTATSVISUALIZER_DISPLAYTIME (PORTALSTATSVISUALIZER_REFRESHTIME + 0.05f)
+
+void CPortalGameStatsVisualizer::FrameUpdatePostEntityThink()
+{
+ if( m_fRefreshTimer > gpGlobals->curtime )
+ return;
+
+ //refresh now
+ m_fRefreshTimer = gpGlobals->curtime + PORTALSTATSVISUALIZER_REFRESHTIME;
+
+ static const Vector vPlayerBoxMins( -16.0f, -16.0f, 0.0f ), vPlayerBoxMaxs( 16.0f, 16.0f, 72.0f ), vPlayerTextOffset( 0.0f, 0.0f, 36.0f );
+ static const Vector vSmallBoxMins( -7.5f, -7.5f, -7.5f ), vSmallBoxMaxs( 7.5f, 7.5f, 7.5f );
+
+ if( portalstats_visdeaths.GetBool() )
+ {
+ for( int i = m_pCurrentMapStats->m_pDeaths->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::PlayerDeaths_t &DeathStat = m_pCurrentMapStats->m_pDeaths->Element( i );
+ NDebugOverlay::Box( DeathStat.ptPositionOfDeath, vPlayerBoxMins, vPlayerBoxMaxs, 255, 0, 0, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Text( DeathStat.ptPositionOfDeath + vPlayerTextOffset, DeathStat.szAttackerClassName, true, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ }
+ }
+
+ if( portalstats_visjumps.GetBool() )
+ {
+ for( int i = m_pCurrentMapStats->m_pJumps->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::JumpEvent_t &JumpStat = m_pCurrentMapStats->m_pJumps->Element( i );
+
+ NDebugOverlay::Box( JumpStat.ptPlayerPositionAtJumpStart, vPlayerBoxMins, vPlayerBoxMaxs, 0, 255, 0, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Line( JumpStat.ptPlayerPositionAtJumpStart + vPlayerTextOffset, JumpStat.ptPlayerPositionAtJumpStart + vPlayerTextOffset + (JumpStat.vPlayerVelocityAtJumpStart), 0, 255, 0, false, PORTALSTATSVISUALIZER_REFRESHTIME );
+ }
+ }
+
+ if( portalstats_visusages.GetBool() )
+ {
+ for( int i = m_pCurrentMapStats->m_pUseEvents->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::PlayerUse_t &UseEvent = m_pCurrentMapStats->m_pUseEvents->Element( i );
+
+ NDebugOverlay::Box( UseEvent.ptTraceStart, vSmallBoxMins, vSmallBoxMaxs, 0, 0, 255, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Line( UseEvent.ptTraceStart, UseEvent.ptTraceStart + (UseEvent.vTraceDelta * 100.0f), 0, 0, 255, false, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Text( UseEvent.ptTraceStart, UseEvent.szUseEntityClassName, true, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ }
+ }
+
+ if( portalstats_visstucks.GetBool() )
+ {
+ for( int i = m_pCurrentMapStats->m_pStuckSpots->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::StuckEvent_t &StuckEvent = m_pCurrentMapStats->m_pStuckSpots->Element( i );
+
+ NDebugOverlay::Box( StuckEvent.ptPlayerPosition, vPlayerBoxMins, vPlayerBoxMaxs, 255, 0, 255, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ }
+ }
+
+ if( portalstats_visplacement.GetBool() )
+ {
+ Assert( m_pCurrentMapStats->m_pPlacements->Count() == m_PortalPlacementAngles.Count() );
+
+ for( int i = m_pCurrentMapStats->m_pPlacements->Count(); --i >= 0; )
+ {
+ Portal_Gamestats_LevelStats_t::PortalPlacement_t &PlacementStat = m_pCurrentMapStats->m_pPlacements->Element( i );
+
+ unsigned char brightnessval;
+ if( PlacementStat.iSuccessCode == 0 )
+ brightnessval = 255;//worked
+ else
+ brightnessval = 64;//failed
+
+ NDebugOverlay::BoxAngles( PlacementStat.ptPlacementPosition, CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs, m_PortalPlacementAngles[i], 0, brightnessval, brightnessval, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Box( PlacementStat.ptPlayerFiredFrom, vSmallBoxMins, vSmallBoxMaxs, 0, brightnessval, brightnessval, 100, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ NDebugOverlay::Line( PlacementStat.ptPlayerFiredFrom, PlacementStat.ptPlacementPosition, 0, brightnessval, brightnessval, false, PORTALSTATSVISUALIZER_DISPLAYTIME );
+ }
+ }
+}
+
+
+static CUtlVector<CUtlString> s_PortalStatsDataFileList;
+static void PortalStats_UpdateFileList( void )
+{
+ s_PortalStatsDataFileList.RemoveAll();
+ FileFindHandle_t fileHandle;
+ const char *pszFileName = filesystem->FindFirst( "customstats/*.dat", &fileHandle );
+
+ while( pszFileName )
+ {
+ // Skip it if it's a directory
+ if( !filesystem->FindIsDirectory( fileHandle ) )
+ {
+ char szRelativeFileName[_MAX_PATH];
+ Q_snprintf( szRelativeFileName, sizeof( szRelativeFileName ), "customstats/%s", pszFileName );
+
+ // Only load files from the current mod's directory hierarchy
+ if( filesystem->FileExists( szRelativeFileName, "MOD" ) )
+ {
+ int index = s_PortalStatsDataFileList.AddToTail();
+ s_PortalStatsDataFileList[index].Set( pszFileName );
+ }
+ }
+
+ pszFileName = filesystem->FindNext( fileHandle );
+ }
+
+ filesystem->FindClose( fileHandle );
+}
+
+static int PortalStats_LoadFile_f_CompletionFunc( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] )
+{
+ PortalStats_UpdateFileList();
+
+ int iRetCount = MIN( s_PortalStatsDataFileList.Count(), COMMAND_COMPLETION_MAXITEMS );
+ for( int i = 0; i != iRetCount; ++i )
+ {
+ Q_snprintf( commands[i], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", partial, s_PortalStatsDataFileList[i].Get() );
+ }
+
+ return iRetCount;
+}
+
+static char s_szCurrentlyLoadedStatsFile[256] = "";
+
+static void PortalStats_LoadFileHelper( const char *szFileName )
+{
+ CUtlBuffer statsfileContents;
+ char szRelativeName[256];
+
+ Q_snprintf( szRelativeName, sizeof( szRelativeName ), "customstats/%s", szFileName );
+
+ if( filesystem->ReadFile( szRelativeName, "MOD", statsfileContents ) )
+ {
+ Q_strncpy( s_szCurrentlyLoadedStatsFile, szFileName, sizeof( s_szCurrentlyLoadedStatsFile ) );
+
+ s_GameStatsVisualizer.Clear();
+ s_GameStatsVisualizer.LoadCustomDataFromBuffer( statsfileContents );
+ s_GameStatsVisualizer.PrepareLevelData();
+ s_GameStatsVisualizer.m_fRefreshTimer = 0.0f; //start drawing new data right away
+ }
+}
+
+static void PortalStats_LoadFile_f( const CCommand &args )
+{
+ if( args.ArgC() > 1 )
+ {
+ PortalStats_LoadFileHelper( args[1] );
+ }
+}
+
+static void PortalStats_LoadNextFile_f( void )
+{
+ PortalStats_UpdateFileList();
+ if( s_PortalStatsDataFileList.Count() == 0 )
+ return;
+
+ int i;
+ for( i = s_PortalStatsDataFileList.Count(); --i >= 0; )
+ {
+ if( Q_stricmp( s_PortalStatsDataFileList[i].Get(), s_szCurrentlyLoadedStatsFile ) == 0 )
+ break;
+ }
+
+ if( i < 0 )
+ {
+ //currently loaded file not found, just load the first
+ PortalStats_LoadFileHelper( s_PortalStatsDataFileList[0] );
+ }
+ else
+ {
+ ++i;
+ if( i == s_PortalStatsDataFileList.Count() )
+ {
+ DevMsg( "PortalStats_LoadNextFile looping to first file in directory.\n" );
+ NDebugOverlay::ScreenText( 0.3f, 0.5f, "PortalStats_LoadNextFile looping to first file in directory.", 255, 255, 255, 255, 2.0f );
+ i = 0;
+ }
+
+ PortalStats_LoadFileHelper( s_PortalStatsDataFileList[i] );
+ }
+}
+
+static void PortalStats_LoadPrevFile_f( void )
+{
+ PortalStats_UpdateFileList();
+ if( s_PortalStatsDataFileList.Count() == 0 )
+ return;
+
+ int i;
+ for( i = s_PortalStatsDataFileList.Count(); --i >= 0; )
+ {
+ if( Q_stricmp( s_PortalStatsDataFileList[i].Get(), s_szCurrentlyLoadedStatsFile ) == 0 )
+ break;
+ }
+
+ if( i < 0 )
+ {
+ //currently loaded file not found, just load the first
+ PortalStats_LoadFileHelper( s_PortalStatsDataFileList[0] );
+ }
+ else
+ {
+ --i;
+ if( i < 0 )
+ {
+ i = s_PortalStatsDataFileList.Count() - 1;
+ DevMsg( "PortalStats_LoadPrevFile looping to last file in directory.\n" );
+ NDebugOverlay::ScreenText( 0.3f, 0.5f, "PortalStats_LoadPrevFile looping to last file in directory.", 255, 255, 255, 255, 2.0f );
+ }
+
+ PortalStats_LoadFileHelper( s_PortalStatsDataFileList[i] );
+ }
+}
+
+//static ConCommand Portal_LoadCustomStatsFile("portal_loadcustomstatsfile", Portal_LoadCustomStatsFile_f, "Load a custom stats file for visualizing.", FCVAR_DONTRECORD, Portal_LoadCustomStatsFile_f_CompletionFunc );
+static ConCommand PortalStats_LoadFile("portalstats_loadfile", PortalStats_LoadFile_f, "Load a custom stats file for visualizing.", FCVAR_DONTRECORD, PortalStats_LoadFile_f_CompletionFunc );
+static ConCommand PortalStats_LoadNextFile("portalstats_loadnextfile", PortalStats_LoadNextFile_f, "Load the next custom stats file for visualizing.", FCVAR_DONTRECORD );
+static ConCommand PortalStats_LoadPrevFile("portalstats_loadprevfile", PortalStats_LoadPrevFile_f, "Load the next custom stats file for visualizing.", FCVAR_DONTRECORD );
+
+
+
+
+
+
+
+
+
+
+
+#endif //#ifdef PORTAL_GAMESTATS_VERBOSE
+
diff --git a/game/server/portal/cbaseanimatingprojectile.cpp b/game/server/portal/cbaseanimatingprojectile.cpp
new file mode 100644
index 0000000..06b5c3f
--- /dev/null
+++ b/game/server/portal/cbaseanimatingprojectile.cpp
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base class for simple projectiles
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "cbaseanimatingprojectile.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+LINK_ENTITY_TO_CLASS( baseanimating_projectile, CBaseAnimatingProjectile );
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CBaseAnimatingProjectile )
+
+ DEFINE_FIELD( m_iDmg, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iDmgType, FIELD_INTEGER ),
+
+END_DATADESC()
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBaseAnimatingProjectile::Spawn( char *pszModel,
+ const Vector &vecOrigin,
+ const Vector &vecVelocity,
+ edict_t *pOwner,
+ MoveType_t iMovetype,
+ MoveCollide_t nMoveCollide,
+ int iDamage,
+ int iDamageType )
+{
+ Precache();
+
+ SetSolid( SOLID_BBOX );
+ SetModel( pszModel );
+
+ UTIL_SetSize( this, vec3_origin, vec3_origin );
+
+ m_iDmg = iDamage;
+ m_iDmgType = iDamageType;
+
+ SetMoveType( iMovetype, nMoveCollide );
+
+ UTIL_SetOrigin( this, vecOrigin );
+ SetAbsVelocity( vecVelocity );
+
+ SetOwnerEntity( Instance( pOwner ) );
+
+ QAngle qAngles;
+ VectorAngles( vecVelocity, qAngles );
+ SetAbsAngles( qAngles );
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBaseAnimatingProjectile::Touch( CBaseEntity *pOther )
+{
+ CBaseEntity *pOwner;
+
+ pOwner = GetOwnerEntity();
+
+ if( !pOwner )
+ {
+ pOwner = this;
+ }
+
+ trace_t tr;
+ tr = BaseClass::GetTouchTrace( );
+
+ CTakeDamageInfo info( this, pOwner, m_iDmg, m_iDmgType );
+ GuessDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
+ pOther->TakeDamage( info );
+
+ UTIL_Remove( this );
+}
diff --git a/game/server/portal/cbaseanimatingprojectile.h b/game/server/portal/cbaseanimatingprojectile.h
new file mode 100644
index 0000000..4744870
--- /dev/null
+++ b/game/server/portal/cbaseanimatingprojectile.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base class for simple projectiles
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CBASEANIMATINGPROJECTILE_H
+#define CBASEANIMATINGPROJECTILE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+enum MoveType_t;
+enum MoveCollide_t;
+
+
+//=============================================================================
+//=============================================================================
+class CBaseAnimatingProjectile : public CBaseAnimating
+{
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CBaseAnimatingProjectile, CBaseAnimating );
+
+public:
+ void Touch( CBaseEntity *pOther );
+
+ void Spawn( char *pszModel,
+ const Vector &vecOrigin,
+ const Vector &vecVelocity,
+ edict_t *pOwner,
+ MoveType_t iMovetype,
+ MoveCollide_t nMoveCollide,
+ int iDamage,
+ int iDamageType );
+
+ virtual void Precache( void ) {};
+
+ int m_iDmg;
+ int m_iDmgType;
+};
+
+#endif // CBASEANIMATINGPROJECTILE_H
diff --git a/game/server/portal/env_lightrail_endpoint.cpp b/game/server/portal/env_lightrail_endpoint.cpp
new file mode 100644
index 0000000..7b35131
--- /dev/null
+++ b/game/server/portal/env_lightrail_endpoint.cpp
@@ -0,0 +1,190 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "env_lightrail_endpoint_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+LINK_ENTITY_TO_CLASS( env_lightrail_endpoint, CEnv_Lightrail_Endpoint );
+
+BEGIN_DATADESC( CEnv_Lightrail_Endpoint )
+ DEFINE_KEYFIELD( m_flSmallScale, FIELD_FLOAT, "small_fx_scale" ),
+ DEFINE_KEYFIELD( m_flLargeScale, FIELD_FLOAT, "large_fx_scale" ),
+ DEFINE_FIELD( m_nState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flDuration, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
+
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "StartCharge", InputStartCharge ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartSmallFX", InputStartSmallFX ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartLargeFX", InputStartLargeFX ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "Stop", InputStop ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CEnv_Lightrail_Endpoint, DT_Env_Lightrail_Endpoint )
+ SendPropFloat( SENDINFO(m_flSmallScale), 0, SPROP_NOSCALE),
+ SendPropFloat( SENDINFO(m_flLargeScale), 0, SPROP_NOSCALE),
+ SendPropInt( SENDINFO(m_nState), 8, SPROP_UNSIGNED),
+ SendPropFloat( SENDINFO(m_flDuration), 0, SPROP_NOSCALE),
+ SendPropFloat( SENDINFO(m_flStartTime), 0, SPROP_NOSCALE),
+ SendPropInt( SENDINFO(m_spawnflags), 0, SPROP_UNSIGNED),
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+// Precache:
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::Precache()
+{
+ BaseClass::Precache();
+ PrecacheMaterial( "effects/light_rail_endpoint" ); //Sprite used for the effects
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::Spawn( void )
+{
+ Precache();
+
+ UTIL_SetSize( this, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );
+
+ // See if we start active
+ if ( HasSpawnFlags( SF_ENDPOINT_START_SMALLFX ) )
+ {
+ m_nState = (int)ENDPOINT_STATE_LARGEFX; //THIS NEEDS TO BE CHANGED TO SMALL FX STATE
+ m_flStartTime = gpGlobals->curtime;
+ }
+
+ // No model but we still need to force this!
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flWarmUpTime -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::StartCharge( float flWarmUpTime )
+{
+ m_nState = (int)ENDPOINT_STATE_CHARGING;
+ m_flDuration = flWarmUpTime;
+ m_flStartTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::StartLargeFX( void )
+{
+ m_nState = (int)ENDPOINT_STATE_LARGEFX;
+ m_flStartTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::StartSmallFX( void )
+{
+ m_nState = (int)ENDPOINT_STATE_SMALLFX;
+ m_flStartTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flCoolDownTime -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::StopLargeFX( float flCoolDownTime )
+{
+ m_nState = (int)ENDPOINT_STATE_OFF;
+ m_flDuration = flCoolDownTime;
+ m_flStartTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flCoolDownTime -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::StopSmallFX( float flCoolDownTime )
+{
+ m_nState = (int)ENDPOINT_STATE_OFF;
+ m_flDuration = flCoolDownTime;
+ m_flStartTime = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::InputStartCharge( inputdata_t &inputdata )
+{
+ StartCharge( inputdata.value.Float() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::InputStartLargeFX( inputdata_t &inputdata )
+{
+ StartLargeFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::InputStartSmallFX( inputdata_t &inputdata )
+{
+ StartSmallFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CEnv_Lightrail_Endpoint::InputStop( inputdata_t &inputdata )
+{
+ StopLargeFX( inputdata.value.Float() );
+}
+
+CBaseViewModel *IsViewModelMoveParent_( CBaseEntity *pEffect )
+{
+ if ( pEffect->GetMoveParent() )
+ {
+ CBaseViewModel *pViewModel = dynamic_cast<CBaseViewModel *>( pEffect->GetMoveParent() );
+
+ if ( pViewModel )
+ {
+ return pViewModel;
+ }
+ }
+
+ return NULL;
+}
+
+int CEnv_Lightrail_Endpoint::UpdateTransmitState( void )
+{
+ if ( IsViewModelMoveParent_( this ) )
+ {
+ return SetTransmitState( FL_EDICT_FULLCHECK );
+ }
+
+ return BaseClass::UpdateTransmitState();
+}
+
+int CEnv_Lightrail_Endpoint::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ CBaseViewModel *pViewModel = IsViewModelMoveParent_( this );
+
+ if ( pViewModel )
+ {
+ return pViewModel->ShouldTransmit( pInfo );
+ }
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
diff --git a/game/server/portal/env_portal_credits.cpp b/game/server/portal/env_portal_credits.cpp
new file mode 100644
index 0000000..79492ed
--- /dev/null
+++ b/game/server/portal/env_portal_credits.cpp
@@ -0,0 +1,186 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "EnvMessage.h"
+#include "engine/IEngineSound.h"
+#include "KeyValues.h"
+#include "filesystem.h"
+#include "Color.h"
+#include "gamestats.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+class CPortalCredits : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CMessage, CPointEntity );
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void InputRollCredits( inputdata_t &inputdata );
+ void InputRollOutroCredits( inputdata_t &inputdata );
+ void InputShowLogo( inputdata_t &inputdata );
+ void InputSetLogoLength( inputdata_t &inputdata );
+ void InputRollPortalOutroCredits( inputdata_t &inputdata );
+
+ COutputEvent m_OnCreditsDone;
+
+ virtual void Precache();
+ virtual void OnRestore();
+private:
+
+ void RollOutroCredits();
+ void RollPortalOutroCredits();
+
+ bool m_bRolledOutroCredits;
+ float m_flLogoLength;
+};
+
+LINK_ENTITY_TO_CLASS( env_portal_credits, CPortalCredits );
+
+BEGIN_DATADESC( CPortalCredits )
+ DEFINE_INPUTFUNC( FIELD_VOID, "RollCredits", InputRollCredits ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RollOutroCredits", InputRollOutroCredits ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RollPortalOutroCredits", InputRollPortalOutroCredits ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ShowLogo", InputShowLogo ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLogoLength", InputSetLogoLength ),
+ DEFINE_OUTPUT( m_OnCreditsDone, "OnCreditsDone"),
+
+ DEFINE_FIELD( m_bRolledOutroCredits, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLogoLength, FIELD_FLOAT )
+END_DATADESC()
+
+void CPortalCredits::Spawn( void )
+{
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+}
+
+static void CreditsDone_f( void )
+{
+ CPortalCredits *pCredits = (CPortalCredits*)gEntList.FindEntityByClassname( NULL, "env_credits" );
+
+ if ( pCredits )
+ {
+ pCredits->m_OnCreditsDone.FireOutput( pCredits, pCredits );
+ }
+}
+
+static ConCommand creditsdone("creditsdone", CreditsDone_f );
+
+extern ConVar sv_unlockedchapters;
+
+
+void CPortalCredits::Precache( void )
+{
+ PrecacheScriptSound( "Portal.song_credits" );
+
+ BaseClass::Precache();
+}
+
+void CPortalCredits::OnRestore()
+{
+ BaseClass::OnRestore();
+
+ if ( m_bRolledOutroCredits )
+ {
+ // Roll them again so that the client .dll will send the "creditsdone" message and we'll
+ // actually get back to the main menu
+ RollOutroCredits();
+ }
+}
+
+void CPortalCredits::RollOutroCredits()
+{
+ sv_unlockedchapters.SetValue( "15" );
+
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "CreditsPortalMsg" );
+ WRITE_BYTE( 3 );
+ MessageEnd();
+}
+
+void CPortalCredits::InputRollOutroCredits( inputdata_t &inputdata )
+{
+ RollOutroCredits();
+
+ // In case we save restore
+ m_bRolledOutroCredits = true;
+
+ gamestats->Event_Credits();
+}
+
+void CPortalCredits::RollPortalOutroCredits()
+{
+ sv_unlockedchapters.SetValue( "15" );
+
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "CreditsPortalMsg" );
+ WRITE_BYTE( 4 );
+ MessageEnd();
+}
+
+void CPortalCredits::InputRollPortalOutroCredits( inputdata_t &inputdata )
+{
+ RollPortalOutroCredits();
+
+ // In case we save restore
+ m_bRolledOutroCredits = true;
+
+ gamestats->Event_Credits();
+}
+
+
+void CPortalCredits::InputShowLogo( inputdata_t &inputdata )
+{
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ if ( m_flLogoLength )
+ {
+ UserMessageBegin( user, "LogoTimeMsg" );
+ WRITE_FLOAT( m_flLogoLength );
+ MessageEnd();
+ }
+ else
+ {
+ UserMessageBegin( user, "CreditsPortalMsg" );
+ WRITE_BYTE( 1 );
+ MessageEnd();
+ }
+}
+
+void CPortalCredits::InputSetLogoLength( inputdata_t &inputdata )
+{
+ m_flLogoLength = inputdata.value.Float();
+}
+
+
+void CPortalCredits::InputRollCredits( inputdata_t &inputdata )
+{
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "CreditsPortalMsg" );
+ WRITE_BYTE( 2 );
+ MessageEnd();
+}
diff --git a/game/server/portal/env_portal_path_track.cpp b/game/server/portal/env_portal_path_track.cpp
new file mode 100644
index 0000000..bfb6f86
--- /dev/null
+++ b/game/server/portal/env_portal_path_track.cpp
@@ -0,0 +1,218 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+#include "env_portal_path_track_shared.h"
+#include "beam_shared.h"
+
+//**********( MODULE CONSTANTS )*************
+
+#define TRACK_FX_WIDTH_ON 4.0f
+#define TRACK_FX_WIDTH_OFF 4.0f
+#define TRACK_FX_BRIGHTNESS_ON 100
+#define TRACK_FX_BRIGHTNESS_OFF 10
+#define TRACK_FX_COLOR_ON 140,235,255
+#define TRACK_FX_COLOR_OFF 235,243,243
+#define TRACK_FX_SCROLL 25.6f
+
+
+ConVar sv_portal_pathtrack_track_width_on ( "sv_portal_pathtrack_track_width_on", "4.0", FCVAR_CHEAT );
+
+//**********( DATA TABLE )*******************
+
+BEGIN_DATADESC( CEnvPortalPathTrack )
+
+ // Data members
+ DEFINE_FIELD( m_bTrackActive, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEndpointActive, FIELD_BOOLEAN ),
+
+ // keyfield data
+// DEFINE_KEYFIELD( m_fScaleEndpoint, FIELD_FLOAT, "End_point_scale" ),
+// DEFINE_KEYFIELD( m_fScaleTrack, FIELD_FLOAT, "Track_beam_scale" ),
+// DEFINE_KEYFIELD( m_fFadeOutEndpoint, FIELD_FLOAT, "End_point_fadeout"),
+// DEFINE_KEYFIELD( m_fFadeInEndpoint, FIELD_FLOAT, "End_point_fadein"),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "ActivateTrackFX", InputActivateTrack ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ActivateEndPointFX", InputActivateEndpoint ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DeactivateTrackFX", InputDeactivateTrack ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DeactivateEndPointFX", InputDeactivateEndpoint ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnActivatedEndpoint, "OnActivateFX"),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CEnvPortalPathTrack, DT_EnvPortalPathTrack )
+
+ SendPropBool( SENDINFO(m_bTrackActive) ),
+ SendPropBool( SENDINFO(m_bEndpointActive) ),
+ SendPropInt ( SENDINFO(m_nState) ),
+
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( env_portal_path_track, CEnvPortalPathTrack );
+
+
+//*********( FUNCTION IMPLEMENTATIONS )***************
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+CEnvPortalPathTrack::CEnvPortalPathTrack()
+{
+ m_bTrackActive = false;
+ m_bEndpointActive = false;
+// m_fScaleEndpoint = 1.0f;
+// m_fScaleTrack = 1.0f;
+}
+
+CEnvPortalPathTrack::~CEnvPortalPathTrack()
+{
+ ShutDownTrackFX(); //Make sure to deallocate the track beam and particle effects
+}
+
+//-----------------------------------------------------------------------------
+// Precache:
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::Precache()
+{
+ BaseClass::Precache();
+ PrecacheMaterial( "effects/combinemuzzle2_dark" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::Spawn( void )
+{
+ Precache();
+ BaseClass::Spawn();
+
+ UTIL_SetSize( this, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );
+
+ // No model but we still need to force this!
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+}
+
+void CEnvPortalPathTrack::Activate( void )
+{
+ BaseClass::Activate(); //Link the happy friends so I know where my next target entity is
+ InitTrackFX(); //Initialize the FX for the track beam
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the track beam and it's partical effects
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InitTrackFX()
+{
+ m_pBeam = CBeam::BeamCreate( "sprites/track_beam.vmt", TRACK_FX_WIDTH_ON ); //Allocate a mr. beamy, and set his scroll sprite and width
+
+ if ( m_pnext )
+ {
+ m_pBeam->PointEntInit( GetAbsOrigin(), m_pnext ); //Set up the beam to draw from its center to it's next track.
+ }
+
+ ActivateTrackFX(); //Set prettiness
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Kills the track beam and it's partical effects
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::ShutDownTrackFX()
+{
+ if ( m_pBeam )
+ {
+ UTIL_Remove( m_pBeam );
+ m_pBeam = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the endpoint particle effects
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InitEndpointFX()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activates the visual effects on the path track between two endpoints
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InputActivateTrack(inputdata_t &inputdata)
+{
+ ActivateTrackFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activates the visual effects on the endpoint
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InputActivateEndpoint(inputdata_t &inputdata)
+{
+ ActivateEndpointFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activates the visual effects on the path track between two endpoints
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InputDeactivateTrack(inputdata_t &inputdata)
+{
+ DeactivateTrackFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activates the visual effects on the endpoint
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::InputDeactivateEndpoint(inputdata_t &inputdata)
+{
+ DeactivateEndpointFX();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate all of the track's beams (at least the ones that are flagged to display)
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::ActivateTrackFX ( void )
+{
+ m_pBeam->SetColor( TRACK_FX_COLOR_ON );
+ m_pBeam->SetScrollRate( (int)TRACK_FX_SCROLL );
+ m_pBeam->SetBrightness( TRACK_FX_BRIGHTNESS_ON );
+ m_pBeam->TurnOff();
+ m_nState = (int)PORTAL_PATH_TRACK_STATE_ACTIVE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate all of the track's beams (at least the ones that are flagged to display)
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::DeactivateTrackFX ( void )
+{
+ m_pBeam->SetColor( TRACK_FX_COLOR_OFF );
+ m_pBeam->SetScrollRate( (int)TRACK_FX_SCROLL );
+ m_pBeam->SetBrightness( TRACK_FX_BRIGHTNESS_OFF );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate all of the endpoint's glowy bits that are flagged to display
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::ActivateEndpointFX ( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate all of the endpoint's glowy bits that are flagged to display
+// Input : -
+//-----------------------------------------------------------------------------
+void CEnvPortalPathTrack::DeactivateEndpointFX ( void )
+{
+} \ No newline at end of file
diff --git a/game/server/portal/func_liquidportal.cpp b/game/server/portal/func_liquidportal.cpp
new file mode 100644
index 0000000..e99043d
--- /dev/null
+++ b/game/server/portal/func_liquidportal.cpp
@@ -0,0 +1,344 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Rising liquid that acts as a one-way portal
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#include "cbase.h"
+#include "func_liquidportal.h"
+#include "portal_player.h"
+#include "isaverestore.h"
+#include "saverestore_utlvector.h"
+
+LINK_ENTITY_TO_CLASS( func_liquidportal, CFunc_LiquidPortal );
+
+BEGIN_DATADESC( CFunc_LiquidPortal )
+ DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bFillInProgress, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fFillStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_fFillEndTime, FIELD_TIME ),
+ DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
+ DEFINE_UTLVECTOR( m_hTeleportList, FIELD_EHANDLE ),
+ DEFINE_UTLVECTOR( m_hLeftToTeleportThisFill, FIELD_EHANDLE ),
+
+ DEFINE_KEYFIELD( m_strInitialLinkedPortal, FIELD_STRING, "InitialLinkedPortal" ),
+ DEFINE_KEYFIELD( m_fFillTime, FIELD_FLOAT, "FillTime" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetLinkedLiquidPortal", InputSetLinkedLiquidPortal ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFillTime", InputSetFillTime ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartFilling", InputStartFilling ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "AddActivatorToTeleportList", InputAddActivatorToTeleportList ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RemoveActivatorFromTeleportList", InputRemoveActivatorFromTeleportList ),
+
+ DEFINE_FUNCTION( CBaseEntity::Think ),
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST( CFunc_LiquidPortal, DT_Func_LiquidPortal )
+ SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
+ SendPropFloat( SENDINFO(m_fFillStartTime) ),
+ SendPropFloat( SENDINFO(m_fFillEndTime) ),
+END_SEND_TABLE()
+
+
+CFunc_LiquidPortal::CFunc_LiquidPortal( void )
+: m_bFillInProgress( false )
+{
+ m_matrixThisToLinked.Identity(); //Zero space is a bad place. No heroes to face, but we need 1's in this case.
+
+}
+
+void CFunc_LiquidPortal::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ SetSolid( SOLID_VPHYSICS );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) );
+
+ CBaseEntity *pBaseEnt = gEntList.FindEntityByName( NULL, STRING(m_strInitialLinkedPortal) );
+ Assert( (pBaseEnt == NULL) || (dynamic_cast<CFunc_LiquidPortal *>(pBaseEnt) != NULL) );
+ SetLinkedLiquidPortal( (CFunc_LiquidPortal *)pBaseEnt );
+ SetThink( &CFunc_LiquidPortal::Think );
+}
+
+void CFunc_LiquidPortal::Activate( void )
+{
+ BaseClass::Activate();
+
+ SetSolid( SOLID_VPHYSICS );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) );
+
+ ComputeLinkMatrix(); //collision origin may have changed during activation
+
+ SetThink( &CFunc_LiquidPortal::Think );
+
+ for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; )
+ {
+ CBaseEntity *pEnt = m_hLeftToTeleportThisFill[i].Get();
+
+ if( pEnt && pEnt->IsPlayer() )
+ {
+ ((CPortal_Player *)pEnt)->m_hSurroundingLiquidPortal = this;
+ }
+ }
+}
+
+int CFunc_LiquidPortal::Save( ISave &save )
+{
+ if( !BaseClass::Save( save ) )
+ return 0;
+
+ save.StartBlock( "LiquidPortal" );
+
+ short iTeleportListCount = m_hTeleportList.Count();
+ save.WriteShort( &iTeleportListCount );
+
+ if( iTeleportListCount != 0 )
+ save.WriteEHandle( m_hTeleportList.Base(), iTeleportListCount );
+
+ short iLeftToTeleportThisFillCount = m_hLeftToTeleportThisFill.Count();
+ save.WriteShort( &iLeftToTeleportThisFillCount );
+
+ if( iLeftToTeleportThisFillCount != 0 )
+ save.WriteEHandle( m_hLeftToTeleportThisFill.Base(), iLeftToTeleportThisFillCount );
+
+ save.EndBlock();
+
+ return 1;
+}
+
+int CFunc_LiquidPortal::Restore( IRestore &restore )
+{
+ m_hTeleportList.RemoveAll();
+ m_hLeftToTeleportThisFill.RemoveAll();
+
+ if( !BaseClass::Restore( restore ) )
+ return 0;
+
+ char szBlockName[SIZE_BLOCK_NAME_BUF];
+ restore.StartBlock( szBlockName );
+
+ if( !FStrEq( szBlockName, "LiquidPortal" ) ) //loading a save without liquid portal save data
+ return 1;
+
+ short iTeleportListCount;
+ restore.ReadShort( &iTeleportListCount );
+
+ if( iTeleportListCount != 0 )
+ {
+ m_hTeleportList.SetCount( iTeleportListCount );
+ restore.ReadEHandle( m_hTeleportList.Base(), iTeleportListCount );
+ }
+
+ short iLeftToTeleportThisFillCount;
+ restore.ReadShort( &iLeftToTeleportThisFillCount );
+
+ if( iLeftToTeleportThisFillCount != 0 )
+ {
+ m_hLeftToTeleportThisFill.SetCount( iLeftToTeleportThisFillCount );
+ restore.ReadEHandle( m_hLeftToTeleportThisFill.Base(), iLeftToTeleportThisFillCount );
+ }
+
+ restore.EndBlock();
+
+ return 1;
+}
+
+
+void CFunc_LiquidPortal::InputSetLinkedLiquidPortal( inputdata_t &inputdata )
+{
+ CBaseEntity *pBaseEnt = gEntList.FindEntityByName( NULL, inputdata.value.String() );
+ Assert( (pBaseEnt == NULL) || (dynamic_cast<CFunc_LiquidPortal *>(pBaseEnt) != NULL) );
+ SetLinkedLiquidPortal( (CFunc_LiquidPortal *)pBaseEnt );
+}
+
+void CFunc_LiquidPortal::InputSetFillTime( inputdata_t &inputdata )
+{
+ m_fFillTime = inputdata.value.Float();
+}
+
+void CFunc_LiquidPortal::InputStartFilling( inputdata_t &inputdata )
+{
+ AssertMsg( m_fFillEndTime <= gpGlobals->curtime, "Fill already in progress." );
+ m_fFillStartTime = gpGlobals->curtime;
+ m_fFillEndTime = gpGlobals->curtime + m_fFillTime;
+ m_bFillInProgress = true;
+
+ //reset the teleport list for this fill
+ m_hLeftToTeleportThisFill.RemoveAll();
+ m_hLeftToTeleportThisFill.AddVectorToTail( m_hTeleportList );
+
+ SetNextThink( gpGlobals->curtime + TICK_INTERVAL );
+}
+
+void CFunc_LiquidPortal::InputAddActivatorToTeleportList( inputdata_t &inputdata )
+{
+ if( inputdata.pActivator == NULL )
+ return;
+
+ for( int i = m_hTeleportList.Count(); --i >= 0; )
+ {
+ if( m_hTeleportList[i].Get() == inputdata.pActivator )
+ return; //only have 1 reference of each entity
+ }
+
+ m_hTeleportList.AddToTail( inputdata.pActivator );
+ if( m_bFillInProgress )
+ m_hLeftToTeleportThisFill.AddToTail( inputdata.pActivator );
+
+ if( inputdata.pActivator->IsPlayer() )
+ ((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal = this;
+}
+
+void CFunc_LiquidPortal::InputRemoveActivatorFromTeleportList( inputdata_t &inputdata )
+{
+ if( inputdata.pActivator == NULL )
+ return;
+
+ for( int i = m_hTeleportList.Count(); --i >= 0; )
+ {
+ if( m_hTeleportList[i].Get() == inputdata.pActivator )
+ {
+ m_hTeleportList.FastRemove( i );
+
+ if( inputdata.pActivator->IsPlayer() && (((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal.Get() == this) )
+ ((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal = NULL;
+
+ if( m_bFillInProgress )
+ {
+ //remove from the list for this fill as well
+ for( int j = m_hLeftToTeleportThisFill.Count(); --j >= 0; )
+ {
+ if( m_hLeftToTeleportThisFill[j].Get() == inputdata.pActivator )
+ {
+ m_hLeftToTeleportThisFill.FastRemove( j );
+ break;
+ }
+ }
+ }
+
+ return;
+ }
+ }
+}
+
+void CFunc_LiquidPortal::SetLinkedLiquidPortal( CFunc_LiquidPortal *pLinked )
+{
+ CFunc_LiquidPortal *pCurrentLinkedPortal = m_hLinkedPortal.Get();
+ if( pCurrentLinkedPortal == pLinked )
+ return;
+
+ if( pCurrentLinkedPortal != NULL )
+ {
+ m_hLinkedPortal = NULL;
+ pCurrentLinkedPortal->SetLinkedLiquidPortal( NULL );
+ }
+
+ m_hLinkedPortal = pLinked;
+ if( pLinked != NULL )
+ pLinked->SetLinkedLiquidPortal( this );
+
+ ComputeLinkMatrix();
+}
+
+void CFunc_LiquidPortal::ComputeLinkMatrix( void )
+{
+ CFunc_LiquidPortal *pLinkedPortal = m_hLinkedPortal.Get();
+ if( pLinkedPortal )
+ {
+ VMatrix matLocalToWorld, matLocalToWorldInv, matRemoteToWorld;
+
+ matLocalToWorld = EntityToWorldTransform();
+ matRemoteToWorld = pLinkedPortal->EntityToWorldTransform();
+
+ MatrixInverseTR( matLocalToWorld, matLocalToWorldInv );
+ m_matrixThisToLinked = matRemoteToWorld * matLocalToWorldInv;
+
+ MatrixInverseTR( m_matrixThisToLinked, pLinkedPortal->m_matrixThisToLinked );
+ }
+ else
+ {
+ m_matrixThisToLinked.Identity();
+ }
+}
+
+void CFunc_LiquidPortal::TeleportImmersedEntity( CBaseEntity *pEntity )
+{
+ if( pEntity == NULL )
+ return;
+
+ if( pEntity->IsPlayer() )
+ {
+ CPortal_Player *pEntityAsPlayer = (CPortal_Player *)pEntity;
+
+ Vector vNewOrigin = m_matrixThisToLinked * pEntity->GetAbsOrigin();
+ QAngle qNewAngles = TransformAnglesToWorldSpace( pEntityAsPlayer->EyeAngles(), m_matrixThisToLinked.As3x4() );
+ Vector vNewVelocity = m_matrixThisToLinked.ApplyRotation( pEntity->GetAbsVelocity() );
+
+ pEntity->Teleport( &vNewOrigin, &qNewAngles, &vNewVelocity );
+
+ pEntityAsPlayer->m_hSurroundingLiquidPortal = m_hLinkedPortal;
+ }
+ else
+ {
+ Vector vNewOrigin = m_matrixThisToLinked * pEntity->GetAbsOrigin();
+ QAngle qNewAngles = TransformAnglesToWorldSpace( pEntity->GetAbsAngles(), m_matrixThisToLinked.As3x4() );
+ Vector vNewVelocity = m_matrixThisToLinked.ApplyRotation( pEntity->GetAbsVelocity() );
+
+ pEntity->Teleport( &vNewOrigin, &qNewAngles, &vNewVelocity );
+ }
+}
+
+void CFunc_LiquidPortal::Think( void )
+{
+ if( m_bFillInProgress )
+ {
+ if( gpGlobals->curtime < m_fFillEndTime )
+ {
+ float fInterp = ((gpGlobals->curtime - m_fFillStartTime) / (m_fFillEndTime - m_fFillStartTime));
+ Vector vMins, vMaxs;
+ GetCollideable()->WorldSpaceSurroundingBounds( &vMins, &vMaxs );
+ vMaxs.z = vMins.z + ((vMaxs.z - vMins.z) * fInterp);
+
+ for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; )
+ {
+ CBaseEntity *pEntity = m_hLeftToTeleportThisFill[i].Get();
+ if( pEntity == NULL )
+ continue;
+
+ Vector vEntMins, vEntMaxs;
+ pEntity->GetCollideable()->WorldSpaceSurroundingBounds( &vEntMins, &vEntMaxs );
+
+ if( vEntMaxs.z <= vMaxs.z )
+ {
+ TeleportImmersedEntity( pEntity );
+ m_hLeftToTeleportThisFill.FastRemove( i );
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + TICK_INTERVAL );
+ }
+ else
+ {
+ //teleport everything that's left in the list
+ for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; )
+ {
+ TeleportImmersedEntity( m_hLeftToTeleportThisFill[i].Get() );
+ }
+
+ m_hLeftToTeleportThisFill.RemoveAll();
+ m_bFillInProgress = false;
+ }
+ }
+}
+
+
+
+
diff --git a/game/server/portal/func_liquidportal.h b/game/server/portal/func_liquidportal.h
new file mode 100644
index 0000000..3e3f532
--- /dev/null
+++ b/game/server/portal/func_liquidportal.h
@@ -0,0 +1,65 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Rising liquid that acts as a one-way portal
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#ifndef FUNC_LIQUIDPORTAL_H
+#define FUNC_LIQUIDPORTAL_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "triggers.h"
+
+class CFunc_LiquidPortal : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CFunc_LiquidPortal, CBaseEntity );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CFunc_LiquidPortal( void );
+
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual void Think( void );
+
+ virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+ virtual int Save( ISave &save );
+ virtual int Restore( IRestore &restore );
+
+ void InputSetLinkedLiquidPortal( inputdata_t &inputdata );
+ void InputSetFillTime( inputdata_t &inputdata ); //time it takes to fill the portal volume
+ void InputStartFilling( inputdata_t &inputdata ); //start filling with portal liquid, will teleport entities as they become completely enveloped
+
+ //add/remove teleportables to offload the selection process to triggers, each with their own filters
+ void InputAddActivatorToTeleportList( inputdata_t &inputdata ); //add an activator entity to the list of entities to teleport when filling
+ void InputRemoveActivatorFromTeleportList( inputdata_t &inputdata ); //remove an activator entity from the list of entities to teleport when filling
+
+ void ComputeLinkMatrix( void );
+ void SetLinkedLiquidPortal( CFunc_LiquidPortal *pLinked );
+
+ void TeleportImmersedEntity( CBaseEntity *pEntity );
+
+ CNetworkHandle( CFunc_LiquidPortal, m_hLinkedPortal ); //the portal this portal is linked to
+ VMatrix m_matrixThisToLinked; //the matrix that will transform a point relative to this portal, to a point relative to the linked portal
+ float m_fFillTime; //how long it takes to fill completely
+ bool m_bFillInProgress;
+ CNetworkVar( float, m_fFillStartTime ); // time started filling with portal liquid, will teleport entities as they become completely enveloped
+ CNetworkVar( float, m_fFillEndTime ); // time that filling should be finished and touching entities teleport
+
+ CUtlVector<EHANDLE> m_hTeleportList; //list of entities to teleport when filling
+ CUtlVector<EHANDLE> m_hLeftToTeleportThisFill; //list of entities that have not yet teleported during this fill, they get teleported out when fully immersed in the liquid
+
+ string_t m_strInitialLinkedPortal;
+};
+
+#endif //#ifndef FUNC_LIQUIDPORTAL_H
+
diff --git a/game/server/portal/func_noportal_volume.cpp b/game/server/portal/func_noportal_volume.cpp
new file mode 100644
index 0000000..1e23bfa
--- /dev/null
+++ b/game/server/portal/func_noportal_volume.cpp
@@ -0,0 +1,130 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume in which no portal can be placed. Keeps a global list loaded in from the map
+// and provides an interface with which prop_portal can get this list and avoid successfully
+// creating portals wholly or partially inside the volume.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#include "cbase.h"
+#include "func_noportal_volume.h"
+#include "prop_portal_shared.h"
+#include "portal_shareddefs.h"
+#include "portal_util_shared.h"
+#include "collisionutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Spawnflags
+#define SF_START_INACTIVE 0x01
+
+CEntityClassList<CFuncNoPortalVolume> g_FuncNoPortalVolumeList;
+template <> CFuncNoPortalVolume *CEntityClassList<CFuncNoPortalVolume>::m_pClassList = NULL;
+
+CFuncNoPortalVolume* GetNoPortalVolumeList()
+{
+ return g_FuncNoPortalVolumeList.m_pClassList;
+}
+
+
+LINK_ENTITY_TO_CLASS( func_noportal_volume, CFuncNoPortalVolume );
+
+BEGIN_DATADESC( CFuncNoPortalVolume )
+
+ DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iListIndex, FIELD_INTEGER ),
+ // No need to save this, its rebuilt on construct
+ //DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+
+ DEFINE_FUNCTION( GetIndex ),
+ DEFINE_FUNCTION( IsActive ),
+
+END_DATADESC()
+
+
+CFuncNoPortalVolume::CFuncNoPortalVolume()
+{
+ m_bActive = true;
+
+ // Add me to the global list
+ g_FuncNoPortalVolumeList.Insert( this );
+}
+
+CFuncNoPortalVolume::~CFuncNoPortalVolume()
+{
+ g_FuncNoPortalVolumeList.Remove( this );
+}
+
+
+void CFuncNoPortalVolume::Spawn()
+{
+ BaseClass::Spawn();
+
+ if ( m_spawnflags & SF_START_INACTIVE )
+ {
+ m_bActive = false;
+ }
+ else
+ {
+ m_bActive = true;
+ }
+
+ // Bind to our model, cause we need the extents for bounds checking
+ SetModel( STRING( GetModelName() ) );
+ SetRenderMode( kRenderNone ); // Don't draw
+ SetSolid( SOLID_VPHYSICS ); // we may want slanted walls, so we'll use OBB
+ AddSolidFlags( FSOLID_NOT_SOLID );
+}
+
+void CFuncNoPortalVolume::OnActivate( void )
+{
+ if ( !GetCollideable() )
+ return;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal->m_bActivated &&
+ IsOBBIntersectingOBB( pTempPortal->GetAbsOrigin(), pTempPortal->GetAbsAngles(), CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
+ GetAbsOrigin(), GetCollideable()->GetCollisionAngles(), GetCollideable()->OBBMins(), GetCollideable()->OBBMaxs() ) )
+ {
+ pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pTempPortal->Fizzle();
+ }
+ }
+ }
+}
+
+void CFuncNoPortalVolume::InputActivate( inputdata_t &inputdata )
+{
+ m_bActive = true;
+
+ OnActivate();
+}
+
+void CFuncNoPortalVolume::InputDeactivate( inputdata_t &inputdata )
+{
+ m_bActive = false;
+}
+
+void CFuncNoPortalVolume::InputToggle( inputdata_t &inputdata )
+{
+ m_bActive = !m_bActive;
+
+ if ( m_bActive )
+ {
+ OnActivate();
+ }
+}
+
diff --git a/game/server/portal/func_noportal_volume.h b/game/server/portal/func_noportal_volume.h
new file mode 100644
index 0000000..fcf91ee
--- /dev/null
+++ b/game/server/portal/func_noportal_volume.h
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume in which no portal can be placed. Keeps a global list loaded in from the map
+// and provides an interface with which prop_portal can get this list and avoid successfully
+// creating portals wholly or partially inside the volume.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#ifndef _FUNC_NOPORTAL_VOLUME_H_
+#define _FUNC_NOPORTAL_VOLUME_H_
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cbase.h"
+
+class CFuncNoPortalVolume : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CFuncNoPortalVolume, CBaseEntity );
+
+ CFuncNoPortalVolume();
+ ~CFuncNoPortalVolume();
+
+ // Overloads from base entity
+ virtual void Spawn( void );
+
+ void OnActivate( void );
+
+ // Inputs to flip functionality on and off
+ void InputActivate( inputdata_t &inputdata );
+ void InputDeactivate( inputdata_t &inputdata );
+ void InputToggle( inputdata_t &inputdata );
+
+ // misc public methods
+ unsigned int GetIndex () { return m_iListIndex; } // returns the list index of this camera
+ bool IsActive() { return m_bActive; } // is this area currently blocking portals
+
+ CFuncNoPortalVolume *m_pNext; // Needed for the template list
+
+ DECLARE_DATADESC();
+
+private:
+ bool m_bActive; // are we currently blocking portals
+ unsigned int m_iListIndex; // what is my index into the global noportal_volume list
+
+
+};
+
+// Global interface for getting the list of noportal_volumes
+CFuncNoPortalVolume* GetNoPortalVolumeList();
+
+
+#endif \ No newline at end of file
diff --git a/game/server/portal/func_portal_bumper.cpp b/game/server/portal/func_portal_bumper.cpp
new file mode 100644
index 0000000..cc65991
--- /dev/null
+++ b/game/server/portal/func_portal_bumper.cpp
@@ -0,0 +1,103 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume which bumps portal placement. Keeps a global list loaded in from the map
+// and provides an interface with which prop_portal can get this list and avoid successfully
+// creating portals partially inside the volume.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#include "cbase.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+// Spawnflags
+#define SF_START_INACTIVE 0x01
+
+
+class CFuncPortalBumper : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CFuncPortalBumper, CBaseEntity );
+
+ CFuncPortalBumper();
+
+ // Overloads from base entity
+ virtual void Spawn( void );
+
+ // Inputs to flip functionality on and off
+ void InputActivate( inputdata_t &inputdata );
+ void InputDeactivate( inputdata_t &inputdata );
+ void InputToggle( inputdata_t &inputdata );
+
+ // misc public methods
+ bool IsActive() { return m_bActive; } // is this area currently bumping portals
+
+ DECLARE_DATADESC();
+
+private:
+ bool m_bActive; // are we currently blocking portals
+
+
+};
+
+
+LINK_ENTITY_TO_CLASS( func_portal_bumper, CFuncPortalBumper );
+
+BEGIN_DATADESC( CFuncPortalBumper )
+
+ DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+
+ DEFINE_FUNCTION( IsActive ),
+
+END_DATADESC()
+
+
+CFuncPortalBumper::CFuncPortalBumper()
+{
+ m_bActive = true;
+}
+
+void CFuncPortalBumper::Spawn()
+{
+ BaseClass::Spawn();
+
+ if ( m_spawnflags & SF_START_INACTIVE )
+ {
+ m_bActive = false;
+ }
+ else
+ {
+ m_bActive = true;
+ }
+
+ // Bind to our model, cause we need the extents for bounds checking
+ SetModel( STRING( GetModelName() ) );
+ SetRenderMode( kRenderNone ); // Don't draw
+ SetSolid( SOLID_VPHYSICS ); // we may want slanted walls, so we'll use OBB
+ AddSolidFlags( FSOLID_NOT_SOLID );
+}
+
+void CFuncPortalBumper::InputActivate( inputdata_t &inputdata )
+{
+ m_bActive = true;
+}
+
+void CFuncPortalBumper::InputDeactivate( inputdata_t &inputdata )
+{
+ m_bActive = false;
+}
+
+void CFuncPortalBumper::InputToggle( inputdata_t &inputdata )
+{
+ m_bActive = !m_bActive;
+}
+
diff --git a/game/server/portal/func_portal_detector.cpp b/game/server/portal/func_portal_detector.cpp
new file mode 100644
index 0000000..660da49
--- /dev/null
+++ b/game/server/portal/func_portal_detector.cpp
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume in which no portal can be placed. Keeps a global list loaded in from the map
+// and provides an interface with which prop_portal can get this list and avoid successfully
+// creating portals wholly or partially inside the volume.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#include "cbase.h"
+#include "func_portal_detector.h"
+#include "prop_portal_shared.h"
+#include "portal_shareddefs.h"
+#include "portal_util_shared.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Spawnflags
+#define SF_START_INACTIVE 0x01
+
+
+LINK_ENTITY_TO_CLASS( func_portal_detector, CFuncPortalDetector );
+
+BEGIN_DATADESC( CFuncPortalDetector )
+
+ DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_INTEGER, "LinkageGroupID" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+
+ DEFINE_OUTPUT( m_OnStartTouchPortal1, "OnStartTouchPortal1" ),
+ DEFINE_OUTPUT( m_OnStartTouchPortal2, "OnStartTouchPortal2" ),
+ DEFINE_OUTPUT( m_OnStartTouchLinkedPortal, "OnStartTouchLinkedPortal" ),
+ DEFINE_OUTPUT( m_OnStartTouchBothLinkedPortals, "OnStartTouchBothLinkedPortals" ),
+
+ DEFINE_FUNCTION( IsActive ),
+
+END_DATADESC()
+
+
+void CFuncPortalDetector::Spawn()
+{
+ BaseClass::Spawn();
+
+ if ( m_spawnflags & SF_START_INACTIVE )
+ {
+ m_bActive = false;
+ }
+ else
+ {
+ m_bActive = true;
+ }
+
+ // Bind to our model, cause we need the extents for bounds checking
+ SetModel( STRING( GetModelName() ) );
+ SetRenderMode( kRenderNone ); // Don't draw
+ SetSolid( SOLID_VPHYSICS ); // we may want slanted walls, so we'll use OBB
+ AddSolidFlags( FSOLID_NOT_SOLID );
+}
+
+void CFuncPortalDetector::OnActivate( void )
+{
+ Vector vMin, vMax;
+ CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
+
+ Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
+ Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
+
+ bool bTouchedPortal1 = false;
+ bool bTouchedPortal2 = false;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+
+ //require that it's active and/or linked?
+
+ if( pTempPortal->GetLinkageGroup() == m_iLinkageGroupID && UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, pTempPortal ) )
+ {
+ if( pTempPortal->IsPortal2() )
+ {
+ m_OnStartTouchPortal2.FireOutput( pTempPortal, this );
+
+ if ( pTempPortal->IsActivedAndLinked() )
+ {
+ bTouchedPortal2 = true;
+ m_OnStartTouchLinkedPortal.FireOutput( pTempPortal, this );
+ }
+ }
+ else
+ {
+ m_OnStartTouchPortal1.FireOutput( pTempPortal, this );
+
+ if ( pTempPortal->IsActivedAndLinked() )
+ {
+ bTouchedPortal1 = true;
+ m_OnStartTouchLinkedPortal.FireOutput( pTempPortal, this );
+ }
+ }
+ }
+ }
+ }
+
+ if ( bTouchedPortal1 && bTouchedPortal2 )
+ {
+ m_OnStartTouchBothLinkedPortals.FireOutput( this, this );
+ }
+}
+
+void CFuncPortalDetector::InputDisable( inputdata_t &inputdata )
+{
+ m_bActive = false;
+}
+
+void CFuncPortalDetector::InputEnable( inputdata_t &inputdata )
+{
+ m_bActive = true;
+
+ OnActivate();
+}
+
+void CFuncPortalDetector::InputToggle( inputdata_t &inputdata )
+{
+ m_bActive = !m_bActive;
+
+ if ( m_bActive )
+ {
+ OnActivate();
+ }
+}
diff --git a/game/server/portal/func_portal_detector.h b/game/server/portal/func_portal_detector.h
new file mode 100644
index 0000000..3a6e325
--- /dev/null
+++ b/game/server/portal/func_portal_detector.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume which fires an output when a portal is placed in it.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#ifndef _FUNC_PORTAL_DETECTOR_H_
+#define _FUNC_PORTAL_DETECTOR_H_
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cbase.h"
+
+class CFuncPortalDetector : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CFuncPortalDetector, CBaseEntity );
+
+ // Overloads from base entity
+ virtual void Spawn( void );
+
+ void OnActivate( void );
+
+ // Inputs to flip functionality on and off
+ void InputDisable( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+ void InputToggle( inputdata_t &inputdata );
+
+ // misc public methods
+ bool IsActive( void ) { return m_bActive; } // is this area currently detecting portals
+ int GetLinkageGroupID( void ) { return m_iLinkageGroupID; }
+
+ COutputEvent m_OnStartTouchPortal1;
+ COutputEvent m_OnStartTouchPortal2;
+ COutputEvent m_OnStartTouchLinkedPortal;
+ COutputEvent m_OnStartTouchBothLinkedPortals;
+
+ DECLARE_DATADESC();
+
+private:
+ bool m_bActive; // are we currently detecting portals
+ int m_iLinkageGroupID; // what set of portals are we testing for?
+
+};
+
+#endif
diff --git a/game/server/portal/func_portal_orientation.cpp b/game/server/portal/func_portal_orientation.cpp
new file mode 100644
index 0000000..903d12b
--- /dev/null
+++ b/game/server/portal/func_portal_orientation.cpp
@@ -0,0 +1,167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#include "cbase.h"
+#include "func_portal_orientation.h"
+#include "prop_portal_shared.h"
+#include "portal_shareddefs.h"
+#include "portal_util_shared.h"
+#include "portal_placement.h"
+#include "collisionutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+CEntityClassList<CFuncPortalOrientation> g_FuncPortalOrientationVolumeList;
+template <> CFuncPortalOrientation *CEntityClassList<CFuncPortalOrientation>::m_pClassList = NULL;
+
+CFuncPortalOrientation* GetPortalOrientationVolumeList()
+{
+ return g_FuncPortalOrientationVolumeList.m_pClassList;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test for func_orientation_volume ents which could effect the placement angles of a portal.
+// Input : vecCurAngles - Default angles to place (may change)
+// vecCurOrigin - origin of the portal on placement
+// pPortal - The portal attempting to place
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool UTIL_TestForOrientationVolumes( QAngle& vecCurAngles, const Vector& vecCurOrigin, const CProp_Portal* pPortal )
+{
+ if ( !pPortal )
+ return false;
+
+ // Walk list of orientation volumes, obb test each with candidate portal
+ CFuncPortalOrientation *pList = g_FuncPortalOrientationVolumeList.m_pClassList;
+ while ( pList )
+ {
+ if ( !pList->IsActive() )
+ {
+ pList = pList->m_pNext;
+ continue;
+ }
+
+ if ( IsOBBIntersectingOBB( vecCurOrigin, vecCurAngles, CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
+ pList->GetAbsOrigin(), pList->GetCollideable()->GetCollisionAngles(), pList->GetCollideable()->OBBMins(), pList->GetCollideable()->OBBMaxs() ) )
+ {
+ QAngle vecGoalAngles;
+ // Ent is marked to match angles of it's linked partner
+ if ( pList->m_bMatchLinkedAngles )
+ {
+ // This feature requires a linked portal on a floor or ceiling. Bail without effecting
+ // the placement angles if we fail those requirements.
+ CProp_Portal* pLinked = pPortal->m_hLinkedPortal.Get();
+ if ( !pLinked || !(AnglesAreEqual( vecCurAngles.x, -90.0f, 0.1f ) || AnglesAreEqual( vecCurAngles.x, 90.0f, 0.1f )) )
+ return false;
+
+ vecGoalAngles = pLinked->GetAbsAngles();
+ vecCurAngles.y = 0.0f;
+ vecCurAngles.z = vecGoalAngles.z;
+ }
+ // Match the angles loaded in from the map
+ else
+ {
+ vecGoalAngles = pList->m_vecAnglesToFace;
+ vecCurAngles = vecGoalAngles;
+ }
+
+ return true;
+ }
+ pList = pList->m_pNext;
+ }
+
+ return false;
+}
+
+LINK_ENTITY_TO_CLASS( func_portal_orientation, CFuncPortalOrientation );
+
+BEGIN_DATADESC( CFuncPortalOrientation )
+
+ DEFINE_FIELD( m_iListIndex, FIELD_INTEGER ),
+
+ //DEFINE_FIELD ( m_pNext, CFuncPortalOrientation ),
+
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+ DEFINE_KEYFIELD ( m_bMatchLinkedAngles, FIELD_BOOLEAN, "MatchLinkedAngles" ),
+ DEFINE_KEYFIELD ( m_vecAnglesToFace, FIELD_VECTOR, "AnglesToFace" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+
+END_DATADESC()
+
+CFuncPortalOrientation::CFuncPortalOrientation()
+{
+ g_FuncPortalOrientationVolumeList.Insert( this );
+}
+
+CFuncPortalOrientation::~CFuncPortalOrientation()
+{
+ g_FuncPortalOrientationVolumeList.Remove( this );
+}
+
+void CFuncPortalOrientation::Spawn()
+{
+ BaseClass::Spawn();
+
+ // Bind to our model, cause we need the extents for bounds checking
+ SetModel( STRING( GetModelName() ) );
+ SetRenderMode( kRenderNone ); // Don't draw
+ SetSolid( SOLID_VPHYSICS ); // we may want slanted walls, so we'll use OBB
+ AddSolidFlags( FSOLID_NOT_SOLID );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test for portals inside our volume when we switch on, and forcibly rotate them
+//-----------------------------------------------------------------------------
+void CFuncPortalOrientation::OnActivate( void )
+{
+ if ( !GetCollideable() || m_bDisabled )
+ return;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( IsOBBIntersectingOBB( pTempPortal->GetAbsOrigin(), pTempPortal->GetAbsAngles(), CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
+ GetAbsOrigin(), GetCollideable()->GetCollisionAngles(), GetCollideable()->OBBMins(), GetCollideable()->OBBMaxs() ) )
+ {
+ QAngle angNewAngles;
+ if ( m_bMatchLinkedAngles )
+ {
+ CProp_Portal* pLinked = pTempPortal->m_hLinkedPortal.Get();
+ if ( !pLinked )
+ return;
+
+ angNewAngles = pTempPortal->m_hLinkedPortal->GetAbsAngles();
+ }
+ else
+ {
+ angNewAngles = m_vecAnglesToFace;
+ }
+
+ pTempPortal->PlacePortal( pTempPortal->GetAbsOrigin(), angNewAngles, PORTAL_ANALOG_SUCCESS_NO_BUMP );
+ }
+ }
+ }
+}
+
+void CFuncPortalOrientation::InputEnable( inputdata_t &inputdata )
+{
+ m_bDisabled = false;
+
+ OnActivate();
+}
+
+void CFuncPortalOrientation::InputDisable( inputdata_t &inputdata )
+{
+ m_bDisabled = true;
+} \ No newline at end of file
diff --git a/game/server/portal/func_portal_orientation.h b/game/server/portal/func_portal_orientation.h
new file mode 100644
index 0000000..5941a2d
--- /dev/null
+++ b/game/server/portal/func_portal_orientation.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Volume entity which overrides the placement angles of a portal placed within its bounds.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#ifndef _FUNC_PORTAL_ORIENTATION_H_
+#define _FUNC_PORTAL_ORIENTATION_H_
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cbase.h"
+
+class CFuncPortalOrientation : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CFuncPortalOrientation, CBaseEntity );
+ DECLARE_DATADESC();
+
+ CFuncPortalOrientation();
+ ~CFuncPortalOrientation();
+
+ // Overloads from base entity
+ virtual void Spawn( void );
+
+ void OnActivate ( void );
+
+ // Inputs to flip functionality on and off
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+
+ bool IsActive() { return !m_bDisabled; } // is this area causing portals to lock orientation
+
+ bool m_bMatchLinkedAngles;
+ QAngle m_vecAnglesToFace;
+
+ CFuncPortalOrientation *m_pNext; // Needed for the template list
+ unsigned int m_iListIndex;
+private:
+ bool m_bDisabled; // are we currently locking portal orientations
+};
+
+CFuncPortalOrientation* GetPortalOrientationVolumeList();
+
+// Upon portal placement, test for orientation changing volumes
+bool UTIL_TestForOrientationVolumes( QAngle& vecCurAngles, const Vector& vecCurOrigin, const CProp_Portal* pPortal );
+
+#endif //_FUNC_PORTAL_ORIENTATION_H_ \ No newline at end of file
diff --git a/game/server/portal/neurotoxin_countdown.cpp b/game/server/portal/neurotoxin_countdown.cpp
new file mode 100644
index 0000000..0cb4e7a
--- /dev/null
+++ b/game/server/portal/neurotoxin_countdown.cpp
@@ -0,0 +1,306 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the big scary boom-boom machine Antlions fear.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "EnvMessage.h"
+#include "fmtstr.h"
+#include "vguiscreen.h"
+#include "filesystem.h"
+
+
+struct SlideKeywordList_t
+{
+ char szSlideKeyword[64];
+};
+
+
+class CNeurotoxinCountdown : public CBaseEntity
+{
+public:
+
+ DECLARE_CLASS( CNeurotoxinCountdown, CBaseEntity );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual ~CNeurotoxinCountdown();
+
+ virtual bool KeyValue( const char *szKeyName, const char *szValue );
+
+ virtual int UpdateTransmitState();
+ virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void OnRestore( void );
+
+ void ScreenVisible( bool bVisible );
+
+ void Disable( void );
+ void Enable( void );
+
+ void InputDisable( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+
+private:
+
+ // Control panel
+ void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName );
+ void SpawnControlPanels( void );
+ void RestoreControlPanels( void );
+
+private:
+
+ CNetworkVar( bool, m_bEnabled );
+
+ int m_iScreenWidth;
+ int m_iScreenHeight;
+
+ typedef CHandle<CVGuiScreen> ScreenHandle_t;
+ CUtlVector<ScreenHandle_t> m_hScreens;
+};
+
+
+LINK_ENTITY_TO_CLASS( vgui_neurotoxin_countdown, CNeurotoxinCountdown );
+
+//-----------------------------------------------------------------------------
+// Save/load
+//-----------------------------------------------------------------------------
+BEGIN_DATADESC( CNeurotoxinCountdown )
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_iScreenWidth, FIELD_INTEGER, "width" ),
+ DEFINE_KEYFIELD( m_iScreenHeight, FIELD_INTEGER, "height" ),
+
+ //DEFINE_UTLVECTOR( m_hScreens, FIELD_EHANDLE ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CNeurotoxinCountdown, DT_NeurotoxinCountdown )
+ SendPropBool( SENDINFO(m_bEnabled) ),
+END_SEND_TABLE()
+
+
+CNeurotoxinCountdown::~CNeurotoxinCountdown()
+{
+ int i;
+ // Kill the control panels
+ for ( i = m_hScreens.Count(); --i >= 0; )
+ {
+ DestroyVGuiScreen( m_hScreens[i].Get() );
+ }
+ m_hScreens.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Read in worldcraft data...
+//-----------------------------------------------------------------------------
+bool CNeurotoxinCountdown::KeyValue( const char *szKeyName, const char *szValue )
+{
+ //!! temp hack, until worldcraft is fixed
+ // strip the # tokens from (duplicate) key names
+ char *s = (char *)strchr( szKeyName, '#' );
+ if ( s )
+ {
+ *s = '\0';
+ }
+
+ // NOTE: Have to do these separate because they set two values instead of one
+ if( FStrEq( szKeyName, "angles" ) )
+ {
+ Assert( GetMoveParent() == NULL );
+ QAngle angles;
+ UTIL_StringToVector( angles.Base(), szValue );
+
+ // Because the vgui screen basis is strange (z is front, y is up, x is right)
+ // we need to rotate the typical basis before applying it
+ VMatrix mat, rotation, tmp;
+ MatrixFromAngles( angles, mat );
+ MatrixBuildRotationAboutAxis( rotation, Vector( 0, 1, 0 ), 90 );
+ MatrixMultiply( mat, rotation, tmp );
+ MatrixBuildRotateZ( rotation, 90 );
+ MatrixMultiply( tmp, rotation, mat );
+ MatrixToAngles( mat, angles );
+ SetAbsAngles( angles );
+
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+int CNeurotoxinCountdown::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_FULLCHECK );
+}
+
+void CNeurotoxinCountdown::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
+ return;
+
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Force our screens to be sent too.
+ for ( int i=0; i < m_hScreens.Count(); i++ )
+ {
+ CVGuiScreen *pScreen = m_hScreens[i].Get();
+ pScreen->SetTransmit( pInfo, bAlways );
+ }
+}
+
+void CNeurotoxinCountdown::Spawn( void )
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ m_bEnabled = false;
+
+ SpawnControlPanels();
+
+ ScreenVisible( m_bEnabled );
+}
+
+void CNeurotoxinCountdown::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheVGuiScreen( "neurotoxin_countdown_screen" );
+}
+
+void CNeurotoxinCountdown::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ RestoreControlPanels();
+
+ ScreenVisible( m_bEnabled );
+}
+
+void CNeurotoxinCountdown::ScreenVisible( bool bVisible )
+{
+ for ( int iScreen = 0; iScreen < m_hScreens.Count(); ++iScreen )
+ {
+ CVGuiScreen *pScreen = m_hScreens[ iScreen ].Get();
+ if ( bVisible )
+ pScreen->RemoveEffects( EF_NODRAW );
+ else
+ pScreen->AddEffects( EF_NODRAW );
+ }
+}
+
+void CNeurotoxinCountdown::Disable( void )
+{
+ if ( !m_bEnabled )
+ return;
+
+ m_bEnabled = false;
+
+ ScreenVisible( false );
+}
+
+void CNeurotoxinCountdown::Enable( void )
+{
+ if ( m_bEnabled )
+ return;
+
+ m_bEnabled = true;
+
+ ScreenVisible( true );
+}
+
+
+void CNeurotoxinCountdown::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+void CNeurotoxinCountdown::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+void CNeurotoxinCountdown::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "neurotoxin_countdown_screen";
+}
+
+void CNeurotoxinCountdown::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "vgui_screen";
+}
+
+//-----------------------------------------------------------------------------
+// This is called by the base object when it's time to spawn the control panels
+//-----------------------------------------------------------------------------
+void CNeurotoxinCountdown::SpawnControlPanels()
+{
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ float flWidth = m_iScreenWidth;
+ float flHeight = m_iScreenHeight;
+
+ CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, this, this, -1 );
+ pScreen->ChangeTeam( GetTeamNumber() );
+ pScreen->SetActualSize( flWidth, flHeight );
+ pScreen->SetActive( true );
+ pScreen->MakeVisibleOnlyToTeammates( false );
+ pScreen->SetTransparency( true );
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+
+ return;
+ }
+}
+
+void CNeurotoxinCountdown::RestoreControlPanels( void )
+{
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ CVGuiScreen *pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( NULL, pScreenClassname );
+
+ while ( ( pScreen && pScreen->GetOwnerEntity() != this ) || Q_strcmp( pScreen->GetPanelName(), pScreenName ) != 0 )
+ {
+ pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( pScreen, pScreenClassname );
+ }
+
+ if ( pScreen )
+ {
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+ pScreen->SetActive( true );
+ }
+
+ return;
+ }
+} \ No newline at end of file
diff --git a/game/server/portal/npc_portal_turret_floor.cpp b/game/server/portal/npc_portal_turret_floor.cpp
new file mode 100644
index 0000000..64d0afc
--- /dev/null
+++ b/game/server/portal/npc_portal_turret_floor.cpp
@@ -0,0 +1,1532 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "npc_turret_floor.h"
+#include "portal_player.h"
+#include "weapon_physcannon.h"
+#include "basehlcombatweapon_shared.h"
+#include "ammodef.h"
+#include "ai_senses.h"
+#include "ai_memory.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "prop_portal_shared.h"
+#include "Sprite.h"
+
+#define SF_FLOOR_TURRET_AUTOACTIVATE 0x00000020
+#define SF_FLOOR_TURRET_STARTINACTIVE 0x00000040
+#define SF_FLOOR_TURRET_OUT_OF_AMMO 0x00000100
+
+#define FLOOR_TURRET_PORTAL_MODEL "models/props/Turret_01.mdl"
+#define FLOOR_TURRET_GLOW_SPRITE "sprites/glow1.vmt"
+#define FLOOR_TURRET_BC_YAW "aim_yaw"
+#define FLOOR_TURRET_BC_PITCH "aim_pitch"
+#define PORTAL_FLOOR_TURRET_RANGE 1500
+#define PORTAL_FLOOR_TURRET_MAX_SHOT_DELAY 2.5f
+#define FLOOR_TURRET_MAX_WAIT 5
+#define FLOOR_TURRET_SHORT_WAIT 2.0f // Used for FAST_RETIRE spawnflag
+
+#define SF_FLOOR_TURRET_FASTRETIRE 0x00000080
+
+#define TURRET_FLOOR_DAMAGE_MULTIPLIER 3.0f
+#define TURRET_FLOOR_BULLET_FORCE_MULTIPLIER 0.4f
+#define TURRET_FLOOR_PHYSICAL_FORCE_MULTIPLIER 135.0f
+
+#define PORTAL_FLOOR_TURRET_NUM_ROPES 4
+
+//Turret states
+enum portalTurretState_e
+{
+ PORTAL_TURRET_DISABLED = TURRET_STATE_TOTAL,
+ PORTAL_TURRET_COLLIDE,
+ PORTAL_TURRET_PICKUP,
+ PORTAL_TURRET_SHOTAT,
+ PORTAL_TURRET_DISSOLVED,
+
+ PORTAL_TURRET_STATE_TOTAL
+};
+
+//Debug visualization
+extern ConVar g_debug_turret;
+
+//Activities
+extern int ACT_FLOOR_TURRET_OPEN;
+extern int ACT_FLOOR_TURRET_CLOSE;
+extern int ACT_FLOOR_TURRET_OPEN_IDLE;
+extern int ACT_FLOOR_TURRET_CLOSED_IDLE;
+extern int ACT_FLOOR_TURRET_FIRE;
+int ACT_FLOOR_TURRET_FIRE2;
+
+
+const char *g_TalkNames[] =
+{
+ "NPC_FloorTurret.TalkSearch",
+ "NPC_FloorTurret.TalkAutosearch",
+ "NPC_FloorTurret.TalkActive",
+ "NPC_FloorTurret.TalkSupress",
+ "NPC_FloorTurret.TalkDeploy",
+ "NPC_FloorTurret.TalkRetire",
+ "NPC_FloorTurret.TalkTipped",
+ NULL // Must have NULL at end of list in case more states are added to base turret!
+};
+
+const char *g_PortalTalkNames[ PORTAL_TURRET_STATE_TOTAL - TURRET_STATE_TOTAL ] =
+{
+ "NPC_FloorTurret.TalkDisabled",
+ "NPC_FloorTurret.TalkCollide",
+ "NPC_FloorTurret.TalkPickup",
+ "NPC_FloorTurret.TalkShotAt",
+ "NPC_FloorTurret.TalkDissolved"
+};
+
+
+const char* GetTurretTalkName( int iState )
+{
+ if ( iState < TURRET_STATE_TOTAL )
+ return g_TalkNames[ iState ];
+
+ return g_PortalTalkNames[ iState - TURRET_STATE_TOTAL ];
+}
+
+
+class CNPC_Portal_FloorTurret : public CNPC_FloorTurret
+{
+ DECLARE_CLASS( CNPC_Portal_FloorTurret, CNPC_FloorTurret );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+public:
+
+ CNPC_Portal_FloorTurret( void );
+
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual void UpdateOnRemove( void );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+
+ virtual bool ShouldAttractAutoAim( CBaseEntity *pAimingEnt );
+ virtual float GetAutoAimRadius();
+ virtual Vector GetAutoAimCenter();
+
+ virtual void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
+
+ virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
+
+ virtual bool PreThink( turretState_e state );
+ virtual void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy, bool bStrict = false );
+ virtual void SetEyeState( eyeState_t state );
+
+ virtual bool OnSide( void );
+
+ virtual float GetAttackDamageScale( CBaseEntity *pVictim );
+ virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget );
+
+ // Think functions
+ virtual void Retire( void );
+ virtual void Deploy( void );
+ virtual void ActiveThink( void );
+ virtual void SearchThink( void );
+ virtual void AutoSearchThink( void );
+ virtual void TippedThink( void );
+ virtual void HeldThink( void );
+ virtual void InactiveThink( void );
+ virtual void SuppressThink( void );
+ virtual void DisabledThink( void );
+ virtual void HackFindEnemy( void );
+
+ virtual void StartTouch( CBaseEntity *pOther );
+
+ bool IsLaserOn( void ) { return m_bLaserOn; }
+ void LaserOff( void );
+ void LaserOn( void );
+ void RopesOn();
+ void RopesOff();
+
+ void FireBullet( const char *pTargetName );
+
+ // Inputs
+ void InputFireBullet( inputdata_t &inputdata );
+
+private:
+
+ CHandle<CRopeKeyframe> m_hRopes[ PORTAL_FLOOR_TURRET_NUM_ROPES ];
+
+ CNetworkVar( bool, m_bOutOfAmmo );
+ CNetworkVar( bool, m_bLaserOn );
+ CNetworkVar( int, m_sLaserHaloSprite );
+
+ int m_iBarrelAttachments[ 4 ];
+ bool m_bShootWithBottomBarrels;
+ bool m_bDamageForce;
+
+ float m_fSearchSpeed;
+ float m_fMovingTargetThreashold;
+ float m_flDistToEnemy;
+
+ turretState_e m_iLastState;
+ float m_fNextTalk;
+ bool m_bDelayTippedTalk;
+
+};
+
+
+LINK_ENTITY_TO_CLASS( npc_portal_turret_floor, CNPC_Portal_FloorTurret );
+
+//Datatable
+BEGIN_DATADESC( CNPC_Portal_FloorTurret )
+
+ DEFINE_ARRAY( m_hRopes, FIELD_EHANDLE, PORTAL_FLOOR_TURRET_NUM_ROPES ),
+
+ DEFINE_FIELD( m_bOutOfAmmo, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bLaserOn, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_sLaserHaloSprite, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flDistToEnemy, FIELD_FLOAT ),
+
+// DEFINE_ARRAY( m_iBarrelAttachments, FIELD_INTEGER, 4 ),
+ DEFINE_FIELD( m_bShootWithBottomBarrels, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fSearchSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fMovingTargetThreashold, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iLastState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fNextTalk, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bDelayTippedTalk, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_bDamageForce, FIELD_BOOLEAN, "DamageForce" ),
+
+ DEFINE_THINKFUNC( Retire ),
+ DEFINE_THINKFUNC( Deploy ),
+ DEFINE_THINKFUNC( ActiveThink ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( AutoSearchThink ),
+ DEFINE_THINKFUNC( TippedThink ),
+ DEFINE_THINKFUNC( HeldThink ),
+ DEFINE_THINKFUNC( InactiveThink ),
+ DEFINE_THINKFUNC( SuppressThink ),
+ DEFINE_THINKFUNC( DisabledThink ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_STRING, "FireBullet", InputFireBullet ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CNPC_Portal_FloorTurret, DT_NPC_Portal_FloorTurret)
+
+ SendPropBool( SENDINFO( m_bOutOfAmmo ) ),
+ SendPropBool( SENDINFO( m_bLaserOn ) ),
+ SendPropInt( SENDINFO( m_sLaserHaloSprite ) ),
+
+END_SEND_TABLE()
+
+
+CNPC_Portal_FloorTurret::CNPC_Portal_FloorTurret( void )
+{
+ CNPC_FloorTurret::fMaxTipControllerVelocity = 100.0f * 100.0f;
+ CNPC_FloorTurret::fMaxTipControllerAngularVelocity = 30.0f * 30.0f;
+
+ m_bDamageForce = true;
+}
+
+void CNPC_Portal_FloorTurret::Precache( void )
+{
+ SetModelName( MAKE_STRING( FLOOR_TURRET_PORTAL_MODEL ) );
+
+ BaseClass::Precache();
+
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_FIRE2 );
+
+ m_sLaserHaloSprite = PrecacheModel( "sprites/redlaserglow.vmt" );
+ PrecacheModel("effects/redlaser1.vmt");
+
+ for ( int iTalkScript = 0; iTalkScript < PORTAL_TURRET_STATE_TOTAL; ++iTalkScript )
+ {
+ if ( iTalkScript < TURRET_STATE_TOTAL )
+ {
+ if ( g_TalkNames[ iTalkScript ] )
+ PrecacheScriptSound( g_TalkNames[ iTalkScript ] );
+ else
+ iTalkScript = TURRET_STATE_TOTAL - 1; // We hit the last script item, so jump to the portal only states
+ }
+ else
+ {
+ PrecacheScriptSound( g_PortalTalkNames[ iTalkScript - TURRET_STATE_TOTAL ] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn the entity
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ m_iBarrelAttachments[ 0 ] = LookupAttachment( "LFT_Gun1_Muzzle" );
+ m_iBarrelAttachments[ 1 ] = LookupAttachment( "RT_Gun1_Muzzle" );
+ m_iBarrelAttachments[ 2 ] = LookupAttachment( "LFT_Gun2_Muzzle" );
+ m_iBarrelAttachments[ 3 ] = LookupAttachment( "RT_Gun2_Muzzle" );
+
+ m_fSearchSpeed = RandomFloat( 1.0f, 1.4f );
+ m_fMovingTargetThreashold = 20.0f;
+
+ m_bNoAlarmSounds = true;
+ m_bOutOfAmmo = ( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO ) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Find all nearby physics objects and add them to the list of objects we will sense
+ CBaseEntity *pObject = gEntList.FindEntityByClassname( NULL, "prop_physics" );
+ while ( pObject )
+ {
+ // Tell the AI sensing list that we want to consider this
+ g_AI_SensedObjectsManager.AddEntity( pObject );
+
+ pObject = gEntList.FindEntityByClassname( pObject, "prop_physics" );
+ }
+
+ pObject = gEntList.FindEntityByClassname( NULL, "func_physbox" );
+ while ( pObject )
+ {
+ // Tell the AI sensing list that we want to consider this
+ g_AI_SensedObjectsManager.AddEntity( pObject );
+
+ pObject = gEntList.FindEntityByClassname( pObject, "func_physbox" );
+ }
+
+ m_iLastState = TURRET_AUTO_SEARCHING;
+
+ m_iBarrelAttachments[ 0 ] = LookupAttachment( "LFT_Gun1_Muzzle" );
+ m_iBarrelAttachments[ 1 ] = LookupAttachment( "RT_Gun1_Muzzle" );
+ m_iBarrelAttachments[ 2 ] = LookupAttachment( "LFT_Gun2_Muzzle" );
+ m_iBarrelAttachments[ 3 ] = LookupAttachment( "RT_Gun2_Muzzle" );
+}
+
+void CNPC_Portal_FloorTurret::UpdateOnRemove( void )
+{
+ if ( IsDissolving() )
+ EmitSound( GetTurretTalkName( PORTAL_TURRET_DISSOLVED ) );
+
+ LaserOff();
+ RopesOff();
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+int CNPC_Portal_FloorTurret::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( m_lifeState == LIFE_ALIVE && ( info.GetDamageType() & DMG_BULLET ) && !info.GetAttacker()->IsPlayer() )
+ {
+ if ( gpGlobals->curtime > m_fNextTalk )
+ {
+ EmitSound( GetTurretTalkName( PORTAL_TURRET_SHOTAT ) );
+ m_fNextTalk = gpGlobals->curtime + 3.0f;
+ }
+ }
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+bool CNPC_Portal_FloorTurret::ShouldAttractAutoAim( CBaseEntity *pAimingEnt )
+{
+ return ( m_lifeState == LIFE_ALIVE );
+}
+
+float CNPC_Portal_FloorTurret::GetAutoAimRadius()
+{
+ return 64.0f;
+}
+
+Vector CNPC_Portal_FloorTurret::GetAutoAimCenter()
+{
+ // We want to shoot portals right under them
+ return WorldSpaceCenter() + Vector( 0.0f, 0.0f, -18.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ if ( m_lifeState == LIFE_ALIVE && m_bEnabled )
+ {
+ m_bActive = true;
+ SetThink( &CNPC_Portal_FloorTurret::HeldThink );
+ }
+
+ BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
+}
+
+void CNPC_Portal_FloorTurret::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // On teleport, we record a pointer to the portal we are arriving at
+ if ( eventType == NOTIFY_EVENT_TELEPORT )
+ {
+ RopesOff();
+ RopesOn();
+ }
+
+ BaseClass::NotifySystemEvent( pNotify, eventType, params );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the turret is currently in
+//-----------------------------------------------------------------------------
+bool CNPC_Portal_FloorTurret::PreThink( turretState_e state )
+{
+ // Working 2 enums into one integer
+ int iNewState = state;
+
+ // If the turret is dissolving go to a special state
+ if ( IsDissolving() )
+ iNewState = PORTAL_TURRET_DISSOLVED;
+
+ // Need to play these sounds immediately
+ if ( m_iLastState != iNewState && ( ( iNewState == TURRET_TIPPED && !m_bDelayTippedTalk ) ||
+ iNewState == TURRET_RETIRING ||
+ iNewState == PORTAL_TURRET_DISSOLVED ||
+ iNewState == PORTAL_TURRET_PICKUP ) )
+ {
+ m_fNextTalk = gpGlobals->curtime -1.0f;
+ }
+
+ // If we've changed states or are in a state with a repeating message
+ if ( gpGlobals->curtime > m_fNextTalk && m_iLastState != iNewState )
+ {
+ m_iLastState = (turretState_e)iNewState;
+
+ const char *pchScriptName = GetTurretTalkName( m_iLastState );
+
+ switch ( iNewState )
+ {
+ case TURRET_SEARCHING:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 1.75f;
+ break;
+
+ case TURRET_AUTO_SEARCHING:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 1.75f;
+ break;
+
+ /*case TURRET_ACTIVE:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 2.5f;
+ break;*/
+
+ // Suppress is too fleeting for a message
+ /*case TURRET_SUPPRESSING:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 1.5f;
+ break;*/
+
+ case TURRET_DEPLOYING:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 1.75f;
+ break;
+
+ case TURRET_RETIRING:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 3.5f;
+ break;
+
+ case TURRET_TIPPED:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 1.15f;
+ break;
+
+ case PORTAL_TURRET_PICKUP:
+ EmitSound( GetTurretTalkName( PORTAL_TURRET_PICKUP ) );
+ m_fNextTalk = gpGlobals->curtime + 2.25f;
+ break;
+
+ case PORTAL_TURRET_DISSOLVED:
+ EmitSound( pchScriptName );
+ m_fNextTalk = gpGlobals->curtime + 10.0f; // Never going to talk again
+ break;
+ }
+ }
+
+ // New states are not supported by old turret code
+ if ( iNewState != TURRET_TIPPED && iNewState < TURRET_STATE_TOTAL )
+ return BaseClass::PreThink( (turretState_e)iNewState );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire!
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy, bool bStrict )
+{
+ FireBulletsInfo_t info;
+
+ //if ( !bStrict && GetEnemy() == UTIL_PlayerByIndex( 1 ) )
+ CBaseEntity *pEnemy = GetEnemy();
+ if( !bStrict && (pEnemy && pEnemy->IsPlayer()) )
+ {
+ Vector vecDir = GetActualShootTrajectory( vecSrc );
+
+ info.m_vecSrc = vecSrc;
+ info.m_vecDirShooting = vecDir;
+ info.m_iTracerFreq = 1;
+ info.m_iShots = 1;
+ info.m_pAttacker = this;
+ info.m_vecSpread = GetAttackSpread( NULL, GetEnemy() );
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_iAmmoType = m_iAmmoType;
+ }
+ else
+ {
+ // Just shoot where you're facing!
+ Vector vecMuzzle, vecMuzzleDir;
+
+ GetAttachment( m_iMuzzleAttachment, vecMuzzle, &vecMuzzleDir );
+
+ info.m_vecSrc = vecSrc;
+ info.m_vecDirShooting = vecMuzzleDir;
+ info.m_iTracerFreq = 1;
+ info.m_iShots = 1;
+ info.m_pAttacker = this;
+ info.m_vecSpread = GetAttackSpread( NULL, GetEnemy() );
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_iAmmoType = m_iAmmoType;
+ }
+
+ info.m_flDamageForceScale = ( ( !m_bDamageForce ) ? ( 0.0f ) : ( TURRET_FLOOR_BULLET_FORCE_MULTIPLIER ) );
+
+ int iBarrelIndex = ( m_bShootWithBottomBarrels ) ? ( 2 ) : ( 0 );
+ QAngle angBarrelDir;
+
+ // Shoot out of the left barrel if there's nothing solid between the turret's center and the muzzle
+ trace_t tr;
+ GetAttachment( m_iBarrelAttachments[ iBarrelIndex ], info.m_vecSrc, angBarrelDir );
+ Vector vecCenter = GetAbsOrigin();
+ UTIL_TraceLine( vecCenter, info.m_vecSrc, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( !tr.m_pEnt || !tr.m_pEnt->IsWorld() )
+ {
+ FireBullets( info );
+ }
+
+ // Shoot out of the right barrel if there's nothing solid between the turret's center and the muzzle
+ GetAttachment( m_iBarrelAttachments[ iBarrelIndex + 1 ], info.m_vecSrc, angBarrelDir );
+ vecCenter = GetAbsOrigin();
+ UTIL_TraceLine( vecCenter, info.m_vecSrc, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( !tr.m_pEnt || !tr.m_pEnt->IsWorld() )
+ {
+ FireBullets( info );
+ }
+
+ // Flip shooting from the top or bottom
+ m_bShootWithBottomBarrels = !m_bShootWithBottomBarrels;
+
+ EmitSound( "NPC_FloorTurret.ShotSounds" );
+ DoMuzzleFlash();
+
+ // Make ropes shake if they exist
+ for ( int iRope = 0; iRope < PORTAL_FLOOR_TURRET_NUM_ROPES; ++iRope )
+ {
+ if ( m_hRopes[ iRope ] )
+ {
+ m_hRopes[ iRope ]->ShakeRopes( vecSrc, 32.0f, 5.0f );
+ }
+ }
+
+ // If a turret is partially tipped the recoil with each shot so that it can knock itself over
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ if ( up.z < 0.9f )
+ {
+ m_pMotionController->Suspend( 2.0f );
+
+ IPhysicsObject *pTurretPhys = VPhysicsGetObject();
+ Vector vVelocityImpulse = info.m_vecDirShooting * -35.0f;
+ pTurretPhys->AddVelocity( &vVelocityImpulse, &vVelocityImpulse );
+ }
+
+ if ( m_iLastState == TURRET_ACTIVE && gpGlobals->curtime > m_fNextTalk )
+ {
+ EmitSound( GetTurretTalkName( m_iLastState ) );
+ m_fNextTalk = gpGlobals->curtime + 2.5f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the state of the glowing eye attached to the turret
+// Input : state - state the eye should be in
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::SetEyeState( eyeState_t state )
+{
+ // Must have a valid eye to affect
+ if ( !m_hEyeGlow )
+ {
+ // Create our eye sprite
+ m_hEyeGlow = CSprite::SpriteCreate( FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false );
+ if ( !m_hEyeGlow )
+ return;
+
+ m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation );
+ m_hEyeGlow->SetAttachment( this, m_iEyeAttachment );
+ }
+
+ bool bNewState = ( m_iEyeState != state );
+
+ m_iEyeState = state;
+
+ //Set the state
+ switch( state )
+ {
+ default:
+ case TURRET_EYE_SEE_TARGET: //Fade in and scale up
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetBrightness( 255, 0.1f );
+ m_hEyeGlow->SetScale( 0.4f, 0.1f );
+ break;
+
+ case TURRET_EYE_SEEKING_TARGET: //Ping-pongs
+
+ //Toggle our state
+ m_bBlinkState = !m_bBlinkState;
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+
+ if ( m_bBlinkState )
+ {
+ //Fade up and scale up
+ m_hEyeGlow->SetScale( 0.3f, 0.1f );
+ m_hEyeGlow->SetBrightness( 224, 0.1f );
+ }
+ else
+ {
+ //Fade down and scale down
+ m_hEyeGlow->SetScale( 0.2f, 0.1f );
+ m_hEyeGlow->SetBrightness( 192, 0.1f );
+ }
+
+ break;
+
+ case TURRET_EYE_DORMANT: //Fade out and scale down
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetScale( 0.2f, 0.5f );
+ m_hEyeGlow->SetBrightness( 192, 0.5f );
+ break;
+
+ case TURRET_EYE_DEAD: //Fade out slowly
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetScale( 0.1f, 3.0f );
+ m_hEyeGlow->SetBrightness( 0, 3.0f );
+
+ if ( bNewState )
+ m_nSkin = 1;
+ break;
+
+ case TURRET_EYE_DISABLED:
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetScale( 0.1f, 1.0f );
+ m_hEyeGlow->SetBrightness( 0, 1.0f );
+ break;
+ }
+}
+
+inline bool CNPC_Portal_FloorTurret::OnSide( void )
+{
+ if ( GetWaterLevel() > 0 )
+ return true;
+
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ return ( DotProduct( up, Vector(0,0,1) ) < 0.5f );
+}
+
+float CNPC_Portal_FloorTurret::GetAttackDamageScale( CBaseEntity *pVictim )
+{
+ CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
+
+ // Do extra damage to antlions & combine
+ if ( pBCC )
+ {
+ if ( pBCC->Classify() == CLASS_PLAYER )
+ {
+ // Does normal damage when thrashing
+ if ( OnSide() )
+ return 1.0f;
+
+ return TURRET_FLOOR_DAMAGE_MULTIPLIER;
+ }
+ }
+
+ return BaseClass::GetAttackDamageScale( pVictim );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_Portal_FloorTurret::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
+{
+ WeaponProficiency_t weaponProficiency = WEAPON_PROFICIENCY_AVERAGE;
+
+ // Switch our weapon proficiency based upon our target
+ if ( pTarget )
+ {
+ if ( pTarget->Classify() == CLASS_PLAYER || pTarget->Classify() == CLASS_ANTLION || pTarget->Classify() == CLASS_ZOMBIE )
+ {
+ // Make me much more accurate
+ weaponProficiency = WEAPON_PROFICIENCY_PERFECT;
+ }
+ else if ( pTarget->Classify() == CLASS_COMBINE )
+ {
+ // Make me more accurate
+ weaponProficiency = WEAPON_PROFICIENCY_VERY_GOOD;
+ }
+ }
+
+ return VECTOR_CONE_4DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ weaponProficiency ].spreadscale);
+}
+
+void CNPC_Portal_FloorTurret::Retire( void )
+{
+ LaserOn();
+
+ BaseClass::Retire();
+}
+
+void CNPC_Portal_FloorTurret::Deploy( void )
+{
+ LaserOn();
+ RopesOn();
+
+ BaseClass::Deploy();
+}
+
+void CNPC_Portal_FloorTurret::ActiveThink( void )
+{
+ LaserOn();
+
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_ACTIVE ) )
+ return;
+
+ HackFindEnemy();
+
+ //Update our think time
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ //If we've become inactive, go back to searching
+ if ( ( m_bActive == false ) || ( pEnemy == NULL ) )
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+ SetThink( &CNPC_FloorTurret::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+ return;
+ }
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = pEnemy->BodyTarget( vecMid );
+
+ // Store off our last seen location so we can suppress it later
+ m_vecEnemyLKP = vecMidEnemy;
+
+ //Look for our current enemy
+ bool bEnemyInFOV = FInViewCone( pEnemy );
+ bool bEnemyVisible = FVisible( pEnemy ) && pEnemy->IsAlive();
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+ m_flDistToEnemy = VectorNormalize( vecDirToEnemy );
+
+ // If the enemy isn't in the normal fov, check the fov through portals
+ CProp_Portal *pPortal = NULL;
+ if ( pEnemy->IsAlive() )
+ {
+ pPortal = FInViewConeThroughPortal( pEnemy );
+
+ if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) )
+ {
+ // Translate our target across the portal
+ Vector vecMidEnemyTransformed;
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed );
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid;
+ float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed );
+
+ // If it's not visible through normal means or the enemy is closer through the portal, use the translated info
+ if ( !bEnemyInFOV || !bEnemyVisible || flDistToEnemyTransformed < m_flDistToEnemy )
+ {
+ bEnemyInFOV = true;
+ bEnemyVisible = true;
+ vecMidEnemy = vecMidEnemyTransformed;
+ vecDirToEnemy = vecDirToEnemyTransformed;
+ m_flDistToEnemy = flDistToEnemyTransformed;
+ }
+ else
+ {
+ pPortal = NULL;
+ }
+ }
+ else
+ {
+ pPortal = NULL;
+ }
+ }
+
+ //Draw debug info
+ if ( g_debug_turret.GetBool() )
+ {
+ NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
+ NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
+ NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 );
+
+ NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
+ NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
+ NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f );
+ }
+
+ //See if they're past our FOV of attack
+ if ( bEnemyInFOV == false )
+ {
+ // Should we look for a new target?
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+
+ if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE )
+ {
+ // Retire quickly in this case. (The case where we saw the player, but he hid again).
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
+ }
+ else
+ {
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+ }
+
+ SetThink( &CNPC_FloorTurret::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+
+ SpinDown();
+
+ return;
+ }
+
+ //Current enemy is not visible
+ if ( ( bEnemyVisible == false ) || ( m_flDistToEnemy > PORTAL_FLOOR_TURRET_RANGE ))
+ {
+ m_flLastSight = gpGlobals->curtime + 2.0f;
+
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+ SetThink( &CNPC_FloorTurret::SuppressThink );
+
+ return;
+ }
+
+ if ( g_debug_turret.GetBool() )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ // Visualize vertical firing ranges
+ for ( int i = 0; i < 4; i++ )
+ {
+ QAngle angMaxDownPitch = GetAbsAngles();
+
+ switch( i )
+ {
+ case 0: angMaxDownPitch.x -= 15; break;
+ case 1: angMaxDownPitch.x += 15; break;
+ case 2: angMaxDownPitch.x -= 25; break;
+ case 3: angMaxDownPitch.x += 25; break;
+ default:
+ break;
+ }
+
+ Vector vecMaxDownPitch;
+ AngleVectors( angMaxDownPitch, &vecMaxDownPitch );
+ NDebugOverlay::Line( vecMuzzle, vecMuzzle + (vecMaxDownPitch*256), 255, 255, 255, false, 0.1 );
+ }
+ }
+
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D();
+ Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D();
+
+ bool bCanShoot = true;
+ float minCos3d = DOT_10DEGREE; // 10 degrees slop
+
+ if ( m_flDistToEnemy < 60.0 )
+ {
+ vecDirToEnemy2D.NormalizeInPlace();
+ vecMuzzleDir2D.NormalizeInPlace();
+
+ bCanShoot = ( vecDirToEnemy2D.Dot(vecMuzzleDir2D) >= DOT_10DEGREE );
+ minCos3d = 0.7071; // 45 degrees
+ }
+
+ //Fire the gun
+ if ( bCanShoot ) // 10 degree slop XY
+ {
+ float dot3d = DotProduct( vecDirToEnemy, vecMuzzleDir );
+
+ if( m_bOutOfAmmo )
+ {
+ DryFire();
+ }
+ else
+ {
+ if ( dot3d >= minCos3d )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ SetActivity( (Activity)( ( m_bShootWithBottomBarrels ) ? ( ACT_FLOOR_TURRET_FIRE2 ) : ( ACT_FLOOR_TURRET_FIRE ) ) );
+
+ //Fire the weapon
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecMuzzleDir, (dot3d < DOT_10DEGREE) );
+#endif
+ }
+ }
+ }
+ }
+ else
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ }
+
+ //If we can see our enemy, face it
+ if ( bEnemyVisible )
+ {
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vEnemyWorldSpaceCenter = pEnemy->WorldSpaceCenter();
+ if ( pPortal && pPortal->IsActivedAndLinked() )
+ {
+ // Translate our target across the portal
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyWorldSpaceCenter, vEnemyWorldSpaceCenter );
+ }
+
+ Vector vecDirToEnemyEyes = vEnemyWorldSpaceCenter - vecMid;
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+ }
+
+ //Turn to face
+ UpdateFacing();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Target doesn't exist or has eluded us, so search for one
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::SearchThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_SEARCHING ) )
+ return;
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ //If our enemy has died, pick a new enemy
+ if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
+ {
+ SetEnemy( NULL );
+ }
+
+ //Acquire the target
+ if ( GetEnemy() == NULL )
+ {
+ HackFindEnemy();
+ }
+
+ LaserOn();
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ //If we've found a target, spin up the barrel and start to attack
+ if ( pEnemy != NULL )
+ {
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = pEnemy->BodyTarget( vecMid );
+
+ //Look for our current enemy
+ bool bEnemyInFOV = FInViewCone( pEnemy );
+ bool bEnemyVisible = FVisible( pEnemy );
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+ m_flDistToEnemy = VectorNormalize( vecDirToEnemy );
+
+ // If the enemy isn't in the normal fov, check the fov through portals
+ CProp_Portal *pPortal = NULL;
+ pPortal = FInViewConeThroughPortal( pEnemy );
+
+ if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) )
+ {
+ // Translate our target across the portal
+ Vector vecMidEnemyTransformed;
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed );
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid;
+ float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed );
+
+ // If it's not visible through normal means or the enemy is closer through the portal, use the translated info
+ if ( !bEnemyInFOV || !bEnemyVisible || flDistToEnemyTransformed < m_flDistToEnemy )
+ {
+ bEnemyInFOV = true;
+ bEnemyVisible = true;
+ vecMidEnemy = vecMidEnemyTransformed;
+ vecDirToEnemy = vecDirToEnemyTransformed;
+ m_flDistToEnemy = flDistToEnemyTransformed;
+ }
+ }
+
+ // Give enemies that are farther away a longer grace period
+ float fDistanceRatio = m_flDistToEnemy / PORTAL_FLOOR_TURRET_RANGE;
+ m_flShotTime = gpGlobals->curtime + fDistanceRatio * fDistanceRatio * PORTAL_FLOOR_TURRET_MAX_SHOT_DELAY;
+
+ m_flLastSight = 0;
+ SetThink( &CNPC_FloorTurret::ActiveThink );
+ SetEyeState( TURRET_EYE_SEE_TARGET );
+
+ SpinUp();
+
+ if ( gpGlobals->curtime > m_flNextActivateSoundTime )
+ {
+ EmitSound( "NPC_FloorTurret.Activate" );
+ m_flNextActivateSoundTime = gpGlobals->curtime + 3.0;
+ }
+ return;
+ }
+
+ //Are we out of time and need to retract?
+ if ( gpGlobals->curtime > m_flLastSight )
+ {
+ //Before we retrace, make sure that we are spun down.
+ m_flLastSight = 0;
+ SetThink( &CNPC_FloorTurret::Retire );
+ return;
+ }
+
+ //Display that we're scanning
+ m_vecGoalAngles.x = GetAbsAngles().x + ( sin( ( m_flLastSight + gpGlobals->curtime * m_fSearchSpeed ) * 1.5f ) * 20.0f );
+ m_vecGoalAngles.y = GetAbsAngles().y + ( sin( ( m_flLastSight + gpGlobals->curtime * m_fSearchSpeed ) * 2.5f ) * 20.0f );
+
+ //Turn and ping
+ UpdateFacing();
+ Ping();
+
+ // Update rope positions
+ for ( int iRope = 0; iRope < PORTAL_FLOOR_TURRET_NUM_ROPES; ++iRope )
+ {
+ if ( m_hRopes[ iRope ] )
+ {
+ m_hRopes[ iRope ]->EndpointsChanged();
+ }
+ }
+}
+
+void CNPC_Portal_FloorTurret::AutoSearchThink( void )
+{
+ LaserOn();
+ RopesOff();
+
+ BaseClass::AutoSearchThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret has been tipped over and will thrash for awhile
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::TippedThink( void )
+{
+ PreThink( TURRET_TIPPED );
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ SetEnemy( NULL );
+
+ StudioFrameAdvance();
+ // If we're not on side anymore, stop thrashing
+ if ( !OnSide() && VPhysicsGetObject()->GetContactPoint( NULL, NULL ) )
+ {
+ ReturnToLife();
+ return;
+ }
+
+ LaserOn();
+ RopesOn();
+
+ //See if we should continue to thrash
+ if ( gpGlobals->curtime < m_flThrashTime && !IsDissolving() )
+ {
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ if( m_bOutOfAmmo )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ DryFire();
+ }
+ else
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+ GetAttachment( m_iMuzzleAttachment, vecMuzzle, &vecMuzzleDir );
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ SetActivity( (Activity)( ( m_bShootWithBottomBarrels ) ? ( ACT_FLOOR_TURRET_FIRE2 ) : ( ACT_FLOOR_TURRET_FIRE ) ) );
+
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecMuzzleDir );
+#endif
+ }
+
+ m_flShotTime = gpGlobals->curtime + 0.05f;
+ }
+
+ m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat( -60, 60 );
+ m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat( -60, 60 );
+
+ UpdateFacing();
+ }
+ else
+ {
+ //Face forward
+ m_vecGoalAngles = GetAbsAngles();
+
+ //Set ourselves to close
+ if ( GetActivity() != ACT_FLOOR_TURRET_CLOSE )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ //If we're done moving to our desired facing, close up
+ if ( UpdateFacing() == false )
+ {
+ //Make any last death noises and anims
+ EmitSound( "NPC_FloorTurret.Die" );
+ EmitSound( GetTurretTalkName( PORTAL_TURRET_DISABLED ) );
+ SpinDown();
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSE );
+ EmitSound( "NPC_FloorTurret.Retract" );
+
+ CTakeDamageInfo info;
+ info.SetDamage( 1 );
+ info.SetDamageType( DMG_CRUSH );
+ Event_Killed( info );
+ }
+ }
+ else if ( IsActivityFinished() )
+ {
+ m_bActive = false;
+ m_flLastSight = 0;
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSED_IDLE );
+
+ // Don't need to store last NPC anymore, because I've been knocked over
+ if ( m_hLastNPCToKickMe )
+ {
+ m_hLastNPCToKickMe = NULL;
+ m_flKnockOverFailedTime = 0;
+ }
+
+ //Try to look straight
+ if ( UpdateFacing() == false )
+ {
+ m_OnTipped.FireOutput( this, this );
+ SetEyeState( TURRET_EYE_DEAD );
+ //SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
+
+ // Start thinking slowly to see if we're ever set upright somehow
+ SetThink( &CNPC_FloorTurret::InactiveThink );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ RopesOff();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret has been tipped over and will thrash for awhile
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::HeldThink( void )
+{
+ PreThink( (turretState_e)PORTAL_TURRET_PICKUP );
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ SetEnemy( NULL );
+
+ StudioFrameAdvance();
+
+ IPhysicsObject *pTurretPhys = VPhysicsGetObject();
+
+ // If we're not held anymore, stop thrashing
+ if ( !(pTurretPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
+ {
+ m_fNextTalk = gpGlobals->curtime + 1.25f;
+
+ if ( m_lifeState == LIFE_ALIVE )
+ SetThink( &CNPC_FloorTurret::ActiveThink );
+ else
+ SetThink( &CNPC_FloorTurret::InactiveThink );
+ }
+
+ LaserOn();
+ RopesOn();
+
+ //See if we should continue to thrash
+ if ( !IsDissolving() )
+ {
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ DryFire();
+
+ m_flShotTime = gpGlobals->curtime + RandomFloat( 0.25f, 0.75f );
+
+ m_vecGoalAngles.x = GetAbsAngles().x + RandomFloat( -15, 15 );
+ m_vecGoalAngles.y = GetAbsAngles().y + RandomFloat( -40, 40 );
+ }
+
+ UpdateFacing();
+ }
+}
+
+void CNPC_Portal_FloorTurret::InactiveThink( void )
+{
+ LaserOff();
+ RopesOff();
+
+ // Update our PVS state
+ CheckPVSCondition();
+
+ SetNextThink( gpGlobals->curtime + 1.0f );
+
+ // Wake up if we're not on our side
+ if ( !OnSide() && VPhysicsGetObject()->GetContactPoint( NULL, NULL ) && m_bEnabled )
+ {
+ // Never return to life!
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ //ReturnToLife();
+ }
+ else
+ {
+ IPhysicsObject *pTurretPhys = VPhysicsGetObject();
+
+ if ( !(pTurretPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) && pTurretPhys->IsAsleep() )
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
+ else
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ }
+}
+
+void CNPC_Portal_FloorTurret::SuppressThink( void )
+{
+ LaserOn();
+
+ BaseClass::SuppressThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret is not doing anything at all
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::DisabledThink( void )
+{
+ LaserOff();
+ RopesOff();
+
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ if ( OnSide() )
+ {
+ m_OnTipped.FireOutput( this, this );
+ SetEyeState( TURRET_EYE_DEAD );
+ //SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
+ SetThink( NULL );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret doesn't run base AI properly, which is a bad decision.
+// As a result, it has to manually find enemies.
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::HackFindEnemy( void )
+{
+ // We have to refresh our memories before finding enemies, so
+ // dead enemies are cleared out before new ones are added.
+ GetEnemies()->RefreshMemories();
+
+ GetSenses()->Look( PORTAL_FLOOR_TURRET_RANGE );
+ SetEnemy( BestEnemy() );
+
+ if ( GetEnemy() == NULL )
+ {
+ // Look through the list of sensed objects for possible targets
+ AISightIter_t iter;
+ CBaseEntity *pObject;
+ CBaseEntity *pNearest = NULL;
+ float flClosestDistSqr = PORTAL_FLOOR_TURRET_RANGE * PORTAL_FLOOR_TURRET_RANGE;
+
+ for ( pObject = GetSenses()->GetFirstSeenEntity( &iter, SEEN_MISC ); pObject; pObject = GetSenses()->GetNextSeenEntity( &iter ) )
+ {
+ Vector vVelocity;
+ pObject->GetVelocity( &vVelocity );
+
+ // Ignore objects going too slowly
+ if ( vVelocity.LengthSqr() < m_fMovingTargetThreashold )
+ continue;
+
+ float flDistSqr = pObject->WorldSpaceCenter().DistToSqr( GetAbsOrigin() );
+ if ( flDistSqr < flClosestDistSqr )
+ {
+ flClosestDistSqr = flDistSqr;
+ pNearest = pObject;
+ }
+ }
+
+ if ( pNearest )
+ {
+ SetEnemy( pNearest );
+ m_fMovingTargetThreashold += gpGlobals->curtime * 15.0f;
+ if ( m_fMovingTargetThreashold > 800.0f )
+ {
+ m_fMovingTargetThreashold = 800.0f;
+ }
+ }
+ }
+ else
+ {
+ m_fMovingTargetThreashold = 20.0f;
+ }
+}
+
+void CNPC_Portal_FloorTurret::StartTouch( CBaseEntity *pOther )
+{
+ BaseClass::StartTouch( pOther );
+
+ IPhysicsObject *pOtherPhys = pOther->VPhysicsGetObject();
+
+ if ( !pOtherPhys )
+ return;
+
+ if ( !m_pMotionController )
+ return;
+
+ if ( m_pMotionController->Enabled() )
+ {
+ m_pMotionController->Suspend( 2.0f );
+
+ IPhysicsObject *pTurretPhys = VPhysicsGetObject();
+
+ if ( !pOther->IsPlayer() && pOther->GetMoveType() == MOVETYPE_VPHYSICS && !(pTurretPhys && ((pTurretPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) != 0)) )
+ {
+ // Get a lateral impulse
+ Vector vVelocityImpulse = GetAbsOrigin() - pOther->GetAbsOrigin();
+ vVelocityImpulse.z = 0.0f;
+ if ( vVelocityImpulse.IsZero() )
+ {
+ vVelocityImpulse.x = 1.0f;
+ vVelocityImpulse.y = 1.0f;
+ }
+ VectorNormalize( vVelocityImpulse );
+
+ // If impulse is too much along the forward or back axis, skew it
+ Vector vTurretForward, vTurretRight;
+ GetVectors( &vTurretForward, &vTurretRight, NULL );
+ float fForwardDotImpulse = vTurretForward.Dot( vVelocityImpulse );
+ if ( fForwardDotImpulse > 0.7f || fForwardDotImpulse < -0.7f )
+ {
+ vVelocityImpulse += vTurretRight;
+ VectorNormalize( vVelocityImpulse );
+ }
+
+ Vector vAngleImpulse( ( vTurretRight.Dot( vVelocityImpulse ) < 0.0f ) ? ( -1.6f ) : ( 1.6f ), RandomFloat( -0.5f, 0.5f ), RandomFloat( -0.5f, 0.5f ) );
+
+ vVelocityImpulse *= TURRET_FLOOR_PHYSICAL_FORCE_MULTIPLIER;
+ vAngleImpulse *= TURRET_FLOOR_PHYSICAL_FORCE_MULTIPLIER;
+ pTurretPhys->AddVelocity( &vVelocityImpulse, &vAngleImpulse );
+
+ // Check if another turret is hitting us
+ CNPC_Portal_FloorTurret *pPortalFloor = dynamic_cast<CNPC_Portal_FloorTurret*>( pOther );
+ if ( pPortalFloor && pPortalFloor->m_lifeState == LIFE_ALIVE )
+ {
+ Vector vTurretVelocity, vOtherVelocity;
+ pTurretPhys->GetVelocity( &vTurretVelocity, NULL );
+ pOtherPhys->GetVelocity( &vOtherVelocity, NULL );
+
+ // If it's moving faster
+ if ( vOtherVelocity.LengthSqr() > vTurretVelocity.LengthSqr() )
+ {
+ // Make the turret falling onto this one talk
+ pPortalFloor->EmitSound( GetTurretTalkName( PORTAL_TURRET_COLLIDE ) );
+ pPortalFloor->m_fNextTalk = gpGlobals->curtime + 1.2f;
+ pPortalFloor->m_bDelayTippedTalk = true;
+
+ // Delay out potential tipped talking so we can here the other turret talk
+ m_fNextTalk = gpGlobals->curtime + 0.6f;
+ m_bDelayTippedTalk = true;
+ }
+ }
+
+ if ( pPortalFloor && m_bEnabled && m_bLaserOn && !m_bOutOfAmmo )
+ {
+ // Award friendly fire achievement if we're a live turret being knocked over by another turret.
+ IGameEvent *event = gameeventmanager->CreateEvent( "turret_hit_turret" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ }
+ }
+}
+
+void CNPC_Portal_FloorTurret::LaserOff( void )
+{
+ m_bLaserOn = false;
+}
+
+void CNPC_Portal_FloorTurret::LaserOn( void )
+{
+ m_bLaserOn = true;
+}
+
+void CNPC_Portal_FloorTurret::RopesOn( void )
+{
+ for ( int iRope = 0; iRope < PORTAL_FLOOR_TURRET_NUM_ROPES; ++iRope )
+ {
+ // Make a rope if it doesn't exist
+ if ( !m_hRopes[ iRope ] )
+ {
+ CFmtStr str;
+
+ int iStartIndex = LookupAttachment( str.sprintf( "Wire%i_start", iRope + 1 ) );
+ int iEndIndex = LookupAttachment( str.sprintf( "Wire%i_end", iRope + 1 ) );
+
+ m_hRopes[ iRope ] = CRopeKeyframe::Create( this, this, iStartIndex, iEndIndex );
+ if ( m_hRopes[ iRope ] )
+ {
+ m_hRopes[ iRope ]->m_Width = 0.7;
+ m_hRopes[ iRope ]->m_nSegments = ROPE_MAX_SEGMENTS;
+ m_hRopes[ iRope ]->EnableWind( false );
+ m_hRopes[ iRope ]->SetupHangDistance( 9.0f );
+ m_hRopes[ iRope ]->m_bConstrainBetweenEndpoints = true;;
+ }
+ }
+ }
+}
+
+void CNPC_Portal_FloorTurret::RopesOff( void )
+{
+ for ( int iRope = 0; iRope < PORTAL_FLOOR_TURRET_NUM_ROPES; ++iRope )
+ {
+ // Remove rope if it's alive
+ if ( m_hRopes[ iRope ] )
+ {
+ UTIL_Remove( m_hRopes[ iRope ] );
+ m_hRopes[ iRope ] = NULL;
+ }
+ }
+}
+
+void CNPC_Portal_FloorTurret::FireBullet( const char *pTargetName )
+{
+ CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pTargetName );
+ if ( !pEnemy )
+ return;
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = pEnemy->BodyTarget( vecMid );
+
+ // Store off our last seen location so we can suppress it later
+ m_vecEnemyLKP = vecMidEnemy;
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+ m_flDistToEnemy = VectorNormalize( vecDirToEnemy );
+
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ Vector vecMuzzle, vecMuzzleDir;
+ GetAttachment( m_iMuzzleAttachment, vecMuzzle, &vecMuzzleDir );
+
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D();
+ Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D();
+
+ if ( m_flDistToEnemy < 60.0 )
+ {
+ vecDirToEnemy2D.NormalizeInPlace();
+ vecMuzzleDir2D.NormalizeInPlace();
+ }
+
+ //Fire the gun
+ if( m_bOutOfAmmo )
+ {
+ DryFire();
+ }
+ else
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ SetActivity( (Activity)( ( m_bShootWithBottomBarrels ) ? ( ACT_FLOOR_TURRET_FIRE2 ) : ( ACT_FLOOR_TURRET_FIRE ) ) );
+
+ //Fire the weapon
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecDirToEnemy, true );
+#endif
+ }
+
+ //If we can see our enemy, face it
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+
+ //Turn to face
+ UpdateFacing();
+
+ EmitSound( "NPC_FloorTurret.Alert" );
+ SetThink( &CNPC_FloorTurret::SuppressThink );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire a bullet in the specified direction
+//-----------------------------------------------------------------------------
+void CNPC_Portal_FloorTurret::InputFireBullet( inputdata_t &inputdata )
+{
+ FireBullet( inputdata.value.String() );
+}
diff --git a/game/server/portal/npc_portal_turret_ground.cpp b/game/server/portal/npc_portal_turret_ground.cpp
new file mode 100644
index 0000000..4f6200b
--- /dev/null
+++ b/game/server/portal/npc_portal_turret_ground.cpp
@@ -0,0 +1,255 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Combine gun turret that emerges from a trapdoor in the ground.
+//
+//=============================================================================//
+#include "cbase.h"
+#include "npc_turret_ground.h"
+#include "ammodef.h"
+#include "IEffects.h"
+#include "te_effect_dispatch.h"
+
+extern ConVar ai_newgroundturret;
+ConVar turret_ground_damage_multiplier( "turret_ground_damage_multiplier", "8.0", FCVAR_CHEAT );
+
+class CNPC_Portal_GroundTurret : public CNPC_GroundTurret
+{
+ DECLARE_CLASS( CNPC_Portal_GroundTurret, CNPC_GroundTurret );
+ DECLARE_DATADESC();
+
+private:
+
+ float m_fViewconeDegrees;
+
+public:
+
+ virtual void Spawn( void );
+
+ virtual float GetAttackDamageScale( CBaseEntity *pVictim );
+
+ virtual void Shoot();
+ virtual void Scan();
+
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+};
+
+BEGIN_DATADESC( CNPC_Portal_GroundTurret )
+ DEFINE_KEYFIELD( m_fViewconeDegrees, FIELD_FLOAT, "ConeOfFire" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_portal_turret_ground, CNPC_Portal_GroundTurret );
+
+
+void CNPC_Portal_GroundTurret::Spawn( void )
+{
+ Precache();
+
+ UTIL_SetModel( this, "models/combine_turrets/ground_turret.mdl" );
+
+ SetNavType( NAV_FLY );
+ SetSolid( SOLID_VPHYSICS );
+
+ SetBloodColor( DONT_BLEED );
+ m_iHealth = 125;
+ m_flFieldOfView = cos( ((m_fViewconeDegrees / 2.0f) * M_PI / 180.0f) );
+ m_NPCState = NPC_STATE_NONE;
+
+ m_vecSpread.x = 0.5;
+ m_vecSpread.y = 0.5;
+ m_vecSpread.z = 0.5;
+
+ CapabilitiesClear();
+
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ NPCInit();
+
+ CapabilitiesAdd( bits_CAP_SIMPLE_RADIUS_DAMAGE );
+
+ m_iAmmoType = GetAmmoDef()->Index( "PISTOL" );
+
+ m_pSmoke = NULL;
+
+ m_bHasExploded = false;
+ m_bEnabled = false;
+
+ if( ai_newgroundturret.GetBool() )
+ {
+ m_flSensingDist = 384;
+ SetDistLook( m_flSensingDist );
+ }
+ else
+ {
+ m_flSensingDist = 2048;
+ }
+
+ if( !GetParent() )
+ {
+ DevMsg("ERROR! npc_ground_turret with no parent!\n");
+ UTIL_Remove(this);
+ return;
+ }
+
+ m_flTimeNextShoot = gpGlobals->curtime;
+ m_flTimeNextPing = gpGlobals->curtime;
+
+ m_vecClosedPos = GetAbsOrigin();
+
+ StudioFrameAdvance();
+
+ Vector vecPos;
+
+ GetAttachment( "eyes", vecPos );
+ SetViewOffset( vecPos - GetAbsOrigin() );
+
+ GetAttachment( "light", vecPos );
+ m_vecLightOffset = vecPos - GetAbsOrigin();
+}
+
+float CNPC_Portal_GroundTurret::GetAttackDamageScale( CBaseEntity *pVictim )
+{
+ CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
+
+ // Do extra damage to antlions & combine
+ if ( pBCC )
+ {
+ if ( pBCC->Classify() == CLASS_PLAYER )
+ return turret_ground_damage_multiplier.GetFloat();
+ }
+
+ return BaseClass::GetAttackDamageScale( pVictim );
+}
+
+void CNPC_Portal_GroundTurret::Shoot()
+{
+ FireBulletsInfo_t info;
+
+ Vector vecSrc = EyePosition();
+ Vector vecDir;
+
+ GetVectors( &vecDir, NULL, NULL );
+
+ for( int i = 0 ; i < 1 ; i++ )
+ {
+ info.m_vecSrc = vecSrc;
+
+ if( i > 0 || !GetEnemy()->IsPlayer() )
+ {
+ // Subsequent shots or shots at non-players random
+ GetVectors( &info.m_vecDirShooting, NULL, NULL );
+ info.m_vecSpread = m_vecSpread;
+ }
+ else
+ {
+ // First shot is at the enemy.
+ info.m_vecDirShooting = GetActualShootTrajectory( vecSrc );
+ info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
+ }
+
+ info.m_iTracerFreq = 1;
+ info.m_iShots = 1;
+ info.m_pAttacker = this;
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_iAmmoType = m_iAmmoType;
+
+ FireBullets( info );
+
+ trace_t tr;
+ CTraceFilterSkipTwoEntities traceFilter( this, info.m_pAdditionalIgnoreEnt, COLLISION_GROUP_NONE );
+ Vector vecEnd = info.m_vecSrc + vecDir * info.m_flDistance;
+ AI_TraceLine( info.m_vecSrc, vecEnd, MASK_SHOT, &traceFilter, &tr );
+
+ if ( tr.m_pEnt && !tr.m_pEnt->IsPlayer() && ( vecDir * info.m_flDistance * tr.fraction ).Length() < 16.0f )
+ {
+ CTakeDamageInfo damageInfo;
+ damageInfo.SetAttacker( this );
+ damageInfo.SetDamageType( DMG_BULLET );
+ damageInfo.SetDamage( 20.0f );
+
+ TakeDamage( damageInfo );
+
+ EmitSound( "NPC_FloorTurret.DryFire" );
+ }
+ }
+
+ // Do the AR2 muzzle flash
+ CEffectData data;
+ data.m_nEntIndex = entindex();
+ data.m_nAttachmentIndex = LookupAttachment( "eyes" );
+ data.m_flScale = 1.0f;
+ data.m_fFlags = MUZZLEFLASH_COMBINE;
+ DispatchEffect( "MuzzleFlash", data );
+
+ EmitSound( "NPC_FloorTurret.ShotSounds" );
+
+ m_flTimeNextShoot = gpGlobals->curtime + 0.09;
+}
+
+void CNPC_Portal_GroundTurret::Scan( void )
+{
+ if( m_bSeeEnemy )
+ {
+ // Using a bool for this check because the condition gets wiped out by changing schedules.
+ return;
+ }
+
+ if( IsOpeningOrClosing() )
+ {
+ // Moving.
+ return;
+ }
+
+ if( !IsOpen() )
+ {
+ // Closed
+ return;
+ }
+
+ if( !UTIL_FindClientInPVS(edict()) )
+ {
+ return;
+ }
+
+ if( gpGlobals->curtime >= m_flTimeNextPing )
+ {
+ EmitSound( "NPC_FloorTurret.Ping" );
+ m_flTimeNextPing = gpGlobals->curtime + 1.0f;
+ }
+
+ QAngle scanAngle;
+ Vector forward;
+ Vector vecEye = GetAbsOrigin() + m_vecLightOffset;
+
+ // Draw the outer extents
+ scanAngle = GetAbsAngles();
+ scanAngle.y += (m_fViewconeDegrees / 2.0f);
+ AngleVectors( scanAngle, &forward, NULL, NULL );
+ ProjectBeam( vecEye, forward, 1, 30, 0.1 );
+
+ scanAngle = GetAbsAngles();
+ scanAngle.y -= (m_fViewconeDegrees / 2.0f);
+ AngleVectors( scanAngle, &forward, NULL, NULL );
+ ProjectBeam( vecEye, forward, 1, 30, 0.1 );
+
+ // Draw a sweeping beam
+ scanAngle = GetAbsAngles();
+ scanAngle.y += (m_fViewconeDegrees / 2.0f) * sin( gpGlobals->curtime * 6.0f );
+ AngleVectors( scanAngle, &forward, NULL, NULL );
+ ProjectBeam( vecEye, forward, 1, 30, 0.3 );
+}
+
+int CNPC_Portal_GroundTurret::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // Taking damage from myself, make sure it's fatal.
+ CTakeDamageInfo infoCopy = info;
+
+ if ( infoCopy.GetDamageType() == DMG_CRUSH )
+ {
+ infoCopy.SetDamage( GetHealth() );
+ infoCopy.SetDamageType( DMG_REMOVENORAGDOLL | DMG_GENERIC );
+ }
+
+ return BaseClass::BaseClass::OnTakeDamage_Alive( infoCopy );
+}
diff --git a/game/server/portal/npc_rocket_turret.cpp b/game/server/portal/npc_rocket_turret.cpp
new file mode 100644
index 0000000..6975f5c
--- /dev/null
+++ b/game/server/portal/npc_rocket_turret.cpp
@@ -0,0 +1,1413 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ai_senses.h"
+#include "ai_memory.h"
+#include "engine/IEngineSound.h"
+#include "Sprite.h"
+#include "IEffects.h"
+#include "prop_portal_shared.h"
+#include "te.h"
+#include "te_effect_dispatch.h"
+#include "soundenvelope.h" // for looping sound effects
+#include "portal_gamerules.h" // for difficulty settings
+#include "weapon_rpg.h"
+#include "explode.h"
+#include "smoke_trail.h" // smoke trailers on the rocket
+#include "physics_bone_follower.h" // For bone follower manager
+#include "physicsshadowclone.h" // For translating hit entities shadow clones to real ent
+
+//#include "ndebugoverlay.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define ROCKET_TURRET_RANGE 8192
+#define ROCKET_TURRET_EMITER_OFFSET 0.0
+#define ROCKET_TURRET_THINK_RATE 0.05
+#define ROCKET_TURRET_DEATH_EFFECT_TIME 1.5f
+#define ROCKET_TURRET_LOCKON_TIME 2.0f
+#define ROCKET_TURRET_HALF_LOCKON_TIME 1.0f
+#define ROCKET_TURRET_QUARTER_LOCKON_TIME 0.5f
+#define ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME 4.0f
+
+// For search thinks
+#define MAX_DIVERGENCE_X 30.0f
+#define MAX_DIVERGENCE_Y 15.0f
+
+#define ROCKET_TURRET_DECAL_NAME "decals/scorchfade"
+#define ROCKET_TURRET_MODEL_NAME "models/props_bts/rocket_sentry.mdl"
+#define ROCKET_TURRET_PROJECTILE_NAME "models/props_bts/rocket.mdl"
+
+#define ROCKET_TURRET_SOUND_LOCKING "NPC_RocketTurret.LockingBeep"
+#define ROCKET_TURRET_SOUND_LOCKED "NPC_FloorTurret.LockedBeep"
+
+#define ROCKET_PROJECTILE_FIRE_SOUND "NPC_FloorTurret.RocketFire"
+#define ROCKET_PROJECTILE_LOOPING_SOUND "NPC_FloorTurret.RocketFlyLoop"
+#define ROCKET_PROJECTILE_DEFAULT_LIFE 20.0
+
+//Spawnflags
+#define SF_ROCKET_TURRET_START_INACTIVE 0x00000001
+
+// These bones have physics shadows
+const char *pRocketTurretFollowerBoneNames[] =
+{
+ "Root",
+ "Base",
+ "Arm_1",
+ "Arm_2",
+ "Arm_3",
+ "Arm_4",
+ "Rot_LR",
+ "Rot_UD",
+ "Gun_casing",
+ "Gun_Barrel_01",
+ "gun_barrel_02",
+ "loader",
+ "missle_01",
+ "missle_02",
+ "panel",
+};
+
+
+class CNPC_RocketTurret : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_RocketTurret, CAI_BaseNPC );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+public:
+
+ CNPC_RocketTurret( void );
+ ~CNPC_RocketTurret( void );
+
+ void Precache( void );
+ void Spawn( void );
+ virtual void Activate( void );
+
+ virtual ITraceFilter* GetBeamTraceFilter( void );
+ void UpdateOnRemove( void );
+ bool CreateVPhysics( void );
+
+ // Think functions
+ void SearchThink( void ); // Lost Target, spaz out
+ void FollowThink( void ); // Found target, chase it
+ void LockingThink( void ); // Charge up effects
+ void FiringThink( void ); // Currently has rocket out
+ void DyingThink( void ); // Overloading, blowing up
+ void DeathThink( void ); // Destroyed, sparking
+ void OpeningThink ( void ); // Finish open/close animation before using pose params
+ void ClosingThink ( void );
+
+ // Inputs
+ void InputToggle( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+ void InputSetTarget( inputdata_t &inputdata );
+ void InputDestroy( inputdata_t &inputdata );
+
+ void RocketDied( void ); // After rocket hits something and self-destructs (or times out)
+ Class_T Classify( void )
+ {
+ if( m_bEnabled )
+ return CLASS_COMBINE;
+
+ return CLASS_NONE;
+ }
+
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ Vector EyeOffset( Activity nActivity )
+ {
+ return vec3_origin;
+ }
+
+ Vector EyePosition( void )
+ {
+ Vector vMuzzlePos;
+ GetAttachment( m_iMuzzleAttachment, vMuzzlePos, NULL, NULL, NULL );
+ return vMuzzlePos;
+ }
+
+protected:
+
+ bool PreThink( void );
+ void Toggle( void );
+ void Enable( void );
+ void Disable( void );
+ void SetTarget( CBaseEntity* pTarget );
+ void Destroy ( void );
+
+ float UpdateFacing( void );
+ void UpdateAimPoint( void );
+ void FireRocket( void );
+ void UpdateSkin( int nSkin );
+ void UpdateMuzzleMatrix ( void );
+
+ bool TestLOS( const Vector& vAimPoint );
+ bool TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint );
+ bool FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut );
+ void SyncPoseToAimAngles ( void );
+ void LaserOn ( void );
+ void LaserOff ( void );
+
+ bool m_bEnabled;
+ bool m_bHasSightOfEnemy;
+
+ QAngle m_vecGoalAngles;
+ CNetworkVar( QAngle, m_vecCurrentAngles );
+ QAngle m_vecAnglesToEnemy;
+
+ enum
+ {
+ ROCKET_SKIN_IDLE=0,
+ ROCKET_SKIN_LOCKING,
+ ROCKET_SKIN_LOCKED,
+
+ ROCKET_SKIN_COUNT,
+ };
+
+ Vector m_vecDirToEnemy;
+ float m_flDistToEnemy;
+
+ float m_flTimeSpentDying;
+ float m_flTimeLocking; // Period spent locking on to target
+ float m_flTimeLastFired; // Cooldown time between attacks
+
+ float m_flTimeSpentPaused; // for search think's movements
+ float m_flPauseLength;
+ float m_flTotalDivergenceX;
+ float m_flTotalDivergenceY;
+
+ matrix3x4_t m_muzzleToWorld;
+ int m_muzzleToWorldTick;
+
+ int m_iPosePitch;
+ int m_iPoseYaw;
+
+ // Contained Bone Follower manager
+ CBoneFollowerManager m_BoneFollowerManager;
+
+ // Model indices for effects
+ CNetworkVar( int, m_iLaserState );
+ CNetworkVar( int, m_nSiteHalo );
+
+ // Target indicator sprite info
+ int m_iMuzzleAttachment;
+ int m_iLightAttachment;
+
+ COutputEvent m_OnFoundTarget;
+ COutputEvent m_OnLostTarget;
+
+ CTraceFilterSkipTwoEntities m_filterBeams;
+
+ EHANDLE m_hCurRocket;
+
+};
+
+//Datatable
+BEGIN_DATADESC( CNPC_RocketTurret )
+
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecCurrentAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bHasSightOfEnemy, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecAnglesToEnemy, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecDirToEnemy, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flDistToEnemy, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeSpentDying, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeLocking, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeLastFired, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iLaserState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nSiteHalo, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flTimeSpentPaused, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPauseLength, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTotalDivergenceX, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTotalDivergenceY, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iPosePitch, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iPoseYaw, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hCurRocket, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iLightAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_muzzleToWorldTick, FIELD_INTEGER ),
+ DEFINE_FIELD( m_muzzleToWorld, FIELD_MATRIX3X4_WORLDSPACE ),
+// DEFINE_FIELD( m_filterBeams, CTraceFilterSkipTwoEntities ),
+
+ DEFINE_EMBEDDED( m_BoneFollowerManager ),
+
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( FollowThink ),
+ DEFINE_THINKFUNC( LockingThink ),
+ DEFINE_THINKFUNC( FiringThink ),
+ DEFINE_THINKFUNC( DyingThink ),
+ DEFINE_THINKFUNC( DeathThink ),
+ DEFINE_THINKFUNC( OpeningThink ),
+ DEFINE_THINKFUNC( ClosingThink ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ),
+
+ DEFINE_OUTPUT( m_OnFoundTarget, "OnFoundTarget" ),
+ DEFINE_OUTPUT( m_OnLostTarget, "OnLostTarget" ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST(CNPC_RocketTurret, DT_NPC_RocketTurret)
+
+ SendPropInt( SENDINFO( m_iLaserState ), 2 ),
+ SendPropInt( SENDINFO( m_nSiteHalo ) ),
+ SendPropVector( SENDINFO( m_vecCurrentAngles ) ),
+
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( npc_rocket_turret, CNPC_RocketTurret );
+
+
+
+// Projectile class for this weapon, a rocket
+class CRocket_Turret_Projectile : public CMissile
+{
+ DECLARE_CLASS( CRocket_Turret_Projectile, CMissile );
+ DECLARE_DATADESC();
+public:
+ void Precache( void );
+ void Spawn( void );
+
+ virtual void NotifyLauncherOnDeath( void );
+ virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
+ virtual void SetLauncher( EHANDLE hLauncher );
+ virtual void CreateSmokeTrail( void ); // overloaded from base
+
+ virtual void MissileTouch( CBaseEntity *pOther );
+
+ EHANDLE m_hLauncher;
+ CSoundPatch *m_pAmbientSound;
+protected:
+ virtual void DoExplosion( void );
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds ( void );
+
+};
+
+BEGIN_DATADESC( CRocket_Turret_Projectile )
+
+ DEFINE_FIELD( m_hLauncher, FIELD_EHANDLE ),
+
+ DEFINE_FUNCTION( MissileTouch ),
+ DEFINE_SOUNDPATCH( m_pAmbientSound ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( rocket_turret_projectile, CRocket_Turret_Projectile );
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_RocketTurret::CNPC_RocketTurret( void )
+ : m_filterBeams( NULL, NULL, COLLISION_GROUP_DEBRIS )
+{
+ m_bEnabled = false;
+ m_bHasSightOfEnemy = false;
+
+ m_vecGoalAngles.Init();
+ m_vecAnglesToEnemy.Init();
+ m_vecDirToEnemy.Init();
+
+ m_flTimeLastFired = m_flTimeLocking = m_flDistToEnemy = m_flTimeSpentDying = 0.0f;
+
+ m_iLightAttachment = m_iMuzzleAttachment = m_nSiteHalo = 0;
+
+ m_flTimeSpentPaused = m_flPauseLength = m_flTotalDivergenceX = m_flTotalDivergenceY = 0.0f;
+
+ m_hCurRocket = NULL;
+}
+
+CNPC_RocketTurret::~CNPC_RocketTurret( void )
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Precache( void )
+{
+ PrecacheModel("effects/bluelaser1.vmt");
+ m_nSiteHalo = PrecacheModel("sprites/light_glow03.vmt");
+
+ PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKING );
+ PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKED );
+ PrecacheScriptSound ( ROCKET_PROJECTILE_FIRE_SOUND );
+ PrecacheScriptSound ( ROCKET_PROJECTILE_LOOPING_SOUND );
+
+ UTIL_PrecacheDecal( ROCKET_TURRET_DECAL_NAME );
+ PrecacheModel( ROCKET_TURRET_MODEL_NAME );
+ PrecacheModel ( ROCKET_TURRET_PROJECTILE_NAME );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: the entity
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Spawn( void )
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ SetViewOffset( vec3_origin );
+
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ SetModel( ROCKET_TURRET_MODEL_NAME );
+ SetSolid( SOLID_VPHYSICS );
+
+ m_iMuzzleAttachment = LookupAttachment ( "barrel" );
+ m_iLightAttachment = LookupAttachment ( "eye" );
+
+ m_iPosePitch = LookupPoseParameter( "aim_pitch" );
+ m_iPoseYaw = LookupPoseParameter( "aim_yaw" );
+
+ m_vecCurrentAngles = m_vecGoalAngles = GetAbsAngles();
+
+ CreateVPhysics();
+
+ //Set our autostart state
+ m_bEnabled = ( ( m_spawnflags & SF_ROCKET_TURRET_START_INACTIVE ) == false );
+
+ // Set Locked sprite
+ if ( m_bEnabled )
+ {
+ m_iLaserState = 1;
+ SetSequence(LookupSequence("idle"));
+ }
+ else
+ {
+ m_iLaserState = 0;
+ SetSequence(LookupSequence("inactive"));
+ }
+ SetCycle(1.0f);
+ UpdateSkin( ROCKET_SKIN_IDLE );
+
+ SetPoseParameter( "aim_pitch", 0 );
+ SetPoseParameter( "aim_yaw", -180 );
+
+ if ( m_bEnabled )
+ {
+ SetThink( &CNPC_RocketTurret::FollowThink );
+ }
+
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+}
+
+bool CNPC_RocketTurret::CreateVPhysics( void )
+{
+ m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pRocketTurretFollowerBoneNames), pRocketTurretFollowerBoneNames );
+ BaseClass::CreateVPhysics();
+ return true;
+}
+
+void CNPC_RocketTurret::Activate( void )
+{
+ m_filterBeams.SetPassEntity( this );
+ m_filterBeams.SetPassEntity2( UTIL_GetLocalPlayer() );
+ BaseClass::Activate();
+}
+
+ITraceFilter* CNPC_RocketTurret::GetBeamTraceFilter( void )
+{
+ return &m_filterBeams;
+}
+
+void CNPC_RocketTurret::UpdateOnRemove( void )
+{
+ m_BoneFollowerManager.DestroyBoneFollowers();
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_RocketTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ CBaseEntity *pHitEntity = NULL;
+ if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
+ return true;
+
+ if (ppBlocker)
+ {
+ *ppBlocker = pHitEntity;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : void CNPC_RocketTurret::UpdateAimPoint
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::UpdateAimPoint ( void )
+{
+ //If we've become inactive
+ if ( ( m_bEnabled == false ) || ( GetEnemy() == NULL ) )
+ {
+ SetEnemy( NULL );
+ SetNextThink( TICK_NEVER_THINK );
+ m_vecGoalAngles = GetAbsAngles();
+ return;
+ }
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = GetEnemy()->GetAbsOrigin() + (GetEnemy()->WorldAlignMins() + GetEnemy()->WorldAlignMaxs()) * 0.5f;
+
+ //Calculate dir and dist to enemy
+ m_vecDirToEnemy = vecMidEnemy - vecMid;
+ m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy );
+ VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy );
+
+ bool bEnemyVisible = false;
+ if ( !(GetEnemy()->GetFlags() & FL_NOTARGET) )
+ {
+ bool bEnemyVisibleInWorld = FVisible( GetEnemy() );
+
+ // Test portals in our view as possible ways to view the player
+ bool bEnemyVisibleThroughPortal = TestPortalsForLOS( &vecMidEnemy, bEnemyVisibleInWorld );
+
+ bEnemyVisible = bEnemyVisibleInWorld || bEnemyVisibleThroughPortal;
+ }
+
+ //Store off our last seen location
+ UpdateEnemyMemory( GetEnemy(), vecMidEnemy );
+
+ if ( bEnemyVisible )
+ {
+ m_vecDirToEnemy = vecMidEnemy - vecMid;
+ m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy );
+ VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy );
+ }
+
+ //Current enemy is not visible
+ if ( ( bEnemyVisible == false ) || ( m_flDistToEnemy > ROCKET_TURRET_RANGE ) )
+ {
+ // Had LOS, just lost it
+ if ( m_bHasSightOfEnemy )
+ {
+ m_OnLostTarget.FireOutput( GetEnemy(), this );
+ }
+ m_bHasSightOfEnemy = false;
+ }
+
+ //If we can see our enemy
+ if ( bEnemyVisible )
+ {
+ // Had no LOS, just gained it
+ if ( !m_bHasSightOfEnemy )
+ {
+ m_OnFoundTarget.FireOutput( GetEnemy(), this );
+ }
+ m_bHasSightOfEnemy = true;
+ }
+}
+
+bool SignDiffers ( float f1, float f2 )
+{
+ return !( Sign(f1) == Sign(f2) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::SearchThink()
+{
+ if ( PreThink() || GetEnemy() == NULL )
+ return;
+
+ SetSequence ( LookupSequence( "idle" ) );
+ UpdateAimPoint();
+
+ //Update our think time
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+
+ // Still can't see enemy, zip around frantically
+ if ( !m_bHasSightOfEnemy )
+ {
+ if ( m_flTimeSpentPaused >= m_flPauseLength )
+ {
+ float flOffsetX = RandomFloat( -5.0f, 5.0f );
+ float flOffsetY = RandomFloat( -5.0f, 5.0f );
+
+ if ( fabs(m_flTotalDivergenceX) <= MAX_DIVERGENCE_X ||
+ SignDiffers( m_flTotalDivergenceX, flOffsetX ) )
+ {
+ m_flTotalDivergenceX += flOffsetX;
+ m_vecGoalAngles.x += flOffsetX;
+ }
+
+ if ( fabs(m_flTotalDivergenceY) <= MAX_DIVERGENCE_Y ||
+ SignDiffers( m_flTotalDivergenceY, flOffsetY ) )
+ {
+ m_flTotalDivergenceY += flOffsetY;
+ m_vecGoalAngles.y += flOffsetY;
+ }
+
+ // Reset pause timer
+ m_flTimeSpentPaused = 0.0f;
+ m_flPauseLength = RandomFloat( 0.3f, 2.5f );
+ }
+ m_flTimeSpentPaused += ROCKET_TURRET_THINK_RATE;
+ }
+ else
+ {
+ // Found target, go back to following it
+ SetThink( &CNPC_RocketTurret::FollowThink );
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+ }
+
+ // Move beam towards goal angles
+ UpdateFacing();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the turret to fire on targets if they're visible
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::FollowThink( void )
+{
+ // Default to player as enemy
+ if ( GetEnemy() == NULL )
+ {
+ SetEnemy( UTIL_GetLocalPlayer() );
+ }
+
+ SetSequence ( LookupSequence( "idle" ) );
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink() || GetEnemy() == NULL )
+ {
+ return;
+ }
+ //Update our think time
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+
+ UpdateAimPoint();
+
+ m_vecGoalAngles = m_vecAnglesToEnemy;
+
+ // Chase enemy
+ if ( !m_bHasSightOfEnemy )
+ {
+ // Aim at the last known location
+ m_vecGoalAngles = m_vecCurrentAngles;
+
+ // Lost sight, move to search think
+ SetThink( &CNPC_RocketTurret::SearchThink );
+ }
+
+ //Turn to face
+ UpdateFacing();
+
+ // If our facing direction hits our enemy, fire the beam
+ Ray_t rayDmg;
+ Vector vForward;
+ AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
+ Vector vEndPoint = EyePosition() + vForward*ROCKET_TURRET_RANGE;
+ rayDmg.Init( EyePosition(), vEndPoint );
+ rayDmg.m_IsRay = true;
+ trace_t traceDmg;
+
+ // This version reorients through portals
+ CTraceFilterSimple subfilter( this, COLLISION_GROUP_NONE );
+ CTraceFilterTranslateClones filter ( &subfilter );
+ float flRequiredParameter = 2.0f;
+ CProp_Portal* pFirstPortal = UTIL_Portal_FirstAlongRay( rayDmg, flRequiredParameter );
+ UTIL_Portal_TraceRay_Bullets( pFirstPortal, rayDmg, MASK_VISIBLE_AND_NPCS, &filter, &traceDmg, false );
+
+ if ( traceDmg.m_pEnt )
+ {
+ // This thing we're hurting is our enemy
+ if ( traceDmg.m_pEnt == GetEnemy() )
+ {
+ // If we're past the cooldown time, fire another rocket
+ if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME )
+ {
+ SetThink( &CNPC_RocketTurret::LockingThink );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Charge up, prepare to fire and give player time to dodge
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::LockingThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink() )
+ return;
+
+ //Turn to face
+ UpdateFacing();
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+
+ if ( m_flTimeLocking == 0.0f )
+ {
+ // Play lockon sound
+ EmitSound ( ROCKET_TURRET_SOUND_LOCKING );
+ EmitSound ( ROCKET_TURRET_SOUND_LOCKING, gpGlobals->curtime + ROCKET_TURRET_QUARTER_LOCKON_TIME );
+ EmitSound ( ROCKET_TURRET_SOUND_LOCKED, gpGlobals->curtime + ROCKET_TURRET_HALF_LOCKON_TIME );
+
+ ResetSequence(LookupSequence("load"));
+
+ // Change lockon sprite
+ UpdateSkin( ROCKET_SKIN_LOCKING );
+ }
+
+ m_flTimeLocking += ROCKET_TURRET_THINK_RATE;
+
+ if ( m_flTimeLocking > ROCKET_TURRET_LOCKON_TIME )
+ {
+ // Set Locked sprite to 'rocket out' color
+ UpdateSkin( ROCKET_SKIN_LOCKED );
+
+ FireRocket();
+ SetThink ( &CNPC_RocketTurret::FiringThink );
+ m_flTimeLocking = 0.0f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Charge up, deal damage along our facing direction.
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::FiringThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink() )
+ return;
+
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+ CRocket_Turret_Projectile* pRocket = dynamic_cast<CRocket_Turret_Projectile*>(m_hCurRocket.Get());
+
+ if ( pRocket )
+ {
+ // If this rocket has been out too long, detonate it and launch a new one
+ if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_PROJECTILE_DEFAULT_LIFE )
+ {
+ pRocket->ShotDown();
+ m_flTimeLastFired = gpGlobals->curtime;
+ SetThink( &CNPC_RocketTurret::FollowThink );
+ }
+ }
+ else
+ {
+ // Set Locked sprite
+ UpdateSkin( ROCKET_SKIN_IDLE );
+ // Rocket dead, or never created. Revert to follow think
+ m_flTimeLastFired = gpGlobals->curtime;
+ SetThink( &CNPC_RocketTurret::FollowThink );
+ }
+}
+
+void CNPC_RocketTurret::FireRocket ( void )
+{
+ UTIL_Remove( m_hCurRocket );
+
+ CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", EyePosition(), m_vecCurrentAngles, this );
+
+ if ( !pRocket )
+ return;
+
+ m_hCurRocket = pRocket;
+
+ Vector vForward;
+ AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
+
+ m_flTimeLastFired = gpGlobals->curtime;
+
+ EmitSound ( ROCKET_PROJECTILE_FIRE_SOUND );
+ ResetSequence(LookupSequence("fire"));
+
+ pRocket->SetThink( NULL );
+ pRocket->SetMoveType( MOVETYPE_FLY );
+
+ pRocket->CreateSmokeTrail();
+
+ pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME );
+ UTIL_SetSize( pRocket, vec3_origin, vec3_origin );
+
+ pRocket->SetAbsVelocity( vForward * 550 );
+ pRocket->SetLauncher ( this );
+}
+
+void CNPC_RocketTurret::UpdateSkin( int nSkin )
+{
+ m_nSkin = nSkin;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Rocket destructed, resume search behavior
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::RocketDied( void )
+{
+ // Set Locked sprite
+ UpdateSkin( ROCKET_SKIN_IDLE );
+
+ // Rocket dead, return to follow think
+ m_flTimeLastFired = gpGlobals->curtime;
+ SetThink( &CNPC_RocketTurret::FollowThink );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Show this rocket turret has overloaded with effects and noise for a period of time
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::DyingThink( void )
+{
+ // Make the beam graphics freak out a bit
+ m_iLaserState = 2;
+
+
+ UpdateSkin( ROCKET_SKIN_IDLE );
+
+ // If we've freaked out for long enough, be dead
+ if ( m_flTimeSpentDying > ROCKET_TURRET_DEATH_EFFECT_TIME )
+ {
+ Vector vForward;
+ AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
+ g_pEffects->EnergySplash( EyePosition(), vForward, true );
+
+ m_OnDeath.FireOutput( this, this );
+ SetThink( &CNPC_RocketTurret::DeathThink );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ m_flTimeSpentDying = 0.0f;
+ }
+
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+ m_flTimeSpentDying += ROCKET_TURRET_THINK_RATE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sparks and fizzes to show it's broken.
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::DeathThink( void )
+{
+ Vector vForward;
+ AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
+
+ m_iLaserState = 0;
+ SetEnemy( NULL );
+
+ g_pEffects->Sparks( EyePosition(), 1, 1, &vForward );
+ g_pEffects->Smoke( EyePosition(), 0, 6.0f, 20 );
+
+ SetNextThink( gpGlobals->curtime + RandomFloat( 2.0f, 8.0f ) );
+}
+
+void CNPC_RocketTurret::UpdateMuzzleMatrix()
+{
+ if ( gpGlobals->tickcount != m_muzzleToWorldTick )
+ {
+ m_muzzleToWorldTick = gpGlobals->tickcount;
+ GetAttachment( m_iMuzzleAttachment, m_muzzleToWorld );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Avoid aiming/drawing beams while opening and closing
+// Input : -
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::OpeningThink()
+{
+ StudioFrameAdvance();
+
+ // Require these poses for this animation
+ QAngle vecNeutralAngles ( 0, 90, 0 );
+ m_vecGoalAngles = m_vecCurrentAngles = vecNeutralAngles;
+ SyncPoseToAimAngles();
+
+ // Start following player after we're fully opened
+ float flCurProgress = GetCycle();
+ if ( flCurProgress >= 0.99f )
+ {
+
+ LaserOn();
+ SetThink( &CNPC_RocketTurret::FollowThink );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Avoid aiming/drawing beams while opening and closing
+// Input : -
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::ClosingThink()
+{
+ LaserOff();
+
+ // Require these poses for this animation
+ QAngle vecNeutralAngles ( 0, 90, 0 );
+ m_vecGoalAngles = vecNeutralAngles;
+
+ // Once we're within 10 degrees of the neutral pose, start close animation.
+ if ( UpdateFacing() <= 10.0f )
+ {
+ StudioFrameAdvance();
+ }
+
+ SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
+
+ // Start following player after we're fully opened
+ float flCurProgress = GetCycle();
+ if ( flCurProgress >= 0.99f )
+ {
+ SetThink( NULL );
+ SetNextThink( TICK_NEVER_THINK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : void SyncPoseToAimAngles
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::SyncPoseToAimAngles ( void )
+{
+ QAngle localAngles = TransformAnglesToLocalSpace( m_vecCurrentAngles.Get(), EntityToWorldTransform() );
+
+ // Update pitch
+ SetPoseParameter( m_iPosePitch, localAngles.x );
+
+ // Update yaw -- NOTE: This yaw movement is screwy for this model, we must invert the yaw delta and also skew an extra 90 deg to
+ // get the 'forward face' of the turret to match up with the look direction. If the model and it's pose parameters change, this will be wrong.
+ SetPoseParameter( m_iPoseYaw, AngleNormalize( -localAngles.y - 90 ) );
+
+ InvalidateBoneCache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes the turret to face its desired angles
+// Returns distance current and goal angles the angles in degrees.
+//-----------------------------------------------------------------------------
+float CNPC_RocketTurret::UpdateFacing( void )
+{
+ Quaternion qtCurrent ( m_vecCurrentAngles.Get() );
+ Quaternion qtGoal ( m_vecGoalAngles );
+ Quaternion qtOut;
+
+ float flDiff = QuaternionAngleDiff( qtCurrent, qtGoal );
+
+ // 1/10th degree is all the granularity we need, gives rocket player hit box width accuracy at 18k game units.
+ if ( flDiff < 0.1 )
+ return flDiff;
+
+ // Slerp 5% of the way to goal (distance dependant speed, but torque minimial and no euler wrapping issues).
+ QuaternionSlerp( qtCurrent, qtGoal, 0.05, qtOut );
+
+ QAngle vNewAngles;
+ QuaternionAngles( qtOut, vNewAngles );
+
+ m_vecCurrentAngles = vNewAngles;
+
+ SyncPoseToAimAngles();
+
+ return flDiff;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tests if this prop's front point will have direct line of sight to it's target entity once the pose parameters are set to face it
+// Input : vAimPoint - The point to aim at
+// Output : Returns true if target is in direct line of sight, false otherwise.
+//-----------------------------------------------------------------------------
+bool CNPC_RocketTurret::TestLOS( const Vector& vAimPoint )
+{
+ // Snap to face (for accurate traces)
+ QAngle vecOldAngles = m_vecCurrentAngles.m_Value;
+ Vector vecToAimPoint = vAimPoint - EyePosition();
+ VectorAngles( vecToAimPoint, m_vecCurrentAngles.m_Value );
+ SyncPoseToAimAngles();
+
+ Vector vFaceOrigin = EyePosition();
+ trace_t trTarget;
+ Ray_t ray;
+ ray.Init( vFaceOrigin, vAimPoint );
+ ray.m_IsRay = true;
+
+ // This aim point does hit target, now make sure there are no blocking objects in the way
+ CTraceFilterSimple filter ( this, COLLISION_GROUP_NONE );
+ UTIL_Portal_TraceRay( ray, MASK_VISIBLE_AND_NPCS, &filter, &trTarget, false );
+
+ // Set model back to current facing
+ m_vecCurrentAngles = vecOldAngles;
+ SyncPoseToAimAngles();
+
+ return ( trTarget.m_pEnt == GetEnemy() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tests all portals in the turret's vis for possible routes to see it's target point
+// Input : pOutVec - The location to aim at in order to hit the target ent, choosing least rotation if multiple
+// bConsiderNonPortalAimPoint - Output in pOutVec the non portal (direct) aimpoint if it requires the least rotation
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_RocketTurret::TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint = false )
+{
+ // Aim at the target through the world
+ CBaseEntity* pTarget = GetEnemy();
+ if ( !pTarget )
+ {
+ return false;
+ }
+ Vector vAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount == 0 )
+ {
+ *pOutVec = vAimPoint;
+ return false;
+ }
+
+ Vector vCurAim;
+ AngleVectors( m_vecCurrentAngles.m_Value, &vCurAim );
+ vCurAim.NormalizeInPlace();
+
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ Vector *portalAimPoints = (Vector *)stackalloc( sizeof( Vector ) * iPortalCount );
+ bool *bUsable = (bool *)stackalloc( sizeof( bool ) * iPortalCount );
+ float *fPortalDot = (float *)stackalloc( sizeof( float ) * iPortalCount );
+
+ // Test through any active portals: This may be a shorter distance to the target
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+
+ if( !pTempPortal->m_bActivated ||
+ (pTempPortal->m_hLinkedPortal.Get() == NULL) )
+ {
+ //portalAimPoints[i] = vec3_invalid;
+ bUsable[i] = false;
+ continue;
+ }
+
+
+ bUsable[i] = FindAimPointThroughPortal( pPortals[ i ], &portalAimPoints[ i ] );
+ if ( 1 )
+ {
+ QAngle goalAngles;
+ Vector vecToEnemy = portalAimPoints[ i ] - EyePosition();
+ vecToEnemy.NormalizeInPlace();
+
+ // This value is for choosing the easiest aim point for the turret to see through.
+ // 'Easiest' is the least rotation needed.
+ fPortalDot[i] = DotProduct( vecToEnemy, vCurAim );
+ }
+ }
+
+
+ int iCountPortalsThatSeeTarget = 0;
+
+ float fHighestDot = -1.0;
+ if ( bConsiderNonPortalAimPoint )
+ {
+ QAngle enemyRotToFace;
+ Vector vecToEnemy = vAimPoint - EyePosition();
+ vecToEnemy.NormalizeInPlace();
+
+ fHighestDot = DotProduct( vecToEnemy, vCurAim );
+ }
+
+ // Compare aim points, use the closest aim point which has direct LOS
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ if( bUsable[i] )
+ {
+ // This aim point has direct LOS
+ if ( TestLOS( portalAimPoints[ i ] ) && fHighestDot < fPortalDot[ i ] )
+ {
+ *pOutVec = portalAimPoints[ i ];
+ fHighestDot = fPortalDot[ i ];
+
+ ++iCountPortalsThatSeeTarget;
+ }
+ }
+ }
+
+ return (iCountPortalsThatSeeTarget != 0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the center of the target entity as seen through the specified portal
+// Input : pPortal - The portal to look through
+// Output : Vector& output point in world space where the target *appears* to be as seen through the portal
+//-----------------------------------------------------------------------------
+bool CNPC_RocketTurret::FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut )
+{
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ CProp_Portal* pLinked = pPortal->m_hLinkedPortal.Get();
+ CBaseEntity* pTarget = GetEnemy();
+
+ // Require that the portal is facing towards the beam to test through it
+ Vector vRocketToPortal, vPortalForward;
+ VectorSubtract ( pPortal->GetAbsOrigin(), EyePosition(), vRocketToPortal );
+ pPortal->GetVectors( &vPortalForward, NULL, NULL);
+ float fDot = DotProduct( vRocketToPortal, vPortalForward );
+
+ // Portal must be facing the turret, and have a linked partner
+ if ( fDot < 0.0f && pLinked && pLinked->m_bActivated && pTarget )
+ {
+ VMatrix matToPortalView = pLinked->m_matrixThisToLinked;
+ Vector vTargetAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f;
+ *pVecOut = matToPortalView * vTargetAimPoint;
+ return true;
+ }
+ }
+
+ // Bad portal pointer, not linked, no target or otherwise failed
+ return false;
+}
+
+void CNPC_RocketTurret::LaserOn( void )
+{
+ // Set Locked sprite
+ m_iLaserState = 1;
+}
+
+void CNPC_RocketTurret::LaserOff( void )
+{
+ // Set Locked sprite;
+ m_iLaserState = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the turret is currently in
+//-----------------------------------------------------------------------------
+bool CNPC_RocketTurret::PreThink( void )
+{
+ StudioFrameAdvance();
+ CheckPVSCondition();
+
+ //Do not interrupt current think function
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the turret's state
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Toggle( void )
+{
+ //Toggle the state
+ if ( m_bEnabled )
+ {
+ Disable();
+ }
+ else
+ {
+ Enable();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Enable the turret and deploy
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Enable( void )
+{
+ if ( m_bEnabled )
+ return;
+
+ m_bEnabled = true;
+ ResetSequence( LookupSequence("open") );
+
+ SetThink( &CNPC_RocketTurret::OpeningThink );
+ SetNextThink( gpGlobals->curtime + 0.05 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Retire the turret until enabled again
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Disable( void )
+{
+ if ( !m_bEnabled )
+ return;
+
+ UpdateSkin( ROCKET_SKIN_IDLE );
+
+ m_bEnabled = false;
+ ResetSequence(LookupSequence("close"));
+
+ SetThink( &CNPC_RocketTurret::ClosingThink );
+ SetNextThink( gpGlobals->curtime + 0.05 );
+ SetEnemy( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the enemy of this rocket
+// Input : pTarget - the enemy to set
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::SetTarget( CBaseEntity* pTarget )
+{
+ SetEnemy( pTarget );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Explode and set death think
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::Destroy( void )
+{
+ SetThink( &CNPC_RocketTurret::DyingThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::InputToggle( inputdata_t &inputdata )
+{
+ Toggle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+void CNPC_RocketTurret::InputSetTarget( inputdata_t &inputdata )
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, NULL );
+ SetTarget( pTarget );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays some 'death' effects and sets the destroy think
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CNPC_RocketTurret::InputDestroy( inputdata_t &inputdata )
+{
+ Destroy();
+}
+
+
+//-----------------------------------------------------------------------------
+// Projectile methods
+//-----------------------------------------------------------------------------
+void CRocket_Turret_Projectile::Spawn( void )
+{
+ Precache();
+ BaseClass::Spawn();
+ SetTouch ( &CRocket_Turret_Projectile::MissileTouch );
+ CreateSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CRocket_Turret_Projectile::MissileTouch( CBaseEntity *pOther )
+{
+ Assert( pOther );
+ Vector vVel = GetAbsVelocity();
+
+ // Touched a launcher, and is heading towards that launcher
+ if ( FClassnameIs( pOther, "npc_rocket_turret" ) )
+ {
+ Dissolve( NULL, gpGlobals->curtime + 0.1f, false, ENTITY_DISSOLVE_NORMAL );
+ Vector vBounceVel = Vector( -vVel.x, -vVel.y, 200 );
+ SetAbsVelocity ( vBounceVel * 0.1f );
+ QAngle vBounceAngles;
+ VectorAngles( vBounceVel, vBounceAngles );
+ SetAbsAngles ( vBounceAngles );
+ SetLocalAngularVelocity ( QAngle ( 180, 90, 45 ) );
+ UTIL_Remove ( m_hRocketTrail );
+
+ SetSolid ( SOLID_NONE );
+
+ if( m_hRocketTrail )
+ {
+ m_hRocketTrail->SetLifetime(0.1f);
+ m_hRocketTrail = NULL;
+ }
+
+ return;
+ }
+
+ // Don't touch triggers (but DO hit weapons)
+ if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON )
+ return;
+
+ Explode();
+}
+
+
+void CRocket_Turret_Projectile::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( ROCKET_PROJECTILE_LOOPING_SOUND );
+}
+
+void CRocket_Turret_Projectile::NotifyLauncherOnDeath( void )
+{
+ CNPC_RocketTurret* pLauncher = (CNPC_RocketTurret*)m_hLauncher.Get();
+
+ if ( pLauncher )
+ {
+ pLauncher->RocketDied();
+ }
+}
+
+// When teleported (usually by portal)
+void CRocket_Turret_Projectile::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // On teleport, we record a pointer to the portal we are arriving at
+ if ( eventType == NOTIFY_EVENT_TELEPORT )
+ {
+ // HACK: Clearing the owner allows collisions with launcher.
+ // Players have had trouble realizing a launcher's own rockets don't kill it
+ // because they didn't ever collide. We do this after a portal teleport so it avoids self-collisions on launch.
+ SetOwnerEntity( NULL );
+
+ // Restart smoke trail
+ UTIL_Remove( m_hRocketTrail );
+ m_hRocketTrail = NULL; // This shouldn't leak cause the pointer has been handed to the delete list
+ CreateSmokeTrail();
+ }
+}
+
+void CRocket_Turret_Projectile::SetLauncher ( EHANDLE hLauncher )
+{
+ m_hLauncher = hLauncher;
+}
+
+void CRocket_Turret_Projectile::DoExplosion( void )
+{
+ NotifyLauncherOnDeath();
+
+ StopLoopingSounds();
+
+ // Explode
+ ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), 200, 25,
+ SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 100.0f, this);
+
+ // Hackish: Knock turrets in the area
+ CBaseEntity* pTurretIter = NULL;
+
+ while ( (pTurretIter = gEntList.FindEntityByClassnameWithin( pTurretIter, "npc_portal_turret_floor", GetAbsOrigin(), 128 )) != NULL )
+ {
+ CTakeDamageInfo info( this, this, 200, DMG_BLAST );
+ info.SetDamagePosition( GetAbsOrigin() );
+ CalculateExplosiveDamageForce( &info, (pTurretIter->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() );
+
+ pTurretIter->VPhysicsTakeDamage( info );
+ }
+}
+
+void CRocket_Turret_Projectile::CreateSounds()
+{
+ if (!m_pAmbientSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pAmbientSound = controller.SoundCreate( filter, entindex(), ROCKET_PROJECTILE_LOOPING_SOUND );
+ controller.Play( m_pAmbientSound, 1.0, 100 );
+ }
+}
+
+
+void CRocket_Turret_Projectile::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pAmbientSound );
+ m_pAmbientSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+void CRocket_Turret_Projectile::CreateSmokeTrail( void )
+{
+ if ( m_hRocketTrail )
+ return;
+
+ // Smoke trail.
+ if ( (m_hRocketTrail = RocketTrail::CreateRocketTrail()) != NULL )
+ {
+ m_hRocketTrail->m_Opacity = 0.2f;
+ m_hRocketTrail->m_SpawnRate = 100;
+ m_hRocketTrail->m_ParticleLifetime = 0.8f;
+ m_hRocketTrail->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
+ m_hRocketTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
+ m_hRocketTrail->m_StartSize = 8;
+ m_hRocketTrail->m_EndSize = 32;
+ m_hRocketTrail->m_SpawnRadius = 4;
+ m_hRocketTrail->m_MinSpeed = 2;
+ m_hRocketTrail->m_MaxSpeed = 16;
+
+ m_hRocketTrail->SetLifetime( 999 );
+ m_hRocketTrail->FollowEntity( this, "0" );
+ }
+}
+
+
+
+
+
+
+static void fire_rocket_projectile_f( void )
+{
+ CBasePlayer *pPlayer = (CBasePlayer *)UTIL_GetCommandClient();
+
+ Vector ptEyes, vForward;
+ QAngle vLookAng;
+ ptEyes = pPlayer->EyePosition();
+ pPlayer->EyeVectors( &vForward );
+ vLookAng = pPlayer->EyeAngles();
+
+ CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", ptEyes, vLookAng, pPlayer );
+
+ if ( !pRocket )
+ return;
+
+ pRocket->SetThink( NULL );
+ pRocket->SetMoveType( MOVETYPE_FLY );
+
+ pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME );
+ UTIL_SetSize( pRocket, vec3_origin, vec3_origin );
+
+ pRocket->CreateSmokeTrail();
+
+ pRocket->SetAbsVelocity( vForward * 550 );
+ pRocket->SetLauncher ( NULL );
+}
+
+ConCommand fire_rocket_projectile( "fire_rocket_projectile", fire_rocket_projectile_f, "Fires a rocket turret projectile from the player's eyes for testing.", FCVAR_CHEAT ); \ No newline at end of file
diff --git a/game/server/portal/npc_security_camera.cpp b/game/server/portal/npc_security_camera.cpp
new file mode 100644
index 0000000..1c06a0a
--- /dev/null
+++ b/game/server/portal/npc_security_camera.cpp
@@ -0,0 +1,1167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ai_senses.h"
+#include "ai_memory.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "Sprite.h"
+#include "hl2/hl2_player.h"
+#include "soundenvelope.h"
+#include "explode.h"
+#include "IEffects.h"
+#include "animation.h"
+#include "props.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "basehlcombatweapon_shared.h"
+#include "iservervehicle.h"
+#include "physics_prop_ragdoll.h"
+#include "portal_util_shared.h"
+#include "prop_portal.h"
+#include "portal_player.h"
+#include "world.h"
+#include "ai_baseactor.h" // for Glados ent playing VCDs
+#include "sceneentity.h" // precacheing vcds
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define SECURITY_CAMERA_MODEL "models/props/security_camera.mdl"
+#define SECURITY_CAMERA_BC_YAW "aim_yaw"
+#define SECURITY_CAMERA_BC_PITCH "aim_pitch"
+#define SECURITY_CAMERA_RANGE 1500
+#define SECURITY_CAMERA_SPREAD VECTOR_CONE_2DEGREES
+#define SECURITY_CAMERA_MAX_WAIT 5
+#define SECURITY_CAMERA_PING_TIME 1.0f //LPB!!
+
+#define SECURITY_CAMERA_NUM_ROPES 2
+#define SECURITY_CAMERA_GLOW_SPRITE "sprites/glow1.vmt"
+
+//Aiming variables
+#define SECURITY_CAMERA_MAX_NOHARM_PERIOD 0.0f
+#define SECURITY_CAMERA_MAX_GRACE_PERIOD 3.0f
+
+//Spawnflags
+#define SF_SECURITY_CAMERA_AUTOACTIVATE 0x00000020
+#define SF_SECURITY_CAMERA_STARTINACTIVE 0x00000040
+#define SF_SECURITY_CAMERA_NEVERRETIRE 0x00000080
+#define SF_SECURITY_CAMERA_OUT_OF_AMMO 0x00000100
+
+#define CAMERA_DESTROYED_SCENE_1 "scenes/general/generic_security_camera_destroyed-1.vcd"
+#define CAMERA_DESTROYED_SCENE_2 "scenes/general/generic_security_camera_destroyed-2.vcd"
+#define CAMERA_DESTROYED_SCENE_3 "scenes/general/generic_security_camera_destroyed-3.vcd"
+#define CAMERA_DESTROYED_SCENE_4 "scenes/general/generic_security_camera_destroyed-4.vcd"
+#define CAMERA_DESTROYED_SCENE_5 "scenes/general/generic_security_camera_destroyed-5.vcd"
+
+//Heights
+#define SECURITY_CAMERA_YAW_SPEED 7.0f
+
+#define SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN 33
+
+//Turret states
+enum turretState_e
+{
+ TURRET_SEARCHING,
+ TURRET_AUTO_SEARCHING,
+ TURRET_ACTIVE,
+ TURRET_DEPLOYING,
+ TURRET_RETIRING,
+ TURRET_DEAD,
+};
+
+// Forces glados actor to play reaction scenes when player dismounts camera.
+void PlayDismountSounds( void );
+
+
+//
+// Security Camera
+//
+
+class CNPC_SecurityCamera : public CNPCBaseInteractive<CAI_BaseNPC>, public CDefaultPlayerPickupVPhysics
+{
+ DECLARE_CLASS( CNPC_SecurityCamera, CNPCBaseInteractive<CAI_BaseNPC> );
+public:
+
+ CNPC_SecurityCamera( void );
+ ~CNPC_SecurityCamera( void );
+
+ void Precache( void );
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ bool CreateVPhysics( void );
+ virtual void UpdateOnRemove( void );
+ virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
+ virtual int ObjectCaps( void );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ // Think functions
+ void Retire( void );
+ void Deploy( void );
+ void ActiveThink( void );
+ void SearchThink( void );
+ void DeathThink( void );
+
+ // Inputs
+ void InputToggle( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+ void InputRagdoll( inputdata_t &inputdata );
+
+ void SetLastSightTime();
+
+ int OnTakeDamage( const CTakeDamageInfo &inputInfo );
+ virtual void PlayerPenetratingVPhysics( void );
+ bool OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
+
+ bool ShouldSavePhysics() { return true; }
+
+ virtual bool CanBeAnEnemyOf( CBaseEntity *pEnemy );
+
+ Class_T Classify( void )
+ {
+ if( m_bEnabled )
+ return CLASS_COMBINE;
+
+ return CLASS_NONE;
+ }
+
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ Vector EyeOffset( Activity nActivity )
+ {
+ Vector vForward;
+
+ GetVectors( &vForward, 0, 0 );
+
+ return vForward * 10.0f;
+ }
+
+ Vector EyePosition( void )
+ {
+ return GetAbsOrigin() + EyeOffset(GetActivity());
+ }
+
+
+protected:
+
+ bool PreThink( turretState_e state );
+ void Ping( void );
+ void Toggle( void );
+ void Enable( void );
+ void Disable( void );
+
+ void RopesOn( void );
+ void RopesOff( void );
+ void EyeOn( void );
+ void EyeOff( void );
+
+ bool UpdateFacing( void );
+
+private:
+
+ CHandle<CRopeKeyframe> m_hRopes[ SECURITY_CAMERA_NUM_ROPES ];
+ CHandle<CSprite> m_hEyeGlow;
+
+ bool m_bAutoStart;
+ bool m_bActive; //Denotes the turret is deployed and looking for targets
+ bool m_bBlinkState;
+ bool m_bEnabled; //Denotes whether the turret is able to deploy or not
+
+ float m_flLastSight;
+ float m_flPingTime;
+
+ QAngle m_vecGoalAngles;
+ QAngle m_vecCurrentAngles;
+ Vector m_vNoisePos;
+ int m_iTicksTillNextNoise;
+
+ CSoundPatch *m_pMovementSound;
+
+ COutputEvent m_OnDeploy;
+ COutputEvent m_OnRetire;
+
+ DECLARE_DATADESC();
+};
+
+//Datatable
+BEGIN_DATADESC( CNPC_SecurityCamera )
+
+ DEFINE_ARRAY( m_hRopes, FIELD_EHANDLE, SECURITY_CAMERA_NUM_ROPES ),
+ DEFINE_FIELD( m_hEyeGlow, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
+ DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecCurrentAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vNoisePos, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iTicksTillNextNoise, FIELD_INTEGER ),
+
+ DEFINE_SOUNDPATCH( m_pMovementSound ),
+
+ DEFINE_THINKFUNC( Retire ),
+ DEFINE_THINKFUNC( Deploy ),
+ DEFINE_THINKFUNC( ActiveThink ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( DeathThink ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Ragdoll", InputRagdoll ),
+
+ DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ),
+ DEFINE_OUTPUT( m_OnRetire, "OnRetire" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_security_camera, CNPC_SecurityCamera );
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_SecurityCamera::CNPC_SecurityCamera( void )
+{
+ m_bActive = false;
+ m_bAutoStart = false;
+ m_flPingTime = 0;
+ m_flLastSight = 0;
+ m_bBlinkState = false;
+ m_bEnabled = false;
+ m_vecCurrentAngles = QAngle( 0.0f, 0.0f, 0.0f );
+
+ m_vecGoalAngles.Init();
+ m_vNoisePos = Vector( 0.0f, 0.0f, 0.0f );
+ m_iTicksTillNextNoise = 5;
+
+ m_pMovementSound = NULL;
+ m_hEyeGlow = NULL;
+}
+
+CNPC_SecurityCamera::~CNPC_SecurityCamera( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Precache( void )
+{
+ PrecacheModel( SECURITY_CAMERA_MODEL );
+
+ PrecacheScriptSound( "Portalgun.pedestal_rotate_loop" );
+
+ // Scenes for when the player dismounts a security camera. Spoken only if Aperture_AI actor is in the
+ PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_1 );
+ PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_2 );
+ PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_3 );
+ PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_4 );
+ PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_5 );
+
+ BaseClass::Precache();
+}
+
+void CNPC_SecurityCamera::CreateSounds()
+{
+ if (!m_pMovementSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pMovementSound = controller.SoundCreate( filter, entindex(), "Portalgun.pedestal_rotate_loop" );
+ controller.Play( m_pMovementSound, 0, 100 );
+ }
+}
+
+void CNPC_SecurityCamera::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pMovementSound );
+ m_pMovementSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn the entity
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Spawn( void )
+{
+ Precache();
+
+ SetModel( SECURITY_CAMERA_MODEL );
+
+ BaseClass::Spawn();
+
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ SetViewOffset( EyeOffset( ACT_IDLE ) );
+ m_flFieldOfView = VIEW_FIELD_FULL;
+ m_takedamage = DAMAGE_NO;
+ m_iHealth = 1000;
+ m_bloodColor = BLOOD_COLOR_MECH;
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ SetCollisionBounds( Vector( -16.0f, -16.0f, -16.0f ), Vector( 16.0f, 16.0f, 16.0f ) );
+
+ RemoveFlag( FL_AIMTARGET );
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ SetPoseParameter( SECURITY_CAMERA_BC_YAW, 0 );
+ SetPoseParameter( SECURITY_CAMERA_BC_PITCH, 0 );
+
+ //Set our autostart state
+ m_bAutoStart = !!( m_spawnflags & SF_SECURITY_CAMERA_AUTOACTIVATE );
+ m_bEnabled = ( ( m_spawnflags & SF_SECURITY_CAMERA_STARTINACTIVE ) == false );
+
+ //Do we start active?
+ if ( m_bAutoStart && m_bEnabled )
+ {
+ SetThink( &CNPC_SecurityCamera::SearchThink );
+ }
+
+ //Stagger our starting times
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) );
+
+ CreateVPhysics();
+}
+
+void CNPC_SecurityCamera::Activate( void )
+{
+ BaseClass::Activate();
+
+ CreateSounds();
+
+ RopesOn();
+ EyeOn();
+}
+
+bool CNPC_SecurityCamera::CreateVPhysics( void )
+{
+ IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_NOT_STANDABLE, false );
+ if ( !pPhysics )
+ DevMsg( "npc_turret_floor unable to spawn physics object!\n" );
+ else
+ pPhysics->EnableMotion( false );
+
+ return true;
+}
+
+void CNPC_SecurityCamera::UpdateOnRemove( void )
+{
+ RopesOff();
+ EyeOff();
+
+ BaseClass::UpdateOnRemove();
+}
+
+void CNPC_SecurityCamera::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // On teleport, we record a pointer to the portal we are arriving at
+ if ( eventType == NOTIFY_EVENT_TELEPORT )
+ {
+ RopesOff();
+ RopesOn();
+ }
+
+ BaseClass::NotifySystemEvent( pNotify, eventType, params );
+}
+
+int CNPC_SecurityCamera::ObjectCaps( void )
+{
+ IPhysicsObject *pPhysics = VPhysicsGetObject();
+ if ( !pPhysics || !pPhysics->IsMotionEnabled() )
+ return BaseClass::ObjectCaps();
+
+ return ( BaseClass::ObjectCaps() | FCAP_USE_IN_RADIUS | FCAP_USE_ONGROUND | FCAP_IMPULSE_USE );
+}
+
+void CNPC_SecurityCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( pActivator );
+ if ( pPlayer )
+ pPlayer->PickupObject( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_SecurityCamera::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ if ( !m_takedamage )
+ return 0;
+
+ CTakeDamageInfo info = inputInfo;
+
+ if ( m_bActive == false )
+ info.ScaleDamage( 0.1f );
+
+ m_iHealth -= info.GetDamage();
+
+ if ( m_iHealth <= 0 )
+ {
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+
+ RemoveFlag( FL_NPC ); // why are they set in the first place???
+
+ ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false );
+ SetThink( &CNPC_SecurityCamera::DeathThink );
+
+ StopSound( "NPC_SecurityCamera.Alert" );
+
+ m_OnDamaged.FireOutput( info.GetInflictor(), this );
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ return 0;
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We override this code because otherwise we start to move into the
+// tricky realm of player avoidance. Since we don't go through the
+// normal NPC thinking but we ARE an NPC (...) we miss a bunch of
+// book keeping. This means we can become invisible and then never
+// reappear.
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::PlayerPenetratingVPhysics( void )
+{
+ // We don't care!
+}
+
+bool CNPC_SecurityCamera::OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ return !m_bActive;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shut down
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Retire( void )
+{
+ if ( PreThink( TURRET_RETIRING ) )
+ return;
+
+ //Level out the turret
+ m_vecGoalAngles = GetAbsAngles();
+ SetNextThink( gpGlobals->curtime );
+
+ //Set ourselves to close
+ if ( m_bActive )
+ {
+ //Notify of the retraction
+ m_OnRetire.FireOutput( NULL, this );
+ }
+
+ m_bActive = false;
+ m_flLastSight = 0;
+
+ SetThink( &CNPC_SecurityCamera::SUB_DoNothing );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start up
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Deploy( void )
+{
+ if ( PreThink( TURRET_DEPLOYING ) )
+ return;
+
+ m_vecGoalAngles = GetAbsAngles();
+
+ SetNextThink( gpGlobals->curtime );
+
+ if ( !m_bActive )
+ {
+ m_bActive = true;
+
+ //Notify we're deploying
+ m_OnDeploy.FireOutput( NULL, this );
+ }
+
+ m_flPlaybackRate = 0;
+ SetThink( &CNPC_SecurityCamera::SearchThink );
+
+ //EmitSound( "NPC_SecurityCamera.Move" );
+
+ SetLastSightTime();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::SetLastSightTime()
+{
+ if( HasSpawnFlags( SF_SECURITY_CAMERA_NEVERRETIRE ) )
+ {
+ m_flLastSight = FLT_MAX;
+ }
+ else
+ {
+ m_flLastSight = gpGlobals->curtime + SECURITY_CAMERA_MAX_WAIT;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes the turret to face its desired angles
+//-----------------------------------------------------------------------------
+bool CNPC_SecurityCamera::UpdateFacing( void )
+{
+ bool bMoved = false;
+
+ if ( m_vecCurrentAngles.x < m_vecGoalAngles.x )
+ {
+ m_vecCurrentAngles.x += SECURITY_CAMERA_YAW_SPEED;
+
+ if ( m_vecCurrentAngles.x > m_vecGoalAngles.x )
+ m_vecCurrentAngles.x = m_vecGoalAngles.x;
+
+ bMoved = true;
+ }
+
+ if ( m_vecCurrentAngles.y < m_vecGoalAngles.y )
+ {
+ m_vecCurrentAngles.y += SECURITY_CAMERA_YAW_SPEED;
+
+ if ( m_vecCurrentAngles.y > m_vecGoalAngles.y )
+ m_vecCurrentAngles.y = m_vecGoalAngles.y;
+
+ bMoved = true;
+ }
+
+ if ( m_vecCurrentAngles.x > m_vecGoalAngles.x )
+ {
+ m_vecCurrentAngles.x -= SECURITY_CAMERA_YAW_SPEED;
+
+ if ( m_vecCurrentAngles.x < m_vecGoalAngles.x )
+ m_vecCurrentAngles.x = m_vecGoalAngles.x;
+
+ bMoved = true;
+ }
+
+ if ( m_vecCurrentAngles.y > m_vecGoalAngles.y )
+ {
+ m_vecCurrentAngles.y -= SECURITY_CAMERA_YAW_SPEED;
+
+ if ( m_vecCurrentAngles.y < m_vecGoalAngles.y )
+ m_vecCurrentAngles.y = m_vecGoalAngles.y;
+
+ bMoved = true;
+ }
+
+ if ( bMoved )
+ {
+ if ( m_pMovementSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pMovementSound, RandomFloat( 0.7f, 0.9f ), 0.05f );
+ }
+
+ // Update pitch
+ int iPose = LookupPoseParameter( SECURITY_CAMERA_BC_PITCH );
+ SetPoseParameter( iPose, m_vecCurrentAngles.x );
+
+ // Update yaw
+ iPose = LookupPoseParameter( SECURITY_CAMERA_BC_YAW );
+ SetPoseParameter( iPose, m_vecCurrentAngles.y );
+
+ InvalidateBoneCache();
+ }
+ else
+ {
+ if ( m_pMovementSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pMovementSound, 0.0f, 0.05f );
+ }
+ }
+
+ return bMoved;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_SecurityCamera::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ CBaseEntity *pHitEntity = NULL;
+ if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
+ return true;
+
+ // If we hit something that's okay to hit anyway, still fire
+ if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
+ {
+ if (IRelationType(pHitEntity) == D_HT)
+ return true;
+ }
+
+ if (ppBlocker)
+ {
+ *ppBlocker = pHitEntity;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the turret to fire on targets if they're visible
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::ActiveThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_ACTIVE ) )
+ return;
+
+ //Update our think time
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ //If we've become inactive, go back to searching
+ if ( m_bActive == false || !pEnemy )
+ {
+ SetEnemy( NULL );
+ SetLastSightTime();
+ SetThink( &CNPC_SecurityCamera::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+ return;
+ }
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = pEnemy->GetAbsOrigin();
+
+ //Store off our last seen location
+ UpdateEnemyMemory( pEnemy, vecMidEnemy );
+
+ //Look for our current enemy
+ bool bEnemyVisible = pEnemy->IsAlive() && FInViewCone( pEnemy ) && FVisible( pEnemy );
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+ float flDistToEnemy = VectorNormalize( vecDirToEnemy );
+
+ CProp_Portal *pPortal = NULL;
+
+ if ( pEnemy->IsAlive() )
+ {
+ pPortal = FInViewConeThroughPortal( pEnemy );
+
+ if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) )
+ {
+ // Translate our target across the portal
+ Vector vecMidEnemyTransformed;
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed );
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid;
+ float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed );
+
+ // If it's not visible through normal means or the enemy is closer through the portal, use the translated info
+ if ( !bEnemyVisible || flDistToEnemyTransformed < flDistToEnemy )
+ {
+ bEnemyVisible = true;
+ vecMidEnemy = vecMidEnemyTransformed;
+ vecDirToEnemy = vecDirToEnemyTransformed;
+ flDistToEnemy = flDistToEnemyTransformed;
+ }
+ else
+ {
+ pPortal = NULL;
+ }
+ }
+ else
+ {
+ pPortal = NULL;
+ }
+ }
+
+ // Add noise to the look position
+ --m_iTicksTillNextNoise;
+
+ if ( m_iTicksTillNextNoise <= 0 && flDistToEnemy < 256.0f )
+ {
+ m_vNoisePos.x = RandomFloat( -8.0f, 8.0f );
+ m_vNoisePos.y = RandomFloat( -8.0f, 8.0f );
+ m_vNoisePos.z = RandomFloat( 0.0f, 32.0f );
+
+ m_iTicksTillNextNoise = RandomInt( 5, 30 );
+ }
+
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vEnemyEyes = pEnemy->EyePosition();
+
+ if ( pPortal )
+ {
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyEyes, vEnemyEyes );
+ }
+
+ Vector vecDirToEnemyEyes = ( vEnemyEyes + m_vNoisePos ) - vecMid;
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ Vector vForward, vRight, vUp;
+ GetVectors( &vForward, &vRight, &vUp );
+
+ vecAnglesToEnemy.x = acosf( vecDirToEnemyEyes.Dot( -vUp ) ) * ( 180.0f / M_PI );
+
+ Vector vProjectedDirToEnemyEyes = vecDirToEnemyEyes - vecDirToEnemyEyes.Dot( vUp ) * vUp;
+ VectorNormalize( vProjectedDirToEnemyEyes );
+
+ if ( vProjectedDirToEnemyEyes.IsZero() )
+ vecAnglesToEnemy.y = m_vecGoalAngles.y;
+ else
+ {
+ if ( vProjectedDirToEnemyEyes.Dot( vForward ) > 0.0f )
+ vecAnglesToEnemy.y = acosf( vProjectedDirToEnemyEyes.Dot( vRight ) ) * ( 180.0f / M_PI ) - 90.0f;
+ else
+ vecAnglesToEnemy.y = -acosf( vProjectedDirToEnemyEyes.Dot( vRight ) ) * ( 180.0f / M_PI ) - 90.0f;
+ }
+
+ vecAnglesToEnemy.y = AngleNormalize( vecAnglesToEnemy.y );
+
+ //Current enemy is not visible
+ if ( ( bEnemyVisible == false ) || ( flDistToEnemy > SECURITY_CAMERA_RANGE ) )
+ {
+ if ( gpGlobals->curtime > m_flLastSight )
+ {
+ // Should we look for a new target?
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+ SetLastSightTime();
+ SetThink( &CNPC_SecurityCamera::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+
+ return;
+ }
+
+ bEnemyVisible = false;
+ }
+
+ //If we can see our enemy, face it
+ if ( bEnemyVisible )
+ {
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+
+ m_flLastSight = gpGlobals->curtime + 0.5f;
+ }
+
+ //Turn to face
+ UpdateFacing();
+
+ // Update rope positions
+ for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope )
+ {
+ if ( m_hRopes[ iRope ] )
+ {
+ m_hRopes[ iRope ]->EndpointsChanged();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Target doesn't exist or has eluded us, so search for one
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::SearchThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_SEARCHING ) )
+ return;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ //If our enemy has died, pick a new enemy
+ if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
+ {
+ SetEnemy( NULL );
+ }
+
+ //Acquire the target
+ if ( GetEnemy() == NULL )
+ {
+ CBaseEntity *pEnemy = NULL;
+
+ //CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer && pPlayer->IsAlive() )
+ {
+ if ( FInViewCone( pPlayer ) && FVisible( pPlayer ) )
+ {
+ pEnemy = pPlayer;
+ break;
+ }
+ else
+ {
+ CProp_Portal *pPortal = FInViewConeThroughPortal( pPlayer );
+ if ( pPortal && FVisibleThroughPortal( pPortal, pPlayer ) )
+ {
+ pEnemy = pPlayer;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( pEnemy )
+ {
+ SetEnemy( pEnemy );
+ }
+ }
+
+ //If we've found a target follow it
+ if ( GetEnemy() != NULL )
+ {
+ m_flLastSight = 0;
+ m_bActive = true;
+ SetThink( &CNPC_SecurityCamera::ActiveThink );
+
+ //EmitSound( "NPC_CeilingTurret.Active" );
+ return;
+ }
+
+ --m_iTicksTillNextNoise;
+
+ if ( m_iTicksTillNextNoise <= 0 )
+ {
+ //Display that we're scanning
+ m_vecGoalAngles.x = RandomFloat( -10.0f, 30.0f );
+ m_vecGoalAngles.y = RandomFloat( -80.0f, 80.0f );
+
+ m_iTicksTillNextNoise = RandomInt( 10, 35 );
+ }
+
+ //Turn and ping
+ //UpdateFacing();
+ Ping();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the turret is currently in
+//-----------------------------------------------------------------------------
+bool CNPC_SecurityCamera::PreThink( turretState_e state )
+{
+ CheckPVSCondition();
+
+ //Animate
+ StudioFrameAdvance();
+
+ //Do not interrupt current think function
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make a pinging noise so the player knows where we are
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Ping( void )
+{
+ //See if it's time to ping again
+ if ( m_flPingTime > gpGlobals->curtime )
+ return;
+
+ //Ping!
+ //EmitSound( "NPC_CeilingTurret.Ping" );
+
+ m_flPingTime = gpGlobals->curtime + SECURITY_CAMERA_PING_TIME;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the turret's state
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Toggle( void )
+{
+ //Toggle the state
+ if ( m_bEnabled )
+ {
+ Disable();
+ }
+ else
+ {
+ Enable();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Enable the turret and deploy
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Enable( void )
+{
+ m_bEnabled = true;
+
+ // if the turret is flagged as an autoactivate turret, re-enable its ability open self.
+ if ( m_spawnflags & SF_SECURITY_CAMERA_AUTOACTIVATE )
+ {
+ m_bAutoStart = true;
+ }
+
+ SetThink( &CNPC_SecurityCamera::Deploy );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Retire the turret until enabled again
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::Disable( void )
+{
+ m_bEnabled = false;
+ m_bAutoStart = false;
+
+ SetEnemy( NULL );
+ SetThink( &CNPC_SecurityCamera::Retire );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+void CNPC_SecurityCamera::RopesOn( void )
+{
+ for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope )
+ {
+ // Make a rope if it doesn't exist
+ if ( !m_hRopes[ iRope ] )
+ {
+ CFmtStr str;
+
+ int iStartIndex = LookupAttachment( str.sprintf( "Wire%i_A", iRope + 1 ) );
+ int iEndIndex = LookupAttachment( str.sprintf( "Wire%i_B", iRope + 1 ) );
+
+ m_hRopes[ iRope ] = CRopeKeyframe::Create( this, this, iStartIndex, iEndIndex );
+ if ( m_hRopes[ iRope ] )
+ {
+ m_hRopes[ iRope ]->m_Width = 0.7;
+ m_hRopes[ iRope ]->m_nSegments = ROPE_MAX_SEGMENTS;
+ m_hRopes[ iRope ]->EnableWind( false );
+ m_hRopes[ iRope ]->SetupHangDistance( 9.0f );
+ m_hRopes[ iRope ]->m_bConstrainBetweenEndpoints = true;
+ }
+ }
+ }
+}
+
+void CNPC_SecurityCamera::RopesOff( void )
+{
+ for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope )
+ {
+ // Remove rope if it's alive
+ if ( m_hRopes[ iRope ] )
+ {
+ UTIL_Remove( m_hRopes[ iRope ] );
+ m_hRopes[ iRope ] = NULL;
+ }
+ }
+}
+
+void CNPC_SecurityCamera::EyeOn( void )
+{
+ if ( !m_hEyeGlow )
+ {
+ // Create our eye sprite
+ m_hEyeGlow = CSprite::SpriteCreate( SECURITY_CAMERA_GLOW_SPRITE, GetLocalOrigin(), false );
+ if ( !m_hEyeGlow )
+ return;
+
+ m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation );
+ m_hEyeGlow->SetAttachment( this, LookupAttachment( "light" ) );
+ m_hEyeGlow->SetScale( 0.3f, 1.0f );
+ }
+}
+
+void CNPC_SecurityCamera::EyeOff( void )
+{
+ if ( m_hEyeGlow != NULL )
+ {
+ UTIL_Remove( m_hEyeGlow );
+ m_hEyeGlow = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::InputToggle( inputdata_t &inputdata )
+{
+ Toggle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+void CNPC_SecurityCamera::InputRagdoll( inputdata_t &inputdata )
+{
+ if ( !m_bEnabled )
+ return;
+
+ // Leave decal on wall (may want to disable this once decal for where cam touches wall is made)
+ Vector vForward;
+ GetVectors( &vForward, NULL, NULL );
+
+ trace_t tr;
+ UTIL_TraceLine ( GetAbsOrigin() + 10.0f * vForward, GetAbsOrigin() -60.0f * vForward, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.m_pEnt )
+ UTIL_DecalTrace( &tr, "SecurityCamera.Detachment" );
+
+ // Disable it's AI
+ Disable();
+ SetThink( &CNPC_SecurityCamera::DeathThink );
+ EyeOff();
+
+ // Make it move
+ IPhysicsObject *pPhysics = VPhysicsGetObject();
+ if ( !pPhysics || pPhysics->IsMotionEnabled() )
+ return;
+
+ pPhysics->EnableMotion( true );
+ pPhysics->Wake();
+
+ PlayDismountSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_SecurityCamera::DeathThink( void )
+{
+ if ( PreThink( TURRET_DEAD ) )
+ return;
+
+ // Level out our angles
+ m_vecGoalAngles.x = 120.0f;
+ m_vecGoalAngles.y = 0.0f;
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if ( m_lifeState != LIFE_DEAD )
+ {
+ m_lifeState = LIFE_DEAD;
+
+ //EmitSound( "NPC_CeilingTurret.Die" );
+ }
+
+ // lots of smoke
+ Vector pos;
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
+
+ CBroadcastRecipientFilter filter;
+
+ te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10 );
+
+ g_pEffects->Sparks( pos );
+
+ if ( !UpdateFacing() )
+ {
+ m_flPlaybackRate = 0;
+ SetThink( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_SecurityCamera::CanBeAnEnemyOf( CBaseEntity *pEnemy )
+{
+ // If we're out of ammo, make friendly companions ignore us
+ if ( m_spawnflags & SF_SECURITY_CAMERA_OUT_OF_AMMO )
+ {
+ if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ return false;
+ }
+
+ return BaseClass::CanBeAnEnemyOf( pEnemy );
+}
+
+
+void PlayDismountSounds()
+{
+ // Play GLaDOS's audio reaction
+ CPortal_Player* pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( 1 ) );
+ CAI_BaseActor* pGlaDOS = (CAI_BaseActor*)gEntList.FindEntityByName( NULL, "Aperture_AI" );
+
+ if ( !pPlayer || !pGlaDOS )
+ {
+ DevMsg( 2, "Could not play CNPC_SecurityCamera dismount scene, make sure actor named 'Aperture_AI' is present in map.\n" );
+ return;
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "security_camera_detached" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ // If glados is currently talking, don't let her talk over herself or interrupt a potentially important speech.
+ // Should we play the dismount sound after she's done? or is that too disjointed from the camera dismounting act to make sense...
+ if ( IsRunningScriptedScene( pGlaDOS, false ) )
+ {
+ return;
+ }
+
+ pPlayer->IncNumCamerasDetatched();
+ int iNumCamerasDetatched = pPlayer->GetNumCamerasDetatched();
+
+ // If they've knocked down every one possible, play special '1' sound.
+ if ( iNumCamerasDetatched == SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN )
+ {
+ InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_1 );
+ }
+ else // iNumCamerasDetatched < SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN
+ {
+ // Play different sounds based on progress towards security camera knockdown total.
+ switch ( iNumCamerasDetatched )
+ {
+ case 1:
+ InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_2 );
+ break;
+
+ case 2:
+ InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_3 );
+ break;
+
+ case 3:
+ InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_4 );
+ break;
+
+ default:
+ InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_5 );
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/game/server/portal/physicsshadowclone.cpp b/game/server/portal/physicsshadowclone.cpp
new file mode 100644
index 0000000..1f1f448
--- /dev/null
+++ b/game/server/portal/physicsshadowclone.cpp
@@ -0,0 +1,1091 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Clones a physics object (usually with a matrix transform applied)
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "physicsshadowclone.h"
+#include "portal_util_shared.h"
+#include "vphysics/object_hash.h"
+#include "trains.h"
+#include "props.h"
+#include "model_types.h"
+#include "portal/weapon_physcannon.h" //grab controllers
+
+#include "PortalSimulation.h"
+
+#define MAX_SHADOW_CLONE_COUNT 200
+
+static int g_iShadowCloneCount = 0;
+ConVar sv_debug_physicsshadowclones("sv_debug_physicsshadowclones", "0", FCVAR_REPLICATED );
+ConVar sv_use_shadow_clones( "sv_use_shadow_clones", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); //should we create shadow clones?
+
+static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone );
+
+LINK_ENTITY_TO_CLASS( physicsshadowclone, CPhysicsShadowClone );
+
+static CUtlVector<CPhysicsShadowClone *> s_ActiveShadowClones;
+CUtlVector<CPhysicsShadowClone *> const &CPhysicsShadowClone::g_ShadowCloneList = s_ActiveShadowClones;
+static bool s_IsShadowClone[MAX_EDICTS] = { false };
+
+static CPhysicsShadowCloneLL *s_EntityClones[MAX_EDICTS] = { NULL };
+struct ShadowCloneLLEntryManager
+{
+ CPhysicsShadowCloneLL m_ShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
+ CPhysicsShadowCloneLL *m_pFreeShadowCloneLLEntries[MAX_SHADOW_CLONE_COUNT];
+ int m_iUsedEntryIndex;
+
+ ShadowCloneLLEntryManager( void )
+ {
+ m_iUsedEntryIndex = 0;
+ for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
+ {
+ m_pFreeShadowCloneLLEntries[i] = &m_ShadowCloneLLEntries[i];
+ }
+ }
+
+ inline CPhysicsShadowCloneLL *Alloc( void )
+ {
+ return m_pFreeShadowCloneLLEntries[m_iUsedEntryIndex++];
+ }
+
+ inline void Free( CPhysicsShadowCloneLL *pFree )
+ {
+ m_pFreeShadowCloneLLEntries[--m_iUsedEntryIndex] = pFree;
+ }
+};
+static ShadowCloneLLEntryManager s_SCLLManager;
+
+
+CPhysicsShadowClone::CPhysicsShadowClone( void )
+{
+ m_matrixShadowTransform.Identity();
+ m_matrixShadowTransform_Inverse.Identity();
+ m_bShadowTransformIsIdentity = true;
+ s_ActiveShadowClones.AddToTail( this );
+}
+
+CPhysicsShadowClone::~CPhysicsShadowClone( void )
+{
+ VPhysicsDestroyObject();
+ VPhysicsSetObject( NULL );
+ m_hClonedEntity = NULL;
+ s_ActiveShadowClones.FindAndRemove( this ); //also removed in UpdateOnRemove()
+ Assert( s_IsShadowClone[entindex()] == true );
+ s_IsShadowClone[entindex()] = false;
+}
+
+void CPhysicsShadowClone::UpdateOnRemove( void )
+{
+ CBaseEntity *pSource = m_hClonedEntity;
+ if( pSource )
+ {
+ CPhysicsShadowCloneLL *pCloneListHead = s_EntityClones[pSource->entindex()];
+ Assert( pCloneListHead != NULL );
+
+ CPhysicsShadowCloneLL *pFind = pCloneListHead;
+ CPhysicsShadowCloneLL *pLast = pFind;
+ while( pFind->pClone != this )
+ {
+ pLast = pFind;
+ Assert( pFind->pNext != NULL );
+ pFind = pFind->pNext;
+ }
+
+ if( pFind == pCloneListHead )
+ {
+ s_EntityClones[pSource->entindex()] = pFind->pNext;
+ }
+ else
+ {
+ pLast->pNext = pFind->pNext;
+ }
+ s_SCLLManager.Free( pFind );
+ }
+#ifdef _DEBUG
+ else
+ {
+ //verify that it didn't weasel into a list somewhere and get left behind
+ for( int i = 0; i != MAX_SHADOW_CLONE_COUNT; ++i )
+ {
+ CPhysicsShadowCloneLL *pCloneSearch = s_EntityClones[i];
+ while( pCloneSearch )
+ {
+ Assert( pCloneSearch->pClone != this );
+ pCloneSearch = pCloneSearch->pNext;
+ }
+ }
+ }
+#endif
+ VPhysicsDestroyObject();
+ VPhysicsSetObject( NULL );
+ m_hClonedEntity = NULL;
+ s_ActiveShadowClones.FindAndRemove( this ); //also removed in Destructor
+ BaseClass::UpdateOnRemove();
+}
+
+void CPhysicsShadowClone::Spawn( void )
+{
+ AddFlag( FL_DONTTOUCH );
+ AddEffects( EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW );
+
+ FullSync( false );
+ m_bInAssumedSyncState = false;
+
+ BaseClass::Spawn();
+
+ s_IsShadowClone[entindex()] = true;
+}
+
+
+void CPhysicsShadowClone::FullSync( bool bAllowAssumedSync )
+{
+ Assert( IsMarkedForDeletion() == false );
+
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity == NULL )
+ {
+ AssertMsg( VPhysicsGetObject() != NULL, "Been linkless for more than this update, something should have killed this clone." );
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( 0 );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ VPhysicsDestroyObject();
+ return;
+ }
+
+ SetGroundEntity( NULL );
+
+ bool bIsSynced = bAllowAssumedSync;
+ bool bBigChanges = true; //assume there are, and be proven wrong
+
+ if( bAllowAssumedSync )
+ {
+ IPhysicsObject *pSourceObjects[1024];
+ int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
+
+ //scan for really big differences that would definitely require a full sync
+ bBigChanges = ( iObjectCount != m_CloneLinks.Count() );
+ if( !bBigChanges )
+ {
+ for( int i = 0; i != iObjectCount; ++i )
+ {
+ IPhysicsObject *pSourcePhysics = pSourceObjects[i];
+ IPhysicsObject *pClonedPhysics = m_CloneLinks[i].pClone;
+
+ if( (pSourcePhysics != m_CloneLinks[i].pSource) ||
+ (pSourcePhysics->IsCollisionEnabled() != pClonedPhysics->IsCollisionEnabled()) )
+ {
+ bBigChanges = true;
+ bIsSynced = false;
+ break;
+ }
+
+ Vector ptSourcePosition, ptClonePosition;
+ pSourcePhysics->GetPosition( &ptSourcePosition, NULL );
+ if( !m_bShadowTransformIsIdentity )
+ ptSourcePosition = m_matrixShadowTransform * ptSourcePosition;
+
+ pClonedPhysics->GetPosition( &ptClonePosition, NULL );
+
+ if( (ptClonePosition - ptSourcePosition).LengthSqr() > 2500.0f )
+ {
+ bBigChanges = true;
+ bIsSynced = false;
+ break;
+ }
+
+ //Vector vSourceVelocity, vCloneVelocity;
+
+
+ if( !pSourcePhysics->IsAsleep() ) //only allow full syncrosity if the source entity is entirely asleep
+ bIsSynced = false;
+
+ if( m_bInAssumedSyncState && !pClonedPhysics->IsAsleep() )
+ bIsSynced = false;
+ }
+ }
+ else
+ {
+ bIsSynced = false;
+ }
+
+ bIsSynced = false;
+
+ if( bIsSynced )
+ {
+ //good enough to skip a full update
+ if( !m_bInAssumedSyncState )
+ {
+ //do one last sync
+ PartialSync( true );
+
+ //if we don't do this, objects just fall out of the world (it happens, I swear)
+
+ for( int i = m_CloneLinks.Count(); --i >= 0; )
+ {
+ if( (m_CloneLinks[i].pSource->GetShadowController() == NULL) && m_CloneLinks[i].pClone->IsMotionEnabled() )
+ {
+ //m_CloneLinks[i].pClone->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
+ //m_CloneLinks[i].pClone->SetVelocity( &vec3_origin, &vec3_origin );
+ m_CloneLinks[i].pClone->EnableGravity( false );
+ m_CloneLinks[i].pClone->EnableMotion( false );
+ m_CloneLinks[i].pClone->Sleep();
+ }
+ }
+
+ m_bInAssumedSyncState = true;
+ }
+
+ if( sv_debug_physicsshadowclones.GetBool() )
+ DrawDebugOverlayForShadowClone( this );
+
+ return;
+ }
+ }
+
+ m_bInAssumedSyncState = false;
+
+
+
+
+
+ //past this point, we're committed to a broad update
+
+ if( bBigChanges )
+ {
+ MoveType_t sourceMoveType = pClonedEntity->GetMoveType();
+
+
+ IPhysicsObject *pPhysObject = pClonedEntity->VPhysicsGetObject();
+ if( (sourceMoveType == MOVETYPE_CUSTOM) ||
+ (sourceMoveType == MOVETYPE_STEP) ||
+ (sourceMoveType == MOVETYPE_WALK) ||
+ (pPhysObject &&
+ (
+ (pPhysObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ||
+ (pPhysObject->GetShadowController() != NULL)
+ )
+ )
+ )
+ {
+//#ifdef _DEBUG
+ SetMoveType( MOVETYPE_NONE ); //to kill an assert
+//#endif
+ //PUSH should be used sparingly, you can't stand on a MOVETYPE_PUSH object :/
+ SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() ); //either an unclonable movetype, or a shadow/held object
+ }
+ /*else if(sourceMoveType == MOVETYPE_STEP)
+ {
+ //SetMoveType( MOVETYPE_NONE ); //to kill an assert
+ SetMoveType( MOVETYPE_VPHYSICS, pClonedEntity->GetMoveCollide() );
+ }*/
+ else
+ {
+ //if( m_bShadowTransformIsIdentity )
+ SetMoveType( sourceMoveType, pClonedEntity->GetMoveCollide() );
+ //else
+ //{
+ // SetMoveType( MOVETYPE_NONE ); //to kill an assert
+ // SetMoveType( MOVETYPE_PUSH, pClonedEntity->GetMoveCollide() );
+ //}
+ }
+
+ SolidType_t sourceSolidType = pClonedEntity->GetSolid();
+ if( sourceSolidType == SOLID_BBOX )
+ SetSolid( SOLID_VPHYSICS );
+ else
+ SetSolid( sourceSolidType );
+ //SetSolid( SOLID_VPHYSICS );
+
+ SetElasticity( pClonedEntity->GetElasticity() );
+ SetFriction( pClonedEntity->GetFriction() );
+
+
+
+ int iSolidFlags = pClonedEntity->GetSolidFlags() | FSOLID_CUSTOMRAYTEST;
+ if( m_bShadowTransformIsIdentity )
+ iSolidFlags |= FSOLID_CUSTOMBOXTEST; //need this at least for the player or they get stuck in themselves
+ else
+ iSolidFlags &= ~FSOLID_FORCE_WORLD_ALIGNED;
+ /*if( pClonedEntity->IsPlayer() )
+ {
+ iSolidFlags |= FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST;
+ }*/
+
+ SetSolidFlags( iSolidFlags );
+
+
+
+ SetEffects( pClonedEntity->GetEffects() | (EF_NODRAW | EF_NOSHADOW | EF_NORECEIVESHADOW) );
+
+ SetCollisionGroup( pClonedEntity->GetCollisionGroup() );
+
+ SetModelIndex( pClonedEntity->GetModelIndex() );
+ SetModelName( pClonedEntity->GetModelName() );
+
+ if( modelinfo->GetModelType( pClonedEntity->GetModel() ) == mod_studio )
+ SetModel( STRING( pClonedEntity->GetModelName() ) );
+
+
+ CCollisionProperty *pClonedCollisionProp = pClonedEntity->CollisionProp();
+ SetSize( pClonedCollisionProp->OBBMins(), pClonedCollisionProp->OBBMaxs() );
+ }
+
+ FullSyncClonedPhysicsObjects( bBigChanges );
+ SyncEntity( true );
+
+ if( bBigChanges )
+ CollisionRulesChanged();
+
+ if( sv_debug_physicsshadowclones.GetBool() )
+ DrawDebugOverlayForShadowClone( this );
+}
+
+void CPhysicsShadowClone::SyncEntity( bool bPullChanges )
+{
+ m_bShouldUpSync = false;
+
+ CBaseEntity *pSource, *pDest;
+ VMatrix *pTransform;
+ if( bPullChanges )
+ {
+ pSource = m_hClonedEntity.Get();
+ pDest = this;
+ pTransform = &m_matrixShadowTransform;
+
+ if( pSource == NULL )
+ return;
+ }
+ else
+ {
+ pSource = this;
+ pDest = m_hClonedEntity.Get();
+ pTransform = &m_matrixShadowTransform_Inverse;
+
+ if( pDest == NULL )
+ return;
+ }
+
+
+ Vector ptOrigin, vVelocity;
+ QAngle qAngles;
+
+ ptOrigin = pSource->GetAbsOrigin();
+ qAngles = pSource->GetAbsAngles();
+ vVelocity = pSource->GetAbsVelocity();
+
+ if( !m_bShadowTransformIsIdentity )
+ {
+ ptOrigin = (*pTransform) * ptOrigin;
+ qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
+ vVelocity = pTransform->ApplyRotation( vVelocity );
+ }
+ //else
+ //{
+ // pDest->SetGroundEntity( pSource->GetGroundEntity() );
+ //}
+
+ if( (ptOrigin != pDest->GetAbsOrigin()) || (qAngles != pDest->GetAbsAngles()) )
+ {
+ pDest->Teleport( &ptOrigin, &qAngles, NULL );
+ }
+
+ if( vVelocity != pDest->GetAbsVelocity() )
+ {
+ //pDest->IncrementInterpolationFrame();
+ pDest->SetAbsVelocity( vec3_origin ); //the two step process helps, I don't know why, but it does
+ pDest->ApplyAbsVelocityImpulse( vVelocity );
+ }
+}
+
+
+static void FullSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform, bool bTeleport )
+{
+ CGrabController *pGrabController = NULL;
+
+ if( !pSource->IsAsleep() )
+ pDest->Wake();
+
+ float fSavedMass = 0.0f, fSavedRotationalDamping; //setting mass to 0.0f purely to kill a warning that I can't seem to kill with pragmas
+ if( pSource->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ //CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ //Assert( pPlayer );
+
+ CBaseEntity *pLookingForEntity = (CBaseEntity *)pSource->GetGameData();
+
+ CBasePlayer *pHoldingPlayer = GetPlayerHoldingEntity( pLookingForEntity );
+ if( pHoldingPlayer )
+ {
+ pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
+
+ if ( !pGrabController )
+ pGrabController = GetGrabControllerForPhysCannon( pHoldingPlayer->GetActiveWeapon() );
+ }
+
+ AssertMsg( pGrabController, "Physics object is held, but we can't find the holding controller." );
+ GetSavedParamsForCarriedPhysObject( pGrabController, pSource, &fSavedMass, &fSavedRotationalDamping );
+ }
+
+ //Boiler plate
+ {
+ pDest->SetGameIndex( pSource->GetGameIndex() ); //what's it do?
+ pDest->SetCallbackFlags( pSource->GetCallbackFlags() ); //wise?
+ pDest->SetGameFlags( pSource->GetGameFlags() | FVPHYSICS_NO_SELF_COLLISIONS | FVPHYSICS_IS_SHADOWCLONE );
+ pDest->SetMaterialIndex( pSource->GetMaterialIndex() );
+ pDest->SetContents( pSource->GetContents() );
+
+ pDest->EnableCollisions( pSource->IsCollisionEnabled() );
+ pDest->EnableGravity( pSource->IsGravityEnabled() );
+ pDest->EnableDrag( pSource->IsDragEnabled() );
+ pDest->EnableMotion( pSource->IsMotionEnabled() );
+ }
+
+ //Damping
+ {
+ float fSpeedDamp, fRotDamp;
+ if( pGrabController )
+ {
+ pSource->GetDamping( &fSpeedDamp, NULL );
+ pDest->SetDamping( &fSpeedDamp, &fSavedRotationalDamping );
+ }
+ else
+ {
+ pSource->GetDamping( &fSpeedDamp, &fRotDamp );
+ pDest->SetDamping( &fSpeedDamp, &fRotDamp );
+ }
+ }
+
+ //stuff that we really care about
+ {
+ if( pGrabController )
+ pDest->SetMass( fSavedMass );
+ else
+ pDest->SetMass( pSource->GetMass() );
+
+ Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
+ QAngle qAngles;
+
+ pSource->GetPosition( &ptOrigin, &qAngles );
+ pSource->GetVelocity( &vVelocity, &vAngularVelocity );
+ vInertia = pSource->GetInertia();
+
+ if( pTransform )
+ {
+#if 0
+ pDest->SetPositionMatrix( pTransform->As3x4(), true ); //works like we think?
+#else
+ ptOrigin = (*pTransform) * ptOrigin;
+ qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
+ vVelocity = pTransform->ApplyRotation( vVelocity );
+ vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
+#endif
+ }
+
+ //avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
+ if( vInertia != pDest->GetInertia() )
+ pDest->SetInertia( vInertia );
+
+ Vector ptDestOrigin, vDestVelocity, vDestAngularVelocity;
+ QAngle qDestAngles;
+ pDest->GetPosition( &ptDestOrigin, &qDestAngles );
+
+ if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
+ pDest->SetPosition( ptOrigin, qAngles, bTeleport );
+
+ //pDest->SetVelocityInstantaneous( &vec3_origin, &vec3_origin );
+ //pDest->Sleep();
+
+ pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
+
+ if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
+ pDest->SetVelocityInstantaneous( &vVelocity, &vAngularVelocity );
+
+ IPhysicsShadowController *pSourceController = pSource->GetShadowController();
+ if( pSourceController == NULL )
+ {
+ if( pDest->GetShadowController() != NULL )
+ {
+ //we don't need a shadow controller anymore
+ pDest->RemoveShadowController();
+ }
+ }
+ else
+ {
+ IPhysicsShadowController *pDestController = pDest->GetShadowController();
+ if( pDestController == NULL )
+ {
+ //we need a shadow controller
+ float fMaxSpeed, fMaxAngularSpeed;
+ pSourceController->GetMaxSpeed( &fMaxSpeed, &fMaxAngularSpeed );
+
+ pDest->SetShadow( fMaxSpeed, fMaxAngularSpeed, pSourceController->AllowsTranslation(), pSourceController->AllowsRotation() );
+ pDestController = pDest->GetShadowController();
+ pDestController->SetTeleportDistance( pSourceController->GetTeleportDistance() );
+ pDestController->SetPhysicallyControlled( pSourceController->IsPhysicallyControlled() );
+ }
+
+ //sync shadow controllers
+ float fTimeOffset;
+ Vector ptTargetPosition;
+ QAngle qTargetAngles;
+ fTimeOffset = pSourceController->GetTargetPosition( &ptTargetPosition, &qTargetAngles );
+
+ if( pTransform )
+ {
+ ptTargetPosition = (*pTransform) * ptTargetPosition;
+ qTargetAngles = TransformAnglesToWorldSpace( qTargetAngles, pTransform->As3x4() );
+ }
+
+ pDestController->Update( ptTargetPosition, qTargetAngles, fTimeOffset );
+ }
+
+
+ }
+
+ //pDest->RecheckContactPoints();
+}
+
+static void PartialSyncPhysicsObject( IPhysicsObject *pSource, IPhysicsObject *pDest, const VMatrix *pTransform )
+{
+ Vector ptOrigin, vVelocity, vAngularVelocity, vInertia;
+ QAngle qAngles;
+
+ pSource->GetPosition( &ptOrigin, &qAngles );
+ pSource->GetVelocity( &vVelocity, &vAngularVelocity );
+ vInertia = pSource->GetInertia();
+
+ if( pTransform )
+ {
+#if 0
+ //pDest->SetPositionMatrix( matTransform.As3x4(), true ); //works like we think?
+#else
+ ptOrigin = (*pTransform) * ptOrigin;
+ qAngles = TransformAnglesToWorldSpace( qAngles, pTransform->As3x4() );
+ vVelocity = pTransform->ApplyRotation( vVelocity );
+ vAngularVelocity = pTransform->ApplyRotation( vAngularVelocity );
+#endif
+ }
+
+ //avoid oversetting variables (I think that even setting them to the same value they already are disrupts the delicate physics balance)
+ if( vInertia != pDest->GetInertia() )
+ pDest->SetInertia( vInertia );
+
+ Vector ptDestOrigin, vDestVelocity, vDestAngularVelocity;
+ QAngle qDestAngles;
+ pDest->GetPosition( &ptDestOrigin, &qDestAngles );
+ pDest->GetVelocity( &vDestVelocity, &vDestAngularVelocity );
+
+
+ if( (ptOrigin != ptDestOrigin) || (qAngles != qDestAngles) )
+ pDest->SetPosition( ptOrigin, qAngles, false );
+
+ if( (vVelocity != vDestVelocity) || (vAngularVelocity != vDestAngularVelocity) )
+ pDest->SetVelocity( &vVelocity, &vAngularVelocity );
+
+ pDest->EnableCollisions( pSource->IsCollisionEnabled() );
+}
+
+
+
+void CPhysicsShadowClone::FullSyncClonedPhysicsObjects( bool bTeleport )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+ if( pClonedEntity == NULL )
+ {
+ VPhysicsDestroyObject();
+ return;
+ }
+
+ VMatrix *pTransform;
+ if( m_bShadowTransformIsIdentity )
+ pTransform = NULL;
+ else
+ pTransform = &m_matrixShadowTransform;
+
+ IPhysicsObject *(pSourceObjects[1024]);
+ int iObjectCount = pClonedEntity->VPhysicsGetObjectList( pSourceObjects, 1024 );
+
+ //easy out if nothing has changed
+ if( iObjectCount == m_CloneLinks.Count() )
+ {
+ int i;
+ for( i = 0; i != iObjectCount; ++i )
+ {
+ if( pSourceObjects[i] == NULL )
+ break;
+
+ if( pSourceObjects[i] != m_CloneLinks[i].pSource )
+ break;
+ }
+
+ if( i == iObjectCount ) //no changes
+ {
+ for( i = 0; i != iObjectCount; ++i )
+ FullSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform, bTeleport );
+
+ return;
+ }
+ }
+
+
+
+ //copy the existing list of clone links to a temp array, we're going to be starting from scratch and copying links as we need them
+ PhysicsObjectCloneLink_t *pExistingLinks = NULL;
+ int iExistingLinkCount = m_CloneLinks.Count();
+ if( iExistingLinkCount != 0 )
+ {
+ pExistingLinks = (PhysicsObjectCloneLink_t *)stackalloc( sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
+ memcpy( pExistingLinks, m_CloneLinks.Base(), sizeof(PhysicsObjectCloneLink_t) * m_CloneLinks.Count() );
+ }
+ m_CloneLinks.RemoveAll();
+
+ //now, go over the object list we just got from the source entity, and either copy or create links as necessary
+ int i;
+ for( i = 0; i != iObjectCount; ++i )
+ {
+ IPhysicsObject *pSource = pSourceObjects[i];
+
+ if( pSource == NULL ) //this really shouldn't happen, but it does >_<
+ continue;
+
+ PhysicsObjectCloneLink_t cloneLink;
+
+ int j;
+ for( j = 0; j != iExistingLinkCount; ++j )
+ {
+ if( pExistingLinks[j].pSource == pSource )
+ break;
+ }
+
+ if( j != iExistingLinkCount )
+ {
+ //copyable link found
+ cloneLink = pExistingLinks[j];
+ memset( &pExistingLinks[j], 0, sizeof( PhysicsObjectCloneLink_t ) ); //zero out this slot so we don't destroy it in cleanup
+ }
+ else
+ {
+ //no link found to copy, create a new one
+ cloneLink.pSource = pSource;
+
+ //apparently some collision code gets called on creation before we've set extra game flags, so we're going to cheat a bit and temporarily set our extra flags on the source
+ unsigned int iOldGameFlags = pSource->GetGameFlags();
+ pSource->SetGameFlags( iOldGameFlags | FVPHYSICS_IS_SHADOWCLONE );
+
+ unsigned int size = physenv->GetObjectSerializeSize(pSource);
+ byte *pBuffer = (byte *)stackalloc(size);
+ memset( pBuffer, 0, size );
+
+ physenv->SerializeObjectToBuffer( pSource, pBuffer, size ); //this should work across physics environments because the serializer doesn't write anything about itself to the template
+ pSource->SetGameFlags( iOldGameFlags );
+ cloneLink.pClone = m_pOwnerPhysEnvironment->UnserializeObjectFromBuffer( this, pBuffer, size, false ); //unserializer has to be in the target environment
+ assert( cloneLink.pClone ); //there should be absolutely no case where we can't clone a valid existing physics object
+
+ stackfree(pBuffer);
+ }
+
+ FullSyncPhysicsObject( cloneLink.pSource, cloneLink.pClone, pTransform, bTeleport );
+
+ //cloneLink.pClone->Wake();
+
+ m_CloneLinks.AddToTail( cloneLink );
+ }
+
+
+ //now go over the existing links, if any of them haven't been nullified, they need to be deleted
+ for( i = 0; i != iExistingLinkCount; ++i )
+ {
+ if( pExistingLinks[i].pClone )
+ m_pOwnerPhysEnvironment->DestroyObject( pExistingLinks[i].pClone ); //also destroys shadow controller
+ }
+
+
+ VPhysicsSetObject( NULL );
+
+ IPhysicsObject *pSource = m_hClonedEntity->VPhysicsGetObject();
+
+ for( i = m_CloneLinks.Count(); --i >= 0; )
+ {
+ if( m_CloneLinks[i].pSource == pSource )
+ {
+ //m_CloneLinks[i].pClone->Wake();
+ VPhysicsSetObject( m_CloneLinks[i].pClone );
+ break;
+ }
+ }
+
+ if( (i < 0) && (m_CloneLinks.Count() != 0) )
+ {
+ VPhysicsSetObject( m_CloneLinks[0].pClone );
+ }
+
+ stackfree( pExistingLinks );
+
+ //CollisionRulesChanged();
+}
+
+
+
+void CPhysicsShadowClone::PartialSync( bool bPullChanges )
+{
+ VMatrix *pTransform;
+
+ if( bPullChanges )
+ {
+ if( m_bShadowTransformIsIdentity )
+ pTransform = NULL;
+ else
+ pTransform = &m_matrixShadowTransform;
+
+ for( int i = m_CloneLinks.Count(); --i >= 0; )
+ PartialSyncPhysicsObject( m_CloneLinks[i].pSource, m_CloneLinks[i].pClone, pTransform );
+ }
+ else
+ {
+ if( m_bShadowTransformIsIdentity )
+ pTransform = NULL;
+ else
+ pTransform = &m_matrixShadowTransform_Inverse;
+
+ for( int i = m_CloneLinks.Count(); --i >= 0; )
+ PartialSyncPhysicsObject( m_CloneLinks[i].pClone, m_CloneLinks[i].pSource, pTransform );
+ }
+
+ SyncEntity( bPullChanges );
+}
+
+
+
+int CPhysicsShadowClone::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax )
+{
+ int iCountStop = m_CloneLinks.Count();
+ if( iCountStop > listMax )
+ iCountStop = listMax;
+
+ for( int i = 0; i != iCountStop; ++i, ++pList )
+ *pList = m_CloneLinks[i].pClone;
+
+ return iCountStop;
+}
+
+
+void CPhysicsShadowClone::VPhysicsDestroyObject( void )
+{
+ VPhysicsSetObject( NULL );
+
+ for( int i = m_CloneLinks.Count(); --i >= 0; )
+ {
+ Assert( m_CloneLinks[i].pClone != NULL );
+ m_pOwnerPhysEnvironment->DestroyObject( m_CloneLinks[i].pClone );
+ }
+ m_CloneLinks.RemoveAll();
+
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( 0 );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+
+ BaseClass::VPhysicsDestroyObject();
+}
+
+
+
+
+
+
+bool CPhysicsShadowClone::ShouldCollide( int collisionGroup, int contentsMask ) const
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ return pClonedEntity->ShouldCollide( collisionGroup, contentsMask );
+ else
+ return false;
+}
+
+bool CPhysicsShadowClone::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& trace )
+{
+ return false;
+
+ /*CBaseEntity *pSourceEntity = m_hClonedEntity.Get();
+ if( pSourceEntity == NULL )
+ return false;
+
+ enginetrace->ClipRayToEntity( ray, fContentsMask, pSourceEntity, &trace );
+ return trace.DidHit();*/
+}
+
+int CPhysicsShadowClone::ObjectCaps( void )
+{
+ return ((BaseClass::ObjectCaps() | FCAP_DONT_SAVE) & ~(FCAP_FORCE_TRANSITION | FCAP_ACROSS_TRANSITION | FCAP_MUST_SPAWN | FCAP_SAVE_NON_NETWORKABLE));
+}
+
+
+
+
+
+void CPhysicsShadowClone::SetCloneTransformationMatrix( const matrix3x4_t &sourceMatrix )
+{
+ m_matrixShadowTransform = sourceMatrix;
+ m_bShadowTransformIsIdentity = m_matrixShadowTransform.IsIdentity();
+
+ if( m_matrixShadowTransform.InverseGeneral( m_matrixShadowTransform_Inverse ) == false )
+ {
+ m_matrixShadowTransform.InverseTR( m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
+ }
+
+ FullSync();
+ //PartialSync( true );
+}
+
+
+
+
+
+
+void CPhysicsShadowClone::SetClonedEntity( EHANDLE hEntToClone )
+{
+ VPhysicsDestroyObject();
+
+ m_hClonedEntity = hEntToClone;
+
+ //FullSyncClonedPhysicsObjects();
+}
+
+EHANDLE CPhysicsShadowClone::GetClonedEntity( void )
+{
+ return m_hClonedEntity;
+}
+
+
+
+
+//damage relays to source entity
+bool CPhysicsShadowClone::PassesDamageFilter( const CTakeDamageInfo &info )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ return pClonedEntity->PassesDamageFilter( info );
+ else
+ return BaseClass::PassesDamageFilter( info );
+}
+
+bool CPhysicsShadowClone::CanBeHitByMeleeAttack( CBaseEntity *pAttacker )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ return pClonedEntity->CanBeHitByMeleeAttack( pAttacker );
+ else
+ return BaseClass::CanBeHitByMeleeAttack( pAttacker );
+}
+
+int CPhysicsShadowClone::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ return pClonedEntity->OnTakeDamage( info );
+ else
+ return BaseClass::OnTakeDamage( info );
+}
+
+int CPhysicsShadowClone::TakeHealth( float flHealth, int bitsDamageType )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ return pClonedEntity->TakeHealth( flHealth, bitsDamageType );
+ else
+ return BaseClass::TakeHealth( flHealth, bitsDamageType );
+}
+
+void CPhysicsShadowClone::Event_Killed( const CTakeDamageInfo &info )
+{
+ CBaseEntity *pClonedEntity = m_hClonedEntity.Get();
+
+ if( pClonedEntity )
+ pClonedEntity->Event_Killed( info );
+ else
+ BaseClass::Event_Killed( info );
+}
+
+CPhysicsShadowClone *CPhysicsShadowClone::CreateShadowClone( IPhysicsEnvironment *pInPhysicsEnvironment, EHANDLE hEntToClone, const char *szDebugMarker, const matrix3x4_t *pTransformationMatrix /*= NULL*/ )
+{
+ AssertMsg( szDebugMarker != NULL, "All shadow clones must have a debug marker for where it came from in debug builds." );
+
+ if( !sv_use_shadow_clones.GetBool() )
+ return NULL;
+
+ CBaseEntity *pClonedEntity = hEntToClone.Get();
+ if( pClonedEntity == NULL )
+ return NULL;
+
+ AssertMsg( IsShadowClone( pClonedEntity ) == false, "Shouldn't attempt to clone clones" );
+
+ if( pClonedEntity->IsMarkedForDeletion() )
+ return NULL;
+
+ //if( pClonedEntity->IsPlayer() )
+ // return NULL;
+
+ IPhysicsObject *pPhysics = pClonedEntity->VPhysicsGetObject();
+
+ if( pPhysics == NULL )
+ return NULL;
+
+ if( pPhysics->IsStatic() )
+ return NULL;
+
+ if( pClonedEntity->GetSolid() == SOLID_BSP )
+ return NULL;
+
+ if( pClonedEntity->GetSolidFlags() & (FSOLID_NOT_SOLID | FSOLID_TRIGGER) )
+ return NULL;
+
+ if( pClonedEntity->GetFlags() & (FL_WORLDBRUSH | FL_STATICPROP) )
+ return NULL;
+
+ /*if( FClassnameIs( pClonedEntity, "func_door" ) )
+ {
+ //only clone func_door's that are in front of the portal
+
+ return NULL;
+ }*/
+
+ // Too many shadow clones breaks the game (too many entities)
+ if( g_iShadowCloneCount >= MAX_SHADOW_CLONE_COUNT )
+ {
+ AssertMsg( false, "Too many shadow clones, consider upping the limit or reducing the level's physics props" );
+ return NULL;
+ }
+ ++g_iShadowCloneCount;
+
+ CPhysicsShadowClone *pClone = (CPhysicsShadowClone*)CreateEntityByName("physicsshadowclone");
+ s_IsShadowClone[pClone->entindex()] = true;
+ pClone->m_pOwnerPhysEnvironment = pInPhysicsEnvironment;
+ pClone->m_hClonedEntity = hEntToClone;
+ DBG_CODE_NOSCOPE( pClone->m_szDebugMarker = szDebugMarker; );
+
+ CPhysicsShadowCloneLL *pCloneLLEntry = s_SCLLManager.Alloc();
+ pCloneLLEntry->pClone = pClone;
+ pCloneLLEntry->pNext = s_EntityClones[pClonedEntity->entindex()];
+ s_EntityClones[pClonedEntity->entindex()] = pCloneLLEntry;
+
+ if( pTransformationMatrix )
+ {
+ pClone->m_matrixShadowTransform = *pTransformationMatrix;
+ pClone->m_bShadowTransformIsIdentity = pClone->m_matrixShadowTransform.IsIdentity();
+
+ if( !pClone->m_bShadowTransformIsIdentity )
+ {
+ if( pClone->m_matrixShadowTransform.InverseGeneral( pClone->m_matrixShadowTransform_Inverse ) == false )
+ {
+ pClone->m_matrixShadowTransform.InverseTR( pClone->m_matrixShadowTransform_Inverse ); //probably not the right matrix, but we're out of options
+ }
+ }
+ }
+
+ DispatchSpawn( pClone );
+
+ return pClone;
+}
+
+void CPhysicsShadowClone::Free( void )
+{
+ VPhysicsDestroyObject();
+
+ UTIL_Remove( this );
+
+ //Too many shadow clones breaks the game (too many entities)
+ --g_iShadowCloneCount;
+}
+
+
+void CPhysicsShadowClone::FullSyncAllClones( void )
+{
+ for( int i = s_ActiveShadowClones.Count(); --i >= 0; )
+ {
+ s_ActiveShadowClones[i]->FullSync( true );
+ }
+}
+
+
+IPhysicsObject *CPhysicsShadowClone::TranslatePhysicsToClonedEnt( const IPhysicsObject *pPhysics )
+{
+ if( m_hClonedEntity.Get() != NULL )
+ {
+ for( int i = m_CloneLinks.Count(); --i >= 0; )
+ {
+ if( m_CloneLinks[i].pClone == pPhysics )
+ return m_CloneLinks[i].pSource;
+ }
+ }
+
+ return NULL;
+}
+
+
+void CPhysicsShadowClone::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ //the baseclass just screenshakes, makes sounds, and outputs dust, we rely on the original entity to do this when applicable
+}
+
+
+
+
+bool CPhysicsShadowClone::IsShadowClone( const CBaseEntity *pEntity )
+{
+ return s_IsShadowClone[pEntity->entindex()];
+}
+
+CPhysicsShadowCloneLL *CPhysicsShadowClone::GetClonesOfEntity( const CBaseEntity *pEntity )
+{
+ return s_EntityClones[pEntity->entindex()];
+}
+
+
+
+static void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone )
+{
+ unsigned char iColorIntensity = (pClone->IsInAssumedSyncState())?(127):(255);
+
+ int iRed = (pClone->IsUntransformedClone())?(0):(iColorIntensity);
+ int iGreen = iColorIntensity;
+ int iBlue = iColorIntensity;
+
+ NDebugOverlay::EntityBounds( pClone, iRed, iGreen, iBlue, (iColorIntensity>>2), 0.05f );
+}
+
+
+bool CTraceFilterTranslateClones::ShouldHitEntity( IHandleEntity *pEntity, int contentsMask )
+{
+ CBaseEntity *pEnt = EntityFromEntityHandle( pEntity );
+ if( CPhysicsShadowClone::IsShadowClone( pEnt ) )
+ {
+ CBaseEntity *pClonedEntity = ((CPhysicsShadowClone *)pEnt)->GetClonedEntity();
+ CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pClonedEntity );
+ if( pSimulator->m_DataAccess.Simulation.Dynamic.EntFlags[pClonedEntity->entindex()] & PSEF_IS_IN_PORTAL_HOLE )
+ return m_pActualFilter->ShouldHitEntity( pClonedEntity, contentsMask );
+ else
+ return false;
+ }
+ else
+ {
+ return m_pActualFilter->ShouldHitEntity( pEntity, contentsMask );
+ }
+}
+
+TraceType_t CTraceFilterTranslateClones::GetTraceType() const
+{
+ return m_pActualFilter->GetTraceType();
+}
+
+
+
diff --git a/game/server/portal/physicsshadowclone.h b/game/server/portal/physicsshadowclone.h
new file mode 100644
index 0000000..1932079
--- /dev/null
+++ b/game/server/portal/physicsshadowclone.h
@@ -0,0 +1,138 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Clones a physics object by use of shadows
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef PHYSICSSHADOWCLONE_H
+#define PHYSICSSHADOWCLONE_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vphysics_interface.h"
+#include "baseentity.h"
+#include "baseanimating.h"
+
+class CPhysicsShadowClone;
+
+struct PhysicsObjectCloneLink_t
+{
+ IPhysicsObject *pSource;
+ IPhysicsShadowController *pShadowController;
+ IPhysicsObject *pClone;
+};
+
+struct CPhysicsShadowCloneLL
+{
+ CPhysicsShadowClone *pClone;
+ CPhysicsShadowCloneLL *pNext;
+};
+
+#define FVPHYSICS_IS_SHADOWCLONE 0x4000
+
+class CPhysicsShadowClone : public CBaseAnimating
+{
+ DECLARE_CLASS( CPhysicsShadowClone, CBaseAnimating );
+
+private:
+ EHANDLE m_hClonedEntity; //the entity we're supposed to be cloning the physics of
+ VMatrix m_matrixShadowTransform; //all cloned coordinates and angles will be run through this matrix before being applied
+ VMatrix m_matrixShadowTransform_Inverse;
+
+ CUtlVector<PhysicsObjectCloneLink_t> m_CloneLinks; //keeps track of which of our physics objects are linked to the source's objects
+ bool m_bShadowTransformIsIdentity; //the shadow transform doesn't update often, so we can cache this
+ bool m_bImmovable; //cloning a track train or door, something that doesn't really work on a force-based level
+ bool m_bInAssumedSyncState;
+
+ void FullSyncClonedPhysicsObjects( bool bTeleport );
+ void SyncEntity( bool bPullChanges );
+
+ IPhysicsEnvironment *m_pOwnerPhysEnvironment; //clones exist because of multi-environment situations
+
+
+public:
+ CPhysicsShadowClone( void );
+ virtual ~CPhysicsShadowClone( void );
+
+ bool m_bShouldUpSync;
+ DBG_CODE_NOSCOPE( const char *m_szDebugMarker; );
+
+ //do the thing with the stuff, you know, the one that goes WooooWooooWooooWooooWoooo
+ virtual void Spawn( void );
+
+ //crush, kill, DESTROY!!!!!
+ void Free( void );
+
+ //syncs to the source entity in every way possible, assumed sync does some rudimentary tests to see if the object is in sync, and if so, skips the update
+ void FullSync( bool bAllowAssumedSync = false );
+
+ //syncs just the physics objects, bPullChanges should be true when this clone should match it's source, false when it should force differences onto the source entity
+ void PartialSync( bool bPullChanges );
+
+ //virtual bool CreateVPhysics( void );
+ virtual void VPhysicsDestroyObject( void );
+ virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax );
+ virtual int ObjectCaps( void );
+ virtual void UpdateOnRemove( void );
+
+
+
+ //routing to the source entity for cloning goodness
+ virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
+
+ //avoid blocking traces that are supposed to hit our source entity
+ virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
+
+
+
+
+ //is this clone occupying the exact same space as the object it's cloning?
+ inline bool IsUntransformedClone( void ) const { return m_bShadowTransformIsIdentity; };
+ void SetCloneTransformationMatrix( const matrix3x4_t &matTransform );
+
+ inline bool IsInAssumedSyncState( void ) const { return m_bInAssumedSyncState; }
+ inline IPhysicsEnvironment *GetOwnerEnvironment( void ) const { return m_pOwnerPhysEnvironment; }
+
+ //what entity are we cloning?
+ void SetClonedEntity( EHANDLE hEntToClone );
+ EHANDLE GetClonedEntity( void );
+
+
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+
+ //damage relays to source entity if anything ever hits the clone
+ virtual bool PassesDamageFilter( const CTakeDamageInfo &info );
+ virtual bool CanBeHitByMeleeAttack( CBaseEntity *pAttacker );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual int TakeHealth( float flHealth, int bitsDamageType );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+
+
+ static CPhysicsShadowClone *CreateShadowClone( IPhysicsEnvironment *pInPhysicsEnvironment, EHANDLE hEntToClone, const char *szDebugMarker, const matrix3x4_t *pTransformationMatrix = NULL );
+
+ //given a physics object that is part of this clone, tells you which physics object in the source
+ IPhysicsObject *TranslatePhysicsToClonedEnt( const IPhysicsObject *pPhysics );
+
+ static bool IsShadowClone( const CBaseEntity *pEntity );
+ static CPhysicsShadowCloneLL *GetClonesOfEntity( const CBaseEntity *pEntity );
+ static void FullSyncAllClones( void );
+
+ static CUtlVector<CPhysicsShadowClone *> const &g_ShadowCloneList;
+};
+
+
+
+class CTraceFilterTranslateClones : public CTraceFilter //give it another filter, and it'll translate shadow clones into their source entity for tests
+{
+ ITraceFilter *m_pActualFilter; //the filter that tests should be forwarded to after translating clones
+
+public:
+ CTraceFilterTranslateClones( ITraceFilter *pOtherFilter ) : m_pActualFilter(pOtherFilter) {};
+ virtual bool ShouldHitEntity( IHandleEntity *pEntity, int contentsMask );
+ virtual TraceType_t GetTraceType() const;
+};
+
+#endif //#ifndef PHYSICSSHADOWCLONE_H
diff --git a/game/server/portal/portal_client.cpp b/game/server/portal/portal_client.cpp
new file mode 100644
index 0000000..e7ebaef
--- /dev/null
+++ b/game/server/portal/portal_client.cpp
@@ -0,0 +1,176 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== portal_client.cpp ========================================================
+
+ Portal client/server game specific stuff
+
+*/
+
+#include "cbase.h"
+#include "portal_player.h"
+#include "portal_gamerules.h"
+#include "gamerules.h"
+#include "teamplay_gamerules.h"
+#include "entitylist.h"
+#include "physics.h"
+#include "game.h"
+#include "player_resource.h"
+#include "engine/IEngineSound.h"
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+void Host_Say( edict_t *pEdict, bool teamonly );
+
+extern CBaseEntity* FindPickerEntityClass( CBasePlayer *pPlayer, char *classname );
+extern bool g_fGameOver;
+
+/*
+===========
+ClientPutInServer
+
+called each time a player is spawned into the game
+============
+*/
+void ClientPutInServer( edict_t *pEdict, const char *playername )
+{
+ // Allocate a CBasePlayer for pev, and call spawn
+ CPortal_Player *pPlayer = CPortal_Player::CreatePlayer( "player", pEdict );
+ pPlayer->PlayerData()->netname = AllocPooledString( playername );
+}
+
+
+void ClientActive( edict_t *pEdict, bool bLoadGame )
+{
+ CPortal_Player *pPlayer = dynamic_cast< CPortal_Player* >( CBaseEntity::Instance( pEdict ) );
+ Assert( pPlayer );
+
+ pPlayer->InitialSpawn();
+
+ if ( !bLoadGame )
+ {
+ pPlayer->Spawn();
+ }
+}
+
+
+/*
+===============
+const char *GetGameDescription()
+
+Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
+===============
+*/
+const char *GetGameDescription()
+{
+ if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
+ return g_pGameRules->GetGameDescription();
+ else
+ return "Half-Life 2";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a player and optional name returns the entity of that
+// classname that the player is nearest facing
+//
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+CBaseEntity* FindEntity( edict_t *pEdict, char *classname)
+{
+ // If no name was given set bits based on the picked
+ if (FStrEq(classname,""))
+ {
+ return (FindPickerEntityClass( static_cast<CBasePlayer*>(GetContainingEntity(pEdict)), classname ));
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache game-specific models & sounds
+//-----------------------------------------------------------------------------
+void ClientGamePrecache( void )
+{
+ CBaseEntity::PrecacheModel("models/player.mdl");
+ CBaseEntity::PrecacheModel( "models/gibs/agibs.mdl" );
+ CBaseEntity::PrecacheModel("models/weapons/v_hands.mdl");
+
+ CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowAmmo" );
+ CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowHealth" );
+
+ CBaseEntity::PrecacheScriptSound( "Missile.ShotDown" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.DefaultNearmiss" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.GunshipNearmiss" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.StriderNearmiss" );
+
+ CBaseEntity::PrecacheScriptSound( "Geiger.BeepHigh" );
+ CBaseEntity::PrecacheScriptSound( "Geiger.BeepLow" );
+
+ CBaseEntity::PrecacheModel( "models/portals/portal1.mdl" );
+ CBaseEntity::PrecacheModel( "models/portals/portal2.mdl" );
+}
+
+
+// called by ClientKill and DeadThink
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
+{
+ if (gpGlobals->coop || gpGlobals->deathmatch)
+ {
+ if ( fCopyCorpse )
+ {
+ // make a copy of the dead body for appearances sake
+ ((CPortal_Player *)pEdict)->CreateCorpse();
+ }
+
+ // respawn player
+ pEdict->Spawn();
+ }
+ else
+ { // restart the entire server
+ engine->ServerCommand("reload\n");
+ }
+}
+
+void GameStartFrame( void )
+{
+ VPROF("GameStartFrame()");
+ if ( g_fGameOver )
+ return;
+
+ gpGlobals->teamplay = (teamplay.GetInt() != 0);
+}
+
+//=========================================================
+// instantiate the proper game rules object
+//=========================================================
+void InstallGameRules()
+{
+ if ( !gpGlobals->deathmatch )
+ {
+ CreateGameRulesObject( "CPortalGameRules" );
+ return;
+ }
+ else
+ {
+ if ( teamplay.GetInt() > 0 )
+ {
+ // teamplay
+ CreateGameRulesObject( "CTeamplayRules" );
+ }
+ else
+ {
+ // vanilla deathmatch
+ CreateGameRulesObject( "CMultiplayRules" );
+ }
+ }
+}
+
diff --git a/game/server/portal/portal_gamestats.cpp b/game/server/portal/portal_gamestats.cpp
new file mode 100644
index 0000000..452e9da
--- /dev/null
+++ b/game/server/portal/portal_gamestats.cpp
@@ -0,0 +1,683 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "portal_gamestats.h"
+#include "tier1/utlbuffer.h"
+#include "portal_player.h"
+
+#define PORTALSTATS_TRIMEVENT( varName, varType )\
+ if( varName->Count() > varType::TRIMSIZE )\
+ varName->RemoveMultiple( 0, (varName->Count() - varType::TRIMSIZE) );
+
+#define PORTALSTATS_PREPCHUNK( structType, bufName, SizePositionVarNameToUse )\
+ SaveBuffer.PutUnsignedShort( structType::CHUNKID );\
+ int SizePositionVarNameToUse = bufName.TellPut();\
+ bufName.PutUnsignedInt( 0 );
+
+#define PORTALSTATS_WRITECHUNKSIZE( bufName, SizePositionVariable ) \
+{\
+ int SizePositionVariable ## _askdjbhas = bufName.TellPut() - SizePositionVariable;\
+ bufName.SeekPut( CUtlBuffer::SEEK_HEAD, SizePositionVariable );\
+ bufName.PutUnsignedInt( SizePositionVariable ## _askdjbhas );\
+ bufName.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );\
+}
+
+static Portal_Gamestats_LevelStats_t s_DummyStats;
+CPortalGameStats g_PortalGameStats;
+
+class CPortalGameStatsSingleton //used to remove the constructor destructor from the general class
+{
+public:
+ CPortalGameStatsSingleton( void )
+ {
+ gamestats = (CBaseGameStats *)&g_PortalGameStats;
+ CreateLevelStatPointers( &s_DummyStats );
+ }
+
+ ~CPortalGameStatsSingleton( void )
+ {
+ AssertMsg( (CBaseGameStats::StatTrackingAllowed() == false) ||
+ ((s_DummyStats.m_pDeaths->Count() == 0) &&
+ (s_DummyStats.m_pPlacements->Count() == 0) &&
+ (s_DummyStats.m_pUseEvents->Count() == 0) &&
+ (s_DummyStats.m_pStuckSpots->Count() == 0) &&
+ (s_DummyStats.m_pJumps->Count() == 0) &&
+ (s_DummyStats.m_pTimeSpentInVisLeafs->Count() == 0)),
+ "Some stats were deferred to the dummy entry." );
+ DestroyLevelStatPointers( &s_DummyStats );
+ }
+};
+CPortalGameStatsSingleton s_CPGSS_ThisJustSitsInMemory;
+
+CPortalGameStats::CPortalGameStats( void )
+: m_pCurrentMapStats( &s_DummyStats )
+{
+
+}
+
+CPortalGameStats::~CPortalGameStats( void )
+{
+ Clear();
+}
+
+
+
+void Portal_Gamestats_LevelStats_t::AppendSubChunksToBuffer( CUtlBuffer &SaveBuffer )
+{
+ if( m_pDeaths->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pDeaths, PlayerDeaths_t );
+ PORTALSTATS_PREPCHUNK( PlayerDeaths_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iDeathStatsCount = m_pDeaths->Count();
+ SaveBuffer.PutUnsignedInt( iDeathStatsCount );
+ for( int i = 0; i != iDeathStatsCount; ++i )
+ {
+ PlayerDeaths_t &DeathStat = m_pDeaths->Element( i );
+
+ SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.x );
+ SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.y );
+ SaveBuffer.PutFloat( DeathStat.ptPositionOfDeath.z );
+ SaveBuffer.PutInt( DeathStat.iDamageType );
+ SaveBuffer.PutString( DeathStat.szAttackerClassName );
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+
+ if( m_pPlacements->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pPlacements, PortalPlacement_t );
+ PORTALSTATS_PREPCHUNK( PortalPlacement_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iPlacementStatsCount = m_pPlacements->Count();
+ SaveBuffer.PutUnsignedInt( iPlacementStatsCount );
+ for( int i = 0; i != iPlacementStatsCount; ++i )
+ {
+ PortalPlacement_t &PortalPlacementStat = m_pPlacements->Element( i );
+
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.x );
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.y );
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlayerFiredFrom.z );
+
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.x );
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.y );
+ SaveBuffer.PutFloat( PortalPlacementStat.ptPlacementPosition.z );
+
+ SaveBuffer.PutChar( PortalPlacementStat.iSuccessCode );
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+
+ if( m_pUseEvents->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pUseEvents, PlayerUse_t );
+ PORTALSTATS_PREPCHUNK( PlayerUse_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iUseEventCount = m_pUseEvents->Count();
+ SaveBuffer.PutUnsignedInt( iUseEventCount );
+ for( int i = 0; i != iUseEventCount; ++i )
+ {
+ PlayerUse_t &UseEvent = m_pUseEvents->Element( i );
+
+ SaveBuffer.PutFloat( UseEvent.ptTraceStart.x );
+ SaveBuffer.PutFloat( UseEvent.ptTraceStart.y );
+ SaveBuffer.PutFloat( UseEvent.ptTraceStart.z );
+
+ SaveBuffer.PutFloat( UseEvent.vTraceDelta.x );
+ SaveBuffer.PutFloat( UseEvent.vTraceDelta.y );
+ SaveBuffer.PutFloat( UseEvent.vTraceDelta.z );
+
+ SaveBuffer.PutString( UseEvent.szUseEntityClassName );
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+
+ if( m_pStuckSpots->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pStuckSpots, StuckEvent_t );
+ PORTALSTATS_PREPCHUNK( StuckEvent_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iStuckStatsCount = m_pStuckSpots->Count();
+ SaveBuffer.PutUnsignedInt( iStuckStatsCount );
+ for( int i = 0; i != iStuckStatsCount; ++i )
+ {
+ StuckEvent_t &StuckStat = m_pStuckSpots->Element( i );
+
+ SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.x );
+ SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.y );
+ SaveBuffer.PutFloat( StuckStat.ptPlayerPosition.z );
+
+ SaveBuffer.PutFloat( StuckStat.qPlayerAngles.x );
+ SaveBuffer.PutFloat( StuckStat.qPlayerAngles.y );
+ SaveBuffer.PutFloat( StuckStat.qPlayerAngles.z );
+
+ unsigned char bitFlags = 0;
+ if( StuckStat.bNearPortal )
+ bitFlags |= (1 << 0);
+
+ if( StuckStat.bDucking )
+ bitFlags |= (1 << 1);
+
+ SaveBuffer.PutUnsignedChar( bitFlags );
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+
+ if( m_pJumps->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pJumps, JumpEvent_t );
+ PORTALSTATS_PREPCHUNK( JumpEvent_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iJumpStatsCount = m_pJumps->Count();
+ SaveBuffer.PutUnsignedInt( iJumpStatsCount );
+ for( int i = 0; i != iJumpStatsCount; ++i )
+ {
+ JumpEvent_t &JumpStat = m_pJumps->Element( i );
+
+ SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.x );
+ SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.y );
+ SaveBuffer.PutFloat( JumpStat.ptPlayerPositionAtJumpStart.z );
+
+ SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.x );
+ SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.y );
+ SaveBuffer.PutFloat( JumpStat.vPlayerVelocityAtJumpStart.z );
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+
+ if( m_pTimeSpentInVisLeafs->Count() != 0 )
+ {
+ PORTALSTATS_TRIMEVENT( m_pTimeSpentInVisLeafs, LeafTimes_t );
+ PORTALSTATS_PREPCHUNK( LeafTimes_t, SaveBuffer, iSubChunkSizePosition );
+
+ int iLeafTimeStatsCount = m_pTimeSpentInVisLeafs->Count();
+ SaveBuffer.PutUnsignedInt( iLeafTimeStatsCount );
+ for( int i = 0; i != iLeafTimeStatsCount; ++i )
+ {
+ LeafTimes_t &LeafTimeStat = m_pTimeSpentInVisLeafs->Element( i );
+
+ SaveBuffer.PutFloat( LeafTimeStat.fTimeSpentInVisLeaf ); //assumes visleafs will be the same when this data is loaded again, or that there will be a way to invalidate the data
+ }
+
+ PORTALSTATS_WRITECHUNKSIZE( SaveBuffer, iSubChunkSizePosition );
+ }
+}
+
+void Portal_Gamestats_LevelStats_t::LoadSubChunksFromBuffer( CUtlBuffer &LoadBuffer, unsigned int iChunkEndPosition )
+{
+ Clear();
+
+ while( ((unsigned int)LoadBuffer.TellGet()) != iChunkEndPosition )
+ {
+ Assert( (iChunkEndPosition - LoadBuffer.TellGet()) > (sizeof( unsigned short ) + sizeof( unsigned int )) ); //at least an empty chunk left
+
+ unsigned short iChunkID = LoadBuffer.GetUnsignedShort();
+ unsigned int iChunkSize = LoadBuffer.GetUnsignedInt() - sizeof( unsigned int ); //chunk size includes the chunk size data itself
+#ifdef _DEBUG
+ unsigned int iChunkEndPosition = LoadBuffer.TellGet() + iChunkSize; //used in an assert later
+#endif
+
+
+ switch( iChunkID )
+ {
+#ifdef PORTAL_GAMESTATS_VERBOSE //don't bother loading verbose chunks if we're not going to save them back out
+ case PlayerDeaths_t::CHUNKID:
+ {
+ unsigned int iDeathStatCount = LoadBuffer.GetUnsignedInt();
+ for( unsigned int i = 0; i != iDeathStatCount; ++i )
+ {
+ int index = m_pDeaths->AddToTail();
+ PlayerDeaths_t &DeathStat = m_pDeaths->Element( index );
+
+ DeathStat.ptPositionOfDeath.x = LoadBuffer.GetFloat();
+ DeathStat.ptPositionOfDeath.y = LoadBuffer.GetFloat();
+ DeathStat.ptPositionOfDeath.z = LoadBuffer.GetFloat();
+ DeathStat.iDamageType = LoadBuffer.GetInt();
+ LoadBuffer.GetString( DeathStat.szAttackerClassName );
+ }
+
+ break;
+ }
+
+ case PortalPlacement_t::CHUNKID:
+ {
+ unsigned int iPlacementStatCount = LoadBuffer.GetUnsignedInt();
+ for( unsigned int i = 0; i != iPlacementStatCount; ++i )
+ {
+ int index = m_pPlacements->AddToTail();
+ PortalPlacement_t &PlacementStat = m_pPlacements->Element( index );
+
+ PlacementStat.ptPlayerFiredFrom.x = LoadBuffer.GetFloat();
+ PlacementStat.ptPlayerFiredFrom.y = LoadBuffer.GetFloat();
+ PlacementStat.ptPlayerFiredFrom.z = LoadBuffer.GetFloat();
+
+ PlacementStat.ptPlacementPosition.x = LoadBuffer.GetFloat();
+ PlacementStat.ptPlacementPosition.y = LoadBuffer.GetFloat();
+ PlacementStat.ptPlacementPosition.z = LoadBuffer.GetFloat();
+
+ PlacementStat.iSuccessCode = LoadBuffer.GetChar();
+ }
+
+ break;
+ }
+
+ case PlayerUse_t::CHUNKID:
+ {
+ int iUseEventCount = LoadBuffer.GetUnsignedInt();
+ for( int i = 0; i != iUseEventCount; ++i )
+ {
+ int index = m_pUseEvents->AddToTail();
+ PlayerUse_t &UseEvent = m_pUseEvents->Element( index );
+
+ UseEvent.ptTraceStart.x = LoadBuffer.GetFloat();
+ UseEvent.ptTraceStart.y = LoadBuffer.GetFloat();
+ UseEvent.ptTraceStart.z = LoadBuffer.GetFloat();
+
+ UseEvent.vTraceDelta.x = LoadBuffer.GetFloat();
+ UseEvent.vTraceDelta.y = LoadBuffer.GetFloat();
+ UseEvent.vTraceDelta.z = LoadBuffer.GetFloat();
+
+ LoadBuffer.GetString( UseEvent.szUseEntityClassName );
+ }
+
+ break;
+ }
+
+ case StuckEvent_t::CHUNKID:
+ {
+ unsigned int iStuckEventCount = LoadBuffer.GetUnsignedInt();
+ for( unsigned int i = 0; i != iStuckEventCount; ++i )
+ {
+ int index = m_pStuckSpots->AddToTail();
+ StuckEvent_t &StuckEvent = m_pStuckSpots->Element( index );
+
+ StuckEvent.ptPlayerPosition.x = LoadBuffer.GetFloat();
+ StuckEvent.ptPlayerPosition.y = LoadBuffer.GetFloat();
+ StuckEvent.ptPlayerPosition.z = LoadBuffer.GetFloat();
+
+ StuckEvent.qPlayerAngles.x = LoadBuffer.GetFloat();
+ StuckEvent.qPlayerAngles.y = LoadBuffer.GetFloat();
+ StuckEvent.qPlayerAngles.z = LoadBuffer.GetFloat();
+
+ unsigned char bitFlags = LoadBuffer.GetUnsignedChar();
+
+ StuckEvent.bNearPortal = ( (bitFlags & (1 << 0)) != 0 );
+ StuckEvent.bDucking = ( (bitFlags & (1 << 1)) != 0 );
+ }
+
+ break;
+ }
+
+ case JumpEvent_t::CHUNKID:
+ {
+ unsigned int iJumpEventCount = LoadBuffer.GetUnsignedInt();
+ for( unsigned int i = 0; i != iJumpEventCount; ++i )
+ {
+ int index = m_pJumps->AddToTail();
+ JumpEvent_t &JumpEvent = m_pJumps->Element( index );
+
+ JumpEvent.ptPlayerPositionAtJumpStart.x = LoadBuffer.GetFloat();
+ JumpEvent.ptPlayerPositionAtJumpStart.y = LoadBuffer.GetFloat();
+ JumpEvent.ptPlayerPositionAtJumpStart.z = LoadBuffer.GetFloat();
+
+ JumpEvent.vPlayerVelocityAtJumpStart.x = LoadBuffer.GetFloat();
+ JumpEvent.vPlayerVelocityAtJumpStart.y = LoadBuffer.GetFloat();
+ JumpEvent.vPlayerVelocityAtJumpStart.z = LoadBuffer.GetFloat();
+ }
+
+ break;
+ }
+
+ case LeafTimes_t::CHUNKID:
+ {
+ //IMPORTANT TODO
+ //TODO: Detect if the leaves have changed and invalidate these counts
+
+ unsigned int iLeafCount = LoadBuffer.GetUnsignedInt();
+ for( unsigned int i = 0; i != iLeafCount; ++i )
+ {
+ int index = m_pTimeSpentInVisLeafs->AddToTail();
+ LeafTimes_t &LeafTime = m_pTimeSpentInVisLeafs->Element( index );
+
+ LeafTime.fTimeSpentInVisLeaf = LoadBuffer.GetFloat();
+ }
+
+ break;
+ }
+#else
+ case PlayerDeaths_t::CHUNKID: //warning workaround
+#endif
+ default:
+ {
+ //an unknown chunk, skip it
+ LoadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, iChunkSize );
+ }
+ };
+
+ Assert( ((unsigned int)LoadBuffer.TellGet()) == iChunkEndPosition );
+
+ };
+}
+
+void Portal_Gamestats_LevelStats_t::Clear( void )
+{
+ m_pDeaths->RemoveAll();
+ m_pPlacements->RemoveAll();
+ m_pStuckSpots->RemoveAll();
+ m_pJumps->RemoveAll();
+ m_pTimeSpentInVisLeafs->RemoveAll();
+}
+
+
+
+
+
+void CPortalGameStats::AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE //we only have verbose chunks for now, so only write custom data if we're verbosely tracking
+
+ SaveBuffer.PutUnsignedShort( PORTAL_GAMESTATS_VERSION );
+
+ //no headers allowed, chunks were chosen for their flexibility in loading even when parts of the data are unknown
+ //you can simulate a header by enclosing the entirety of the custom data in a chunk that starts with a header, but you'll kill loading in old versions
+
+ if( m_CustomMapStats.Count() != 0 ) //we have some map stats
+ {
+ //put out a map chunk for each map
+ for ( int i = m_CustomMapStats.First(); i != m_CustomMapStats.InvalidIndex(); i = m_CustomMapStats.Next( i ) )
+ {
+ SaveBuffer.PutShort( Portal_Gamestats_LevelStats_t::CHUNKID );
+
+ //we can trivially find the chunk size after the chunk is written, but chunk size needs to be at the beginning, reserve the space for chunk size now
+ int iChunkSizePosition = SaveBuffer.TellPut();
+ SaveBuffer.PutUnsignedInt( 0 );
+
+ char const *szMapName = m_CustomMapStats.GetElementName( i );
+ Portal_Gamestats_LevelStats_t &mapStats = m_CustomMapStats[ i ];
+
+ SaveBuffer.PutString( szMapName );
+ mapStats.AppendSubChunksToBuffer( SaveBuffer );
+
+
+ //write out the map stats chunk size
+ {
+ int iChunkSize = SaveBuffer.TellPut() - iChunkSizePosition;
+ Assert( iChunkSize >= sizeof( int ) ); //minimum sizeof( int )
+
+#ifdef _DEBUG
+ int iOldTellPut = SaveBuffer.TellPut(); //needed for an assert below
+#endif
+
+ SaveBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, iChunkSizePosition );
+ SaveBuffer.PutUnsignedInt( iChunkSize );
+ SaveBuffer.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
+
+ Assert( iOldTellPut == SaveBuffer.TellPut() ); //writing the chunk size should have overwritten, not inserted
+ }
+ }
+ }
+
+#endif //#ifdef PORTAL_GAMESTATS_VERBOSE
+}
+
+void CPortalGameStats::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
+{
+#ifdef _DEBUG
+ unsigned short iSaveStatsVersion = LoadBuffer.GetUnsignedShort();
+ AssertOnce( iSaveStatsVersion <= PORTAL_GAMESTATS_VERSION ); //useful to know, but shouldn't be a failure case
+#else
+ LoadBuffer.GetUnsignedShort(); //don't really need the version
+#endif
+
+ int iEndPosition = LoadBuffer.TellPut();
+
+ while( LoadBuffer.TellGet() != iEndPosition )
+ {
+ Assert( (iEndPosition - LoadBuffer.TellGet()) > (sizeof( unsigned short ) + sizeof( unsigned int )) ); //at least an empty chunk left
+
+ unsigned short iChunkID = LoadBuffer.GetUnsignedShort();
+ unsigned int iChunkSize = LoadBuffer.GetUnsignedInt() - sizeof( unsigned int ); //chunk size includes the chunk size data itself
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ unsigned int iChunkEndPosition = LoadBuffer.TellGet() + iChunkSize; //used in an assert later
+#endif
+
+ switch( iChunkID )
+ {
+#ifdef PORTAL_GAMESTATS_VERBOSE //levelstats only have verbose data for the time being, so only bother to load verboseness if we're still tracking it
+ case Portal_Gamestats_LevelStats_t::CHUNKID:
+ {
+ //map chunk
+ char szMapName[256];
+ LoadBuffer.GetString( szMapName );
+
+ Portal_Gamestats_LevelStats_t *mapStats = FindOrAddMapStats( szMapName );
+ mapStats->LoadSubChunksFromBuffer( LoadBuffer, iChunkEndPosition );
+
+ break;
+ }
+#else
+ case Portal_Gamestats_LevelStats_t::CHUNKID: //warning workaround
+#endif
+ default:
+ {
+ //an unknown chunk, skip it
+ LoadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, iChunkSize );
+ }
+ };
+
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ Assert( ((unsigned int)LoadBuffer.TellGet()) == iChunkEndPosition );
+#endif
+ }
+}
+
+
+void CPortalGameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ if( CBaseGameStats::StatTrackingAllowed() == false )
+ return;
+
+ int index = m_pCurrentMapStats->m_pDeaths->AddToTail();
+ Portal_Gamestats_LevelStats_t::PlayerDeaths_t &DeathStat = m_pCurrentMapStats->m_pDeaths->Element( index );
+
+ DeathStat.ptPositionOfDeath = pPlayer->GetAbsOrigin();
+ DeathStat.iDamageType = info.GetDamageType();
+ DeathStat.szAttackerClassName[0] = '\0';
+
+ CBaseEntity *pInflictor = info.GetInflictor();
+ if( pInflictor )
+ Q_strncpy( DeathStat.szAttackerClassName, pInflictor->GetClassname(), sizeof( DeathStat.szAttackerClassName ) );
+#endif
+}
+
+void CPortalGameStats::Event_PlayerJump( const Vector &ptStartPosition, const Vector &vStartVelocity )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ if( CBaseGameStats::StatTrackingAllowed() == false )
+ return;
+
+ int index = m_pCurrentMapStats->m_pJumps->AddToTail();
+ Portal_Gamestats_LevelStats_t::JumpEvent_t &JumpStat = m_pCurrentMapStats->m_pJumps->Element( index );
+
+ JumpStat.ptPlayerPositionAtJumpStart = ptStartPosition;
+ JumpStat.vPlayerVelocityAtJumpStart = vStartVelocity;
+#endif
+}
+
+void CPortalGameStats::Event_PortalPlacement( const Vector &ptPlayerFiredFrom, const Vector &ptAttemptedPosition, char iSuccessCode )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ if( CBaseGameStats::StatTrackingAllowed() == false )
+ return;
+
+ int index = m_pCurrentMapStats->m_pPlacements->AddToTail();
+ Portal_Gamestats_LevelStats_t::PortalPlacement_t &PlacementStat = m_pCurrentMapStats->m_pPlacements->Element( index );
+
+ PlacementStat.ptPlacementPosition = ptAttemptedPosition;
+ PlacementStat.ptPlayerFiredFrom = ptPlayerFiredFrom;
+ PlacementStat.iSuccessCode = iSuccessCode;
+#endif
+}
+
+void CPortalGameStats::Event_PlayerUsed( const Vector &ptTraceStart, const Vector &vTraceDelta, CBaseEntity *pUsedEntity )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ if( CBaseGameStats::StatTrackingAllowed() == false )
+ return;
+
+ static float fLastUseTime = 0.0f;
+
+ if( fLastUseTime > gpGlobals->curtime ) //I'm not positive, but I think curtime resets between levels, cheap to do this
+ fLastUseTime = 0.0f;
+
+ if( (gpGlobals->curtime - fLastUseTime) < 0.25f ) //use events cluster
+ return;
+
+ fLastUseTime = gpGlobals->curtime;
+
+ int index = m_pCurrentMapStats->m_pUseEvents->AddToTail();
+ Portal_Gamestats_LevelStats_t::PlayerUse_t &UseEvent = m_pCurrentMapStats->m_pUseEvents->Element( index );
+
+ UseEvent.ptTraceStart = ptTraceStart;
+ UseEvent.vTraceDelta = vTraceDelta;
+ UseEvent.szUseEntityClassName[0] = '\0';
+
+ if( pUsedEntity )
+ Q_strncpy( UseEvent.szUseEntityClassName, pUsedEntity->GetClassname(), sizeof( UseEvent.szUseEntityClassName ) );
+#endif
+}
+
+void CPortalGameStats::Event_PlayerStuck( CPortal_Player *pPlayer )
+{
+#ifdef PORTAL_GAMESTATS_VERBOSE
+ if( CBaseGameStats::StatTrackingAllowed() == false )
+ return;
+
+ static float fLastStuckTime = 0.0f;
+
+ if( fLastStuckTime > gpGlobals->curtime ) //I'm not positive, but I think curtime resets between levels, cheap to do this
+ fLastStuckTime = 0.0f;
+
+ if( (gpGlobals->curtime - fLastStuckTime) < 10.0f ) //only log one stuck spot per 10 second interval (in case it oscillates)
+ return;
+
+ fLastStuckTime = gpGlobals->curtime;
+
+ int index = m_pCurrentMapStats->m_pStuckSpots->AddToTail();
+ Portal_Gamestats_LevelStats_t::StuckEvent_t &StuckSpot = m_pCurrentMapStats->m_pStuckSpots->Element( index );
+
+ StuckSpot.ptPlayerPosition = pPlayer->GetAbsOrigin();
+ StuckSpot.qPlayerAngles = pPlayer->GetAbsAngles();
+ StuckSpot.bNearPortal = (pPlayer->m_hPortalEnvironment.Get() != NULL);
+ StuckSpot.bDucking = ((pPlayer->m_nButtons & IN_DUCK) != 0);
+#endif
+}
+
+void CPortalGameStats::Event_LevelInit( void )
+{
+ BaseClass::Event_LevelInit();
+ m_pCurrentMapStats = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
+}
+
+void CPortalGameStats::Event_MapChange( const char *szOldMapName, const char *szNewMapName )
+{
+ BaseClass::Event_MapChange( szOldMapName, szNewMapName );
+ m_pCurrentMapStats = FindOrAddMapStats( szNewMapName );
+}
+
+
+
+void CPortalGameStats::Clear( void )
+{
+ for( int i = m_CustomMapStats.First(); i != m_CustomMapStats.InvalidIndex(); i = m_CustomMapStats.Next( i ) )
+ {
+ DestroyLevelStatPointers( &m_CustomMapStats[i] );
+ }
+
+ m_CustomMapStats.RemoveAll();
+}
+
+Portal_Gamestats_LevelStats_t *CPortalGameStats::FindOrAddMapStats( const char *szMapName )
+{
+ int idx = m_CustomMapStats.Find( szMapName );
+ if( idx == m_CustomMapStats.InvalidIndex() )
+ {
+ idx = m_CustomMapStats.Insert( szMapName );
+
+ CreateLevelStatPointers( &m_CustomMapStats[idx] );
+ }
+
+ return &m_CustomMapStats[ idx ];
+}
+
+
+void CreateLevelStatPointers( Portal_Gamestats_LevelStats_t *pFillIn )
+{
+ pFillIn->m_pDeaths = new CUtlVector<Portal_Gamestats_LevelStats_t::PlayerDeaths_t>;
+ pFillIn->m_pPlacements = new CUtlVector<Portal_Gamestats_LevelStats_t::PortalPlacement_t>;
+ pFillIn->m_pUseEvents = new CUtlVector<Portal_Gamestats_LevelStats_t::PlayerUse_t>;
+ pFillIn->m_pStuckSpots = new CUtlVector<Portal_Gamestats_LevelStats_t::StuckEvent_t>;
+ pFillIn->m_pJumps = new CUtlVector<Portal_Gamestats_LevelStats_t::JumpEvent_t>;
+ pFillIn->m_pTimeSpentInVisLeafs = new CUtlVector<Portal_Gamestats_LevelStats_t::LeafTimes_t>;
+}
+
+void DestroyLevelStatPointers( Portal_Gamestats_LevelStats_t *pDestroyFrom )
+{
+ delete pDestroyFrom->m_pDeaths;
+ delete pDestroyFrom->m_pPlacements;
+ delete pDestroyFrom->m_pUseEvents;
+ delete pDestroyFrom->m_pStuckSpots;
+ delete pDestroyFrom->m_pJumps;
+ delete pDestroyFrom->m_pTimeSpentInVisLeafs;
+}
+
+
+
+static char const *portalMaps[] =
+{
+ "testchmb_a_00",
+ "testchmb_a_01",
+ "testchmb_a_02",
+ "testchmb_a_03",
+ "testchmb_a_04",
+ "testchmb_a_05",
+ "testchmb_a_06",
+ "testchmb_a_07",
+ "testchmb_a_08",
+ "testchmb_a_09",
+ "testchmb_a_10",
+ "testchmb_a_11",
+ //"testchmb_a_12", //12 got deleted/skipped
+ "testchmb_a_13",
+ "testchmb_a_14",
+ "testchmb_a_15",
+ "escape_00",
+ "escape_01",
+ "escape_02"
+};
+
+
+bool CPortalGameStats::UserPlayedAllTheMaps( void )
+{
+ int c = ARRAYSIZE( portalMaps );
+ for ( int i = 0; i < c; ++i )
+ {
+ int idx = m_BasicStats.m_MapTotals.Find( portalMaps[ i ] );
+ if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
+ return false;
+ }
+
+ return true;
+}
+
+
diff --git a/game/server/portal/portal_gamestats.h b/game/server/portal/portal_gamestats.h
new file mode 100644
index 0000000..7450900
--- /dev/null
+++ b/game/server/portal/portal_gamestats.h
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef PORTAL_GAMESTATS_H
+#define PORTAL_GAMESTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "gamestats.h"
+
+#define PORTAL_GAMESTATS_VERSION 001
+
+//#define PORTAL_GAMESTATS_VERBOSE //verbose logging of extra stats, only a good idea during internal tests, centralized here for easy on/off
+
+
+
+//NEVER REMOVE A STRUCTURE OR CHANGE A CHUNKID. If you need to change how a chunk works, make a new structure with a new ID
+
+struct Portal_Gamestats_LevelStats_t //most of this is only tracked in verbose mode
+{
+ static const unsigned short CHUNKID = 1;
+
+ //substructures
+ struct PlayerDeaths_t
+ {
+ static const unsigned short CHUNKID = 1; //subchunks start over with id's
+ static const unsigned short TRIMSIZE = 200; //trim logs if more than this many entries exist for a single map
+ Vector ptPositionOfDeath;
+ int iDamageType;
+ char szAttackerClassName[32];
+ };
+
+ struct PortalPlacement_t
+ {
+ static const unsigned short CHUNKID = 2;
+ static const unsigned short TRIMSIZE = 1000; //trim logs if more than this many entries exist for a single map
+ Vector ptPlayerFiredFrom;
+ Vector ptPlacementPosition;
+ char iSuccessCode;
+ };
+
+ struct PlayerUse_t
+ {
+ static const unsigned short CHUNKID = 3;
+ static const unsigned short TRIMSIZE = 500; //trim logs if more than this many entries exist for a single map
+ Vector ptTraceStart;
+ Vector vTraceDelta;
+ char szUseEntityClassName[32];
+ };
+
+ struct StuckEvent_t
+ {
+ static const unsigned short CHUNKID = 4;
+ static const unsigned short TRIMSIZE = 100; //trim logs if more than this many entries exist for a single map
+ Vector ptPlayerPosition;
+ QAngle qPlayerAngles;
+ bool bNearPortal;
+ bool bDucking;
+ };
+
+ struct JumpEvent_t
+ {
+ static const unsigned short CHUNKID = 5;
+ static const unsigned short TRIMSIZE = 1000; //trim logs if more than this many entries exist for a single map
+ Vector ptPlayerPositionAtJumpStart;
+ Vector vPlayerVelocityAtJumpStart;
+ };
+
+ struct LeafTimes_t
+ {
+ static const unsigned short CHUNKID = 6;
+ static const unsigned short TRIMSIZE = 10000; //trim logs if more than this many entries exist for a single map
+ float fTimeSpentInVisLeaf;
+ LeafTimes_t( void ) : fTimeSpentInVisLeaf( 0.0f ) { };
+ };
+
+ //these are created/destroyed by parent CPortalGameStats to avoid create/copy/destroy confusion
+ CUtlVector<PlayerDeaths_t> *m_pDeaths;
+ CUtlVector<PortalPlacement_t> *m_pPlacements;
+ CUtlVector<PlayerUse_t> *m_pUseEvents;
+ CUtlVector<StuckEvent_t> *m_pStuckSpots;
+ CUtlVector<JumpEvent_t> *m_pJumps;
+ CUtlVector<LeafTimes_t> *m_pTimeSpentInVisLeafs;
+
+ void AppendSubChunksToBuffer( CUtlBuffer &SaveBuffer );
+ void LoadSubChunksFromBuffer( CUtlBuffer &LoadBuffer, unsigned int iChunkEndPosition );
+ void Clear( void );
+};
+
+
+class CPortal_Player;
+
+class CPortalGameStats : CBaseGameStats
+{
+ typedef CBaseGameStats BaseClass;
+
+public:
+ ~CPortalGameStats( void );
+ CPortalGameStats( void );
+
+ void Clear( void );
+
+ virtual void Event_LevelInit( void );
+ virtual void Event_MapChange( const char *szOldMapName, const char *szNewMapName );
+ virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
+ void Event_PortalPlacement( const Vector &ptPlayerFiredFrom, const Vector &ptAttemptedPosition, char iSuccessCode );
+ void Event_PlayerJump( const Vector &ptStartPosition, const Vector &vStartVelocity );
+ void Event_PlayerUsed( const Vector &ptTraceStart, const Vector &vTraceDelta, CBaseEntity *pUsedEntity );
+ void Event_PlayerStuck( CPortal_Player *pPlayer );
+
+ virtual bool StatTrackingEnabledForMod( void ) { return true; }
+ virtual bool UserPlayedAllTheMaps( void );
+
+#ifdef _DEBUG
+ virtual bool AutoUpload_OnShutdown( void ) { return false; } //don't upload while we're debugging
+#endif
+
+ virtual void AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer );
+ virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer );
+
+ Portal_Gamestats_LevelStats_t *m_pCurrentMapStats;
+
+protected:
+ CUtlDict< Portal_Gamestats_LevelStats_t, unsigned short > m_CustomMapStats;
+ Portal_Gamestats_LevelStats_t *FindOrAddMapStats( const char *szMapName );
+};
+
+extern CPortalGameStats g_PortalGameStats;
+
+
+void CreateLevelStatPointers( Portal_Gamestats_LevelStats_t *pFillIn );
+void DestroyLevelStatPointers( Portal_Gamestats_LevelStats_t *pDestroyFrom );
+
+
+#endif // PORTAL_GAMESTATS_H
diff --git a/game/server/portal/portal_mp_client.cpp b/game/server/portal/portal_mp_client.cpp
new file mode 100644
index 0000000..837be53
--- /dev/null
+++ b/game/server/portal/portal_mp_client.cpp
@@ -0,0 +1,199 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== portal_client.cpp ========================================================
+
+ Portal client/server game specific stuff
+
+*/
+
+#include "cbase.h"
+#include "portal_player.h"
+#include "portal_gamerules.h"
+#include "gamerules.h"
+#include "teamplay_gamerules.h"
+#include "EntityList.h"
+#include "physics.h"
+#include "game.h"
+#include "player_resource.h"
+#include "engine/IEngineSound.h"
+#include "team.h"
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+void Host_Say( edict_t *pEdict, bool teamonly );
+
+extern CBaseEntity* FindPickerEntityClass( CBasePlayer *pPlayer, char *classname );
+extern bool g_fGameOver;
+
+
+
+void FinishClientPutInServer( CPortal_Player *pPlayer )
+{
+ pPlayer->InitialSpawn();
+ pPlayer->Spawn();
+
+
+ char sName[128];
+ Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
+
+ // First parse the name and remove any %'s
+ for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
+ {
+ // Replace it with a space
+ if ( *pApersand == '%' )
+ *pApersand = ' ';
+ }
+
+ // notify other clients of player joining the game
+ UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
+
+ if ( PortalMPGameRules()->IsTeamplay() == true )
+ {
+ ClientPrint( pPlayer, HUD_PRINTTALK, "You are on team %s1\n", pPlayer->GetTeam()->GetName() );
+ }
+
+ const ConVar *hostname = cvar->FindVar( "hostname" );
+ const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
+
+ KeyValues *data = new KeyValues("data");
+ data->SetString( "title", title ); // info panel title
+ data->SetString( "type", "1" ); // show userdata from stringtable entry
+ data->SetString( "msg", "motd" ); // use this stringtable entry
+
+ //pPlayer->ShowViewPortPanel( PANEL_INFO, true, data );
+
+ data->deleteThis();
+}
+
+/*
+===========
+ClientPutInServer
+
+called each time a player is spawned into the game
+============
+*/
+void ClientPutInServer( edict_t *pEdict, const char *playername )
+{
+ // Allocate a CBasePlayer for pev, and call spawn
+ CPortal_Player *pPlayer = CPortal_Player::CreatePlayer( "player", pEdict );
+ pPlayer->PlayerData()->netname = AllocPooledString( playername );
+}
+
+
+
+
+void ClientActive( edict_t *pEdict, bool bLoadGame )
+{
+ Assert( !bLoadGame );
+ CPortal_Player *pPlayer = dynamic_cast< CPortal_Player* >( CBaseEntity::Instance( pEdict ) );
+ Assert( pPlayer );
+
+ FinishClientPutInServer( pPlayer );
+}
+
+
+/*
+===============
+const char *GetGameDescription()
+
+Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
+===============
+*/
+const char *GetGameDescription()
+{
+ if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
+ return g_pGameRules->GetGameDescription();
+ else
+ return "Half-Life 2";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a player and optional name returns the entity of that
+// classname that the player is nearest facing
+//
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+CBaseEntity* FindEntity( edict_t *pEdict, char *classname)
+{
+ // If no name was given set bits based on the picked
+ if (FStrEq(classname,""))
+ {
+ return (FindPickerEntityClass( static_cast<CBasePlayer*>(GetContainingEntity(pEdict)), classname ));
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache game-specific models & sounds
+//-----------------------------------------------------------------------------
+void ClientGamePrecache( void )
+{
+ CBaseEntity::PrecacheModel("models/player.mdl");
+ CBaseEntity::PrecacheModel( "models/gibs/agibs.mdl" );
+ CBaseEntity::PrecacheModel("models/weapons/v_hands.mdl");
+
+ CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowAmmo" );
+ CBaseEntity::PrecacheScriptSound( "HUDQuickInfo.LowHealth" );
+
+ CBaseEntity::PrecacheScriptSound( "FX_AntlionImpact.ShellImpact" );
+ CBaseEntity::PrecacheScriptSound( "Missile.ShotDown" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.DefaultNearmiss" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.GunshipNearmiss" );
+ CBaseEntity::PrecacheScriptSound( "Bullets.StriderNearmiss" );
+
+ CBaseEntity::PrecacheScriptSound( "Geiger.BeepHigh" );
+ CBaseEntity::PrecacheScriptSound( "Geiger.BeepLow" );
+
+ CBaseEntity::PrecacheModel( "models/portals/portal1.mdl" );
+ CBaseEntity::PrecacheModel( "models/portals/portal2.mdl" );
+}
+
+
+// called by ClientKill and DeadThink
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
+{
+ if (gpGlobals->coop || gpGlobals->deathmatch)
+ {
+ if ( fCopyCorpse )
+ {
+ // make a copy of the dead body for appearances sake
+ ((CPortal_Player *)pEdict)->CreateCorpse();
+ }
+
+ // respawn player
+ pEdict->Spawn();
+ }
+ else
+ { // restart the entire server
+ engine->ServerCommand("reload\n");
+ }
+}
+
+void GameStartFrame( void )
+{
+ VPROF("GameStartFrame()");
+ if ( g_fGameOver )
+ return;
+
+ gpGlobals->teamplay = (teamplay.GetInt() != 0);
+}
+
+//=========================================================
+// instantiate the proper game rules object
+//=========================================================
+void InstallGameRules()
+{
+ CreateGameRulesObject( "CPortalMPGameRules" );
+}
+
diff --git a/game/server/portal/portal_physics_collisionevent.cpp b/game/server/portal/portal_physics_collisionevent.cpp
new file mode 100644
index 0000000..1b216a6
--- /dev/null
+++ b/game/server/portal/portal_physics_collisionevent.cpp
@@ -0,0 +1,486 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "portal_physics_collisionevent.h"
+#include "physicsshadowclone.h"
+#include "prop_combine_ball.h"
+#include "prop_portal.h"
+#include "portal_player.h"
+#include "portal/weapon_physcannon.h" //grab controller
+
+
+int CPortal_CollisionEvent::ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 )
+{
+ if ( !pGameData0 || !pGameData1 )
+ return 1;
+
+ AssertOnce( pObj0 && pObj1 );
+ bool bShadowClonesInvolved = ((pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE) != 0;
+
+ if( bShadowClonesInvolved )
+ {
+ //at least one shadow clone
+
+ if( (pObj0->GetGameFlags() & pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE )
+ return 0; //both are shadow clones
+
+ if( (pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_PLAYER_HELD )
+ {
+ //at least one is held
+
+ //don't let players collide with objects they're holding, they get kinda messed up sometimes
+ if( pGameData0 && ((CBaseEntity *)pGameData0)->IsPlayer() && (GetPlayerHeldEntity( (CBasePlayer *)pGameData0 ) == (CBaseEntity *)pGameData1) )
+ return 0;
+
+ if( pGameData1 && ((CBaseEntity *)pGameData1)->IsPlayer() && (GetPlayerHeldEntity( (CBasePlayer *)pGameData1 ) == (CBaseEntity *)pGameData0) )
+ return 0;
+ }
+ }
+
+
+
+ //everything is in one environment. This means we must tightly control what collides with what
+ if( pGameData0 != pGameData1 )
+ {
+ //this code only decides what CAN'T collide due to portal environment differences, things that should collide will pass through here to deeper ShouldCollide() code
+ CBaseEntity *pEntities[2] = { (CBaseEntity *)pGameData0, (CBaseEntity *)pGameData1 };
+ IPhysicsObject *pPhysObjects[2] = { pObj0, pObj1 };
+ bool bStatic[2] = { pObj0->IsStatic(), pObj1->IsStatic() };
+ CPortalSimulator *pSimulators[2];
+ for( int i = 0; i != 2; ++i )
+ pSimulators[i] = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntities[i] );
+
+ AssertOnce( (bStatic[0] && bStatic[1]) == false ); //hopefully the system doesn't even call in for this, they're both static and can't collide
+ if( bStatic[0] && bStatic[1] )
+ return 0;
+
+#ifdef _DEBUG
+ for( int i = 0; i != 2; ++i )
+ {
+ if( (pSimulators[i] != NULL) && CPhysicsShadowClone::IsShadowClone( pEntities[i] ) )
+ {
+ CPhysicsShadowClone *pClone = (CPhysicsShadowClone *)pEntities[i];
+ CBaseEntity *pSource = pClone->GetClonedEntity();
+
+ CPortalSimulator *pSourceSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pSource );
+ Assert( (pSimulators[i]->m_DataAccess.Simulation.Dynamic.EntFlags[pClone->entindex()] & PSEF_IS_IN_PORTAL_HOLE) == (pSourceSimulator->m_DataAccess.Simulation.Dynamic.EntFlags[pSource->entindex()] & PSEF_IS_IN_PORTAL_HOLE) );
+ }
+ }
+#endif
+
+ if( pSimulators[0] == pSimulators[1] ) //same simulator
+ {
+ if( pSimulators[0] != NULL ) //and not main world
+ {
+ if( bStatic[0] || bStatic[1] )
+ {
+ for( int i = 0; i != 2; ++i )
+ {
+ if( bStatic[i] )
+ {
+ if( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[i] ) )
+ {
+ PS_PhysicsObjectSourceType_t objectSource;
+ if( pSimulators[i]->CreatedPhysicsObject( pPhysObjects[i], &objectSource ) &&
+ ((objectSource == PSPOST_REMOTE_BRUSHES) || (objectSource == PSPOST_REMOTE_STATICPROPS)) )
+ {
+ if( (pSimulators[1-i]->m_DataAccess.Simulation.Dynamic.EntFlags[pEntities[1-i]->entindex()] & PSEF_IS_IN_PORTAL_HOLE) == 0 )
+ return 0; //require that the entity be in the portal hole before colliding with transformed geometry
+ //FIXME: The above requirement might fail horribly for transformed collision blocking the portal from the other side and fast moving objects
+ }
+ }
+ break;
+ }
+ }
+ }
+ else if( bShadowClonesInvolved )
+ {
+ if( ((pSimulators[0]->m_DataAccess.Simulation.Dynamic.EntFlags[pEntities[0]->entindex()] |
+ pSimulators[1]->m_DataAccess.Simulation.Dynamic.EntFlags[pEntities[1]->entindex()]) &
+ PSEF_IS_IN_PORTAL_HOLE) == 0 )
+ {
+ return 0; //neither entity was actually in the portal hole
+ }
+ }
+ }
+ }
+ else //different simulators
+ {
+ if( bShadowClonesInvolved ) //entities can only collide with shadow clones "owned" by the same simulator.
+ return 0;
+
+ if( bStatic[0] || bStatic[1] )
+ {
+ for( int i = 0; i != 2; ++i )
+ {
+ if( bStatic[i] )
+ {
+ int j = 1-i;
+ CPortalSimulator *pSimulator_Entity = pSimulators[j];
+
+ if( pEntities[i]->IsWorld() )
+ {
+ Assert( CPortalSimulator::GetSimulatorThatCreatedPhysicsObject( pPhysObjects[i] ) == NULL );
+ if( pSimulator_Entity )
+ return 0;
+ }
+ else
+ {
+ CPortalSimulator *pSimulator_Static = CPortalSimulator::GetSimulatorThatCreatedPhysicsObject( pPhysObjects[i] ); //might have been a static prop which would yield a new simulator
+
+ if( pSimulator_Static && (pSimulator_Static != pSimulator_Entity) )
+ return 0; //static collideable is from a different simulator
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ Assert( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[0] ) == false );
+ Assert( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[1] ) == false );
+
+ for( int i = 0; i != 2; ++i )
+ {
+ if( pSimulators[i] )
+ {
+ //entities in the physics environment only collide with statics created by the environment (handled above), entities in the same environment (also above), or entities that should be cloned from main to the same environment
+ if( (pSimulators[i]->m_DataAccess.Simulation.Dynamic.EntFlags[pEntities[1-i]->entindex()] & PSEF_CLONES_ENTITY_FROM_MAIN) == 0 ) //not cloned from main
+ return 0;
+ }
+ }
+ }
+
+ }
+ }
+
+ return BaseClass::ShouldCollide( pObj0, pObj1, pGameData0, pGameData1 );
+}
+
+
+
+
+int CPortal_CollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt )
+{
+ if( (pGameData0 == NULL) || (pGameData1 == NULL) )
+ return 0;
+
+ if( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( (CBaseEntity *)pGameData0 ) ||
+ CPSCollisionEntity::IsPortalSimulatorCollisionEntity( (CBaseEntity *)pGameData1 ) )
+ return 0;
+
+ // For portal, don't solve penetrations on combine balls
+ if( FClassnameIs( (CBaseEntity *)pGameData0, "prop_energy_ball" ) ||
+ FClassnameIs( (CBaseEntity *)pGameData1, "prop_energy_ball" ) )
+ return 0;
+
+ if( (pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_PLAYER_HELD )
+ {
+ //at least one is held
+ CBaseEntity *pHeld;
+ CBaseEntity *pOther;
+ IPhysicsObject *pPhysHeld;
+ IPhysicsObject *pPhysOther;
+ if( pObj0->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ pHeld = (CBaseEntity *)pGameData0;
+ pPhysHeld = pObj0;
+ pOther = (CBaseEntity *)pGameData1;
+ pPhysOther = pObj1;
+ }
+ else
+ {
+ pHeld = (CBaseEntity *)pGameData1;
+ pPhysHeld = pObj1;
+ pOther = (CBaseEntity *)pGameData0;
+ pPhysOther = pObj0;
+ }
+
+ //don't let players collide with objects they're holding, they get kinda messed up sometimes
+ if( pOther->IsPlayer() && (GetPlayerHeldEntity( (CBasePlayer *)pOther ) == pHeld) )
+ return 0;
+
+ //held objects are clipping into other objects when travelling across a portal. We're close to ship, so this seems to be the
+ //most localized way to make a fix.
+ //Note that we're not actually going to change whether it should solve, we're just going to tack on some hacks
+ CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pHeld );
+ if( !pHoldingPlayer && CPhysicsShadowClone::IsShadowClone( pHeld ) )
+ pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( ((CPhysicsShadowClone *)pHeld)->GetClonedEntity() );
+
+ Assert( pHoldingPlayer );
+ if( pHoldingPlayer )
+ {
+ CGrabController *pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
+
+ if ( !pGrabController )
+ pGrabController = GetGrabControllerForPhysCannon( pHoldingPlayer->GetActiveWeapon() );
+
+ Assert( pGrabController );
+ if( pGrabController )
+ {
+ GrabController_SetPortalPenetratingEntity( pGrabController, pOther );
+ }
+
+ //NDebugOverlay::EntityBounds( pHeld, 0, 0, 255, 16, 1.0f );
+ //NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 16, 1.0f );
+ //pPhysOther->Wake();
+ //FindClosestPassableSpace( pOther, Vector( 0.0f, 0.0f, 1.0f ) );
+ }
+ }
+
+
+ if( (pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE )
+ {
+ //at least one shadowclone is involved
+
+ if( (pObj0->GetGameFlags() & pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE ) //don't solve between two shadowclones, they're just going to resync in a frame anyways
+ return 0;
+
+
+
+ IPhysicsObject * const pObjects[2] = { pObj0, pObj1 };
+
+ for( int i = 0; i != 2; ++i )
+ {
+ if( pObjects[i]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
+ {
+ int j = 1 - i;
+ if( !pObjects[j]->IsMoveable() )
+ return 0; //don't solve between shadow clones and statics
+
+ if( ((CPhysicsShadowClone *)(pObjects[i]->GetGameData()))->GetClonedEntity() == (pObjects[j]->GetGameData()) )
+ return 0; //don't solve between a shadow clone and its source entity
+ }
+ }
+ }
+
+ return BaseClass::ShouldSolvePenetration( pObj0, pObj1, pGameData0, pGameData1, dt );
+}
+
+
+
+// Data for energy ball vs held item mass swapping hack
+static float s_fSavedMass[2];
+static bool s_bChangedMass[2] = { false, false };
+static bool s_bUseUnshadowed[2] = { false, false };
+static IPhysicsObject *s_pUnshadowed[2] = { NULL, NULL };
+
+
+static void ModifyWeight_PreCollision( vcollisionevent_t *pEvent )
+{
+ Assert( (pEvent->pObjects[0] != NULL) && (pEvent->pObjects[1] != NULL) );
+
+ CBaseEntity *pUnshadowedEntities[2];
+ IPhysicsObject *pUnshadowedObjects[2];
+
+ for( int i = 0; i != 2; ++i )
+ {
+ if( pEvent->pObjects[i]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
+ {
+ CPhysicsShadowClone *pClone = ((CPhysicsShadowClone *)pEvent->pObjects[i]->GetGameData());
+ pUnshadowedEntities[i] = pClone->GetClonedEntity();
+
+ if( pUnshadowedEntities[i] == NULL )
+ return;
+
+ pUnshadowedObjects[i] = pClone->TranslatePhysicsToClonedEnt( pEvent->pObjects[i] );
+
+ if( pUnshadowedObjects[i] == NULL )
+ return;
+ }
+ else
+ {
+ pUnshadowedEntities[i] = (CBaseEntity *)pEvent->pObjects[i]->GetGameData();
+ pUnshadowedObjects[i] = pEvent->pObjects[i];
+ }
+ }
+
+ // HACKHACK: Reduce mass for combine ball vs movable brushes so the collision
+ // appears fully elastic regardless of mass ratios
+ for( int i = 0; i != 2; ++i )
+ {
+ int j = 1-i;
+
+ // One is a combine ball, if the other is a movable brush, reduce the combine ball mass
+ if ( dynamic_cast<CPropCombineBall *>(pUnshadowedEntities[j]) != NULL && pUnshadowedEntities[i] != NULL )
+ {
+ if ( pUnshadowedEntities[i]->GetMoveType() == MOVETYPE_PUSH )
+ {
+ s_bChangedMass[j] = true;
+ s_fSavedMass[j] = pUnshadowedObjects[j]->GetMass();
+ pEvent->pObjects[j]->SetMass( VPHYSICS_MIN_MASS );
+ if( pUnshadowedObjects[j] != pEvent->pObjects[j] )
+ {
+ s_bUseUnshadowed[j] = true;
+ s_pUnshadowed[j] = pUnshadowedObjects[j];
+
+ pUnshadowedObjects[j]->SetMass( VPHYSICS_MIN_MASS );
+ }
+ }
+
+ //HACKHACK: last minute problem knocking over turrets with energy balls, up the mass of the ball by a lot
+ if( FClassnameIs( pUnshadowedEntities[i], "npc_portal_turret_floor" ) )
+ {
+ pUnshadowedObjects[j]->SetMass( pUnshadowedEntities[i]->VPhysicsGetObject()->GetMass() );
+ }
+ }
+ }
+
+ for( int i = 0; i != 2; ++i )
+ {
+ if( ( pUnshadowedObjects[i] && pUnshadowedObjects[i]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
+ {
+ int j = 1-i;
+ if( dynamic_cast<CPropCombineBall *>(pUnshadowedEntities[j]) != NULL )
+ {
+ // [j] is the combine ball, set mass low
+ // if the above ball vs brush entity check didn't already change the mass, change the mass
+ if ( !s_bChangedMass[j] )
+ {
+ s_bChangedMass[j] = true;
+ s_fSavedMass[j] = pUnshadowedObjects[j]->GetMass();
+ pEvent->pObjects[j]->SetMass( VPHYSICS_MIN_MASS );
+ if( pUnshadowedObjects[j] != pEvent->pObjects[j] )
+ {
+ s_bUseUnshadowed[j] = true;
+ s_pUnshadowed[j] = pUnshadowedObjects[j];
+
+ pUnshadowedObjects[j]->SetMass( VPHYSICS_MIN_MASS );
+ }
+ }
+
+ // [i] is the held object, set mass high
+ s_bChangedMass[i] = true;
+ s_fSavedMass[i] = pUnshadowedObjects[i]->GetMass();
+ pEvent->pObjects[i]->SetMass( VPHYSICS_MAX_MASS );
+ if( pUnshadowedObjects[i] != pEvent->pObjects[i] )
+ {
+ s_bUseUnshadowed[i] = true;
+ s_pUnshadowed[i] = pUnshadowedObjects[i];
+
+ pUnshadowedObjects[i]->SetMass( VPHYSICS_MAX_MASS );
+ }
+ }
+ else if( pEvent->pObjects[j]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
+ {
+ //held object vs shadow clone, set held object mass back to grab controller saved mass
+
+ // [i] is the held object
+ s_bChangedMass[i] = true;
+ s_fSavedMass[i] = pUnshadowedObjects[i]->GetMass();
+
+ CGrabController *pGrabController = NULL;
+ CBaseEntity *pLookingForEntity = (CBaseEntity*)pEvent->pObjects[i]->GetGameData();
+ CBasePlayer *pHoldingPlayer = GetPlayerHoldingEntity( pLookingForEntity );
+ if( pHoldingPlayer )
+ pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
+
+ float fSavedMass, fSavedRotationalDamping;
+
+ AssertMsg( pGrabController, "Physics object is held, but we can't find the holding controller." );
+ GetSavedParamsForCarriedPhysObject( pGrabController, pUnshadowedObjects[i], &fSavedMass, &fSavedRotationalDamping );
+
+ pEvent->pObjects[i]->SetMass( fSavedMass );
+ if( pUnshadowedObjects[i] != pEvent->pObjects[i] )
+ {
+ s_bUseUnshadowed[i] = true;
+ s_pUnshadowed[i] = pUnshadowedObjects[i];
+
+ pUnshadowedObjects[i]->SetMass( fSavedMass );
+ }
+ }
+ }
+ }
+}
+
+
+
+void CPortal_CollisionEvent::PreCollision( vcollisionevent_t *pEvent )
+{
+ ModifyWeight_PreCollision( pEvent );
+ return BaseClass::PreCollision( pEvent );
+}
+
+
+static void ModifyWeight_PostCollision( vcollisionevent_t *pEvent )
+{
+ for( int i = 0; i != 2; ++i )
+ {
+ if( s_bChangedMass[i] )
+ {
+ pEvent->pObjects[i]->SetMass( s_fSavedMass[i] );
+ if( s_bUseUnshadowed[i] )
+ {
+ s_pUnshadowed[i]->SetMass( s_fSavedMass[i] );
+ s_bUseUnshadowed[i] = false;
+ }
+ s_bChangedMass[i] = false;
+ }
+ }
+}
+
+void CPortal_CollisionEvent::PostCollision( vcollisionevent_t *pEvent )
+{
+ ModifyWeight_PostCollision( pEvent );
+
+ return BaseClass::PostCollision( pEvent );
+}
+
+void CPortal_CollisionEvent::PostSimulationFrame()
+{
+ //this actually happens once per physics environment simulation, and we don't want that, so do nothing and we'll get a different version manually called
+}
+
+void CPortal_CollisionEvent::PortalPostSimulationFrame( void )
+{
+ BaseClass::PostSimulationFrame();
+}
+
+
+void CPortal_CollisionEvent::AddDamageEvent( CBaseEntity *pEntity, const CTakeDamageInfo &info, IPhysicsObject *pInflictorPhysics, bool bRestoreVelocity, const Vector &savedVel, const AngularImpulse &savedAngVel )
+{
+ const CTakeDamageInfo *pPassDownInfo = &info;
+ CTakeDamageInfo ReplacementDamageInfo; //only used some of the time
+
+ if( (info.GetDamageType() & DMG_CRUSH) &&
+ (pInflictorPhysics->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE) &&
+ (!info.BaseDamageIsValid()) &&
+ (info.GetDamageForce().LengthSqr() > (20000.0f * 20000.0f))
+ )
+ {
+ //VERY likely this was caused by the penetration solver. Since a shadow clone is involved we're going to ignore it becuase it causes more problems than it solves in this case
+ ReplacementDamageInfo = info;
+ ReplacementDamageInfo.SetDamage( 0.0f );
+ pPassDownInfo = &ReplacementDamageInfo;
+ }
+
+ BaseClass::AddDamageEvent( pEntity, *pPassDownInfo, pInflictorPhysics, bRestoreVelocity, savedVel, savedAngVel );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/game/server/portal/portal_physics_collisionevent.h b/game/server/portal/portal_physics_collisionevent.h
new file mode 100644
index 0000000..9b5a2ee
--- /dev/null
+++ b/game/server/portal/portal_physics_collisionevent.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef PORTAL_PHYSICS_COLLISIONEVENT_H
+#define PORTAL_PHYSICS_COLLISIONEVENT_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "physics_collisionevent.h"
+
+class CPortal_CollisionEvent : public CCollisionEvent
+{
+public:
+ DECLARE_CLASS_GAMEROOT( CPortal_CollisionEvent, CCollisionEvent );
+
+ virtual int ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 );
+ virtual void PreCollision( vcollisionevent_t *pEvent );
+ virtual void PostCollision( vcollisionevent_t *pEvent );
+ virtual int ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt );
+
+ virtual void PostSimulationFrame( void );
+ void PortalPostSimulationFrame( void );
+ void AddDamageEvent( CBaseEntity *pEntity, const CTakeDamageInfo &info, IPhysicsObject *pInflictorPhysics, bool bRestoreVelocity, const Vector &savedVel, const AngularImpulse &savedAngVel );
+};
+
+#endif //#ifndef PORTAL_PHYSICS_COLLISIONEVENT_H
diff --git a/game/server/portal/portal_placement.cpp b/game/server/portal/portal_placement.cpp
new file mode 100644
index 0000000..1a5dcef
--- /dev/null
+++ b/game/server/portal/portal_placement.cpp
@@ -0,0 +1,1337 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+#include "portal_placement.h"
+#include "portal_shareddefs.h"
+#include "prop_portal_shared.h"
+#include "func_noportal_volume.h"
+#include "BasePropDoor.h"
+#include "collisionutils.h"
+#include "decals.h"
+#include "physicsshadowclone.h"
+
+
+#define MAXIMUM_BUMP_DISTANCE ( ( PORTAL_HALF_WIDTH * 2.0f ) * ( PORTAL_HALF_WIDTH * 2.0f ) + ( PORTAL_HALF_HEIGHT * 2.0f ) * ( PORTAL_HALF_HEIGHT * 2.0f ) ) / 2.0f
+
+
+struct CPortalCornerFitData
+{
+ trace_t trCornerTrace;
+ Vector ptIntersectionPoint;
+ Vector vIntersectionDirection;
+ Vector vBumpDirection;
+ bool bCornerIntersection;
+ bool bSoftBump;
+};
+
+
+CUtlVector<CBaseEntity *> g_FuncBumpingEntityList;
+bool g_bBumpedByLinkedPortal;
+
+
+ConVar sv_portal_placement_debug ("sv_portal_placement_debug", "0", FCVAR_REPLICATED );
+ConVar sv_portal_placement_never_bump ("sv_portal_placement_never_bump", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+
+bool IsMaterialInList( const csurface_t &surface, char *g_ppszMaterials[] )
+{
+ char szLowerName[ 256 ];
+ Q_strcpy( szLowerName, surface.name );
+ Q_strlower( szLowerName );
+
+ int iMaterial = 0;
+
+ while ( g_ppszMaterials[ iMaterial ] )
+ {
+ if ( Q_strstr( szLowerName, g_ppszMaterials[ iMaterial ] ) )
+ return true;
+
+ ++iMaterial;
+ }
+
+ return false;
+}
+
+bool IsNoPortalMaterial( const csurface_t &surface )
+{
+ if ( surface.flags & SURF_NOPORTAL )
+ return true;
+
+ const surfacedata_t *pdata = physprops->GetSurfaceData( surface.surfaceProps );
+ if ( pdata->game.material == CHAR_TEX_GLASS )
+ return true;
+
+ // Skipping all studio models
+ if ( StringHasPrefix( surface.name, "**studio**" ) )
+ return true;
+
+ return false;
+}
+
+bool IsPassThroughMaterial( const csurface_t &surface )
+{
+ if ( surface.flags & SURF_SKY )
+ return true;
+
+ if ( IsMaterialInList( surface, g_ppszPortalPassThroughMaterials ) )
+ return true;
+
+ return false;
+}
+
+
+void TracePortals( const CProp_Portal *pIgnorePortal, const Vector &vForward, const Vector &vStart, const Vector &vEnd, trace_t &tr )
+{
+ UTIL_ClearTrace( tr );
+
+ Ray_t ray;
+ ray.Init( vStart, vEnd );
+
+ trace_t trTemp;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ UTIL_IntersectRayWithPortalOBBAsAABB( pTempPortal, ray, &trTemp );
+
+ if ( trTemp.fraction < 1.0f && trTemp.fraction < tr.fraction )
+ {
+ tr = trTemp;
+ }
+ }
+ }
+ }
+}
+
+bool TraceBumpingEntities( const Vector &vStart, const Vector &vEnd, trace_t &tr )
+{
+ UTIL_ClearTrace( tr );
+
+ // We use this so portal bumpers can't squeeze a portal into not fitting
+ bool bClosestIsSoftBumper = false;
+
+ // Trace to the surface to see if there's a rotating door in the way
+ CBaseEntity *list[1024];
+
+ Ray_t ray;
+ ray.Init( vStart, vEnd );
+
+ int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, 0 );
+
+ for ( int i = 0; i < nCount; i++ )
+ {
+ trace_t trTemp;
+ UTIL_ClearTrace( trTemp );
+
+ bool bSoftBumper = false;
+
+ if ( FClassnameIs( list[i], "func_portal_bumper" ) )
+ {
+ bSoftBumper = true;
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+ if ( trTemp.startsolid )
+ {
+ trTemp.fraction = 1.0f;
+ }
+ }
+ else if ( FClassnameIs( list[i], "trigger_portal_cleanser" ) )
+ {
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+ if ( trTemp.startsolid )
+ {
+ trTemp.fraction = 1.0f;
+ }
+ }
+ else if ( FClassnameIs( list[i], "func_noportal_volume" ) )
+ {
+ if ( static_cast<CFuncNoPortalVolume*>( list[i] )->IsActive() )
+ {
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+
+ // Bump by an extra 2 units so that the portal isn't touching the no portal volume
+ Vector vDelta = trTemp.endpos - trTemp.startpos;
+ float fLength = VectorNormalize( vDelta ) - 2.0f;
+ if ( fLength < 0.0f )
+ fLength = 0.0f;
+ trTemp.fraction = fLength / ray.m_Delta.Length();
+ trTemp.endpos = trTemp.startpos + vDelta * fLength;
+ }
+ else
+ trTemp.fraction = 1.0f;
+ }
+ else if( FClassnameIs( list[i], "prop_door_rotating" ) )
+ {
+ // Check more precise door collision
+ CBasePropDoor *pRotatingDoor = static_cast<CBasePropDoor *>( list[i] );
+
+ pRotatingDoor->TestCollision( ray, 0, trTemp );
+ }
+
+ // If this is the closest and has only bumped once (for soft bumpers)
+ if ( trTemp.fraction < tr.fraction && ( !bSoftBumper || !g_FuncBumpingEntityList.HasElement( list[i] ) ) )
+ {
+ tr = trTemp;
+ bClosestIsSoftBumper = bSoftBumper;
+ }
+ }
+
+ return bClosestIsSoftBumper;
+}
+
+bool TracePortalCorner( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const Vector &vCorner, const Vector &vForward, int iPlacedBy, ITraceFilter *pTraceFilterPortalShot, trace_t &tr, bool &bSoftBump )
+{
+ Vector vOriginToCorner = vCorner - vOrigin;
+
+ // Check for surface edge
+ trace_t trSurfaceEdge;
+ UTIL_TraceLine( vOrigin - vForward, vCorner - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ float fTotalFraction = trSurfaceEdge.fractionleftsolid;
+
+ while ( trSurfaceEdge.startsolid && trSurfaceEdge.fractionleftsolid > 0.0f && fTotalFraction < 1.0f )
+ {
+ UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vCorner + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ fTotalFraction += trSurfaceEdge.fractionleftsolid + 0.05f;
+ }
+ }
+
+ if ( fTotalFraction < 1.0f )
+ {
+ UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vOrigin - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+ else
+ {
+ trSurfaceEdge.fraction = fTotalFraction;
+ trSurfaceEdge.plane.normal = -trSurfaceEdge.plane.normal;
+ }
+ }
+ else
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+ }
+ else
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+
+ // Check for enclosing wall
+ trace_t trEnclosingWall;
+ UTIL_TraceLine( vOrigin + vForward, vCorner + vForward, MASK_SOLID_BRUSHONLY|CONTENTS_MONSTER, pTraceFilterPortalShot, &trEnclosingWall );
+
+ if ( trSurfaceEdge.fraction < trEnclosingWall.fraction )
+ {
+ trEnclosingWall.fraction = trSurfaceEdge.fraction;
+ trEnclosingWall.plane.normal = trSurfaceEdge.plane.normal;
+ }
+
+ trace_t trPortal;
+ trace_t trBumpingEntity;
+
+ if ( iPlacedBy != PORTAL_PLACED_BY_FIXED )
+ TracePortals( pIgnorePortal, vForward, vOrigin + vForward, vCorner + vForward, trPortal );
+ else
+ UTIL_ClearTrace( trPortal );
+
+ bool bSoftBumper = TraceBumpingEntities( vOrigin + vForward, vCorner + vForward, trBumpingEntity );
+
+ if ( trEnclosingWall.fraction >= 1.0f && trPortal.fraction >= 1.0f && trBumpingEntity.fraction >= 1.0f )
+ {
+ UTIL_ClearTrace( tr );
+ return false;
+ }
+
+ if ( trEnclosingWall.fraction <= trPortal.fraction && trEnclosingWall.fraction <= trBumpingEntity.fraction )
+ {
+ tr = trEnclosingWall;
+ bSoftBump = false;
+ }
+ else if ( trPortal.fraction <= trEnclosingWall.fraction && trPortal.fraction <= trBumpingEntity.fraction )
+ {
+ tr = trPortal;
+ g_bBumpedByLinkedPortal = true;
+ bSoftBump = false;
+ }
+ else if ( !trBumpingEntity.startsolid && trBumpingEntity.fraction <= trEnclosingWall.fraction && trBumpingEntity.fraction <= trPortal.fraction )
+ {
+ tr = trBumpingEntity;
+ bSoftBump = bSoftBumper;
+ }
+ else
+ {
+ UTIL_ClearTrace( tr );
+ return false;
+ }
+
+ return true;
+}
+
+Vector FindBumpVectorInCorner( const Vector &ptCorner1, const Vector &ptCorner2, const Vector &ptIntersectionPoint1, const Vector &ptIntersectionPoint2, const Vector &vIntersectionDirection1, const Vector &vIntersectionDirection2, const Vector &vIntersectionBumpDirection1, const Vector &vIntersectionBumpDirection2 )
+{
+ Vector ptClosestSegment1, ptClosestSegment2;
+ float fT1, fT2;
+
+ CalcLineToLineIntersectionSegment( ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1,
+ ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2,
+ &ptClosestSegment1, &ptClosestSegment2, &fT1, &fT2 );
+
+ Vector ptLineIntersection = ( ptClosestSegment1 + ptClosestSegment2 ) * 0.5f;
+
+ // The 2 corner trace intersections and the intersection of those lines makes a triangle.
+ // We want to make a similar triangle where the base is large enough to fit the edge of the portal
+
+ // Get the the small triangle's legs and leg lengths
+ Vector vShortLeg = ptIntersectionPoint1 - ptLineIntersection;
+ Vector vShortLeg2 = ptIntersectionPoint2 - ptLineIntersection;
+
+ float fShortLegLength = vShortLeg.Length();
+ float fShortLeg2Length = vShortLeg2.Length();
+
+ if ( fShortLegLength == 0.0f || fShortLeg2Length == 0.0f )
+ {
+ // FIXME: Our triangle is actually a point or a line, so there's nothing we can do
+ return vec3_origin;
+ }
+
+ // Normalized legs
+ vShortLeg /= fShortLegLength;
+ vShortLeg2 /= fShortLeg2Length;
+
+ // Check if corners are aligned with one of the legs
+ Vector vCornerToCornerNorm = ptCorner2 - ptCorner1;
+ VectorNormalize( vCornerToCornerNorm );
+
+ float fPortalEdgeDotLeg = vCornerToCornerNorm.Dot( vShortLeg );
+ float fPortalEdgeDotLeg2 = vCornerToCornerNorm.Dot( vShortLeg2 );
+
+ if ( fPortalEdgeDotLeg < -0.9999f || fPortalEdgeDotLeg > 0.9999f || fPortalEdgeDotLeg2 < -0.9999f || fPortalEdgeDotLeg2 > 0.9999f )
+ {
+ // Do a one corner bump with corner 1
+ float fBumpDistance1 = CalcDistanceToLine( ptCorner1, ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1 );
+
+ fBumpDistance1 += PORTAL_BUMP_FORGIVENESS;
+
+ // Do a one corner bump with corner 2
+ float fBumpDistance2 = CalcDistanceToLine( ptCorner2, ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2 );
+
+ fBumpDistance2 += PORTAL_BUMP_FORGIVENESS;
+
+ return vIntersectionBumpDirection1 * fBumpDistance1 + vIntersectionBumpDirection2 * fBumpDistance2;
+ }
+
+ float fLegsDot = vShortLeg.Dot( vShortLeg2 );
+
+ // Need to know if the triangle is pointing toward the portal or away from the portal
+ /*bool bPointingTowardPortal = true;
+
+ Vector vLineIntersectionToCornerNorm = ptCorner1 - ptLineIntersection;
+ VectorNormalize( vLineIntersectionToCornerNorm );
+
+ if ( vLineIntersectionToCornerNorm.Dot( vShortLeg2 ) < fLegsDot )
+ {
+ bPointingTowardPortal = false;
+ }
+
+ if ( !bPointingTowardPortal )*/
+ {
+ // Get the small triangle's base length
+ float fLongBaseLength = ptCorner1.DistTo( ptCorner2 );
+
+ // Get the large triangle's base length
+ float fShortLeg2Angle = acosf( vCornerToCornerNorm.Dot( -vShortLeg ) );
+ float fShortBaseAngle = acosf( fLegsDot );
+ float fShortLegAngle = M_PI_F - fShortBaseAngle - fShortLeg2Angle;
+
+ if ( sinf( fShortLegAngle ) == 0.0f )
+ {
+ return Vector( 1000.0f, 1000.0f, 1000.0f );
+ }
+
+ float fShortBaseLength = sinf( fShortBaseAngle ) * ( fShortLegLength / sinf( fShortLegAngle ) );
+
+ // Avoid divide by zero
+ if ( fShortBaseLength == 0.0f )
+ {
+ return Vector( 0.0f, 0.0f, 0.0f );
+ }
+
+ // Use ratio to get the big triangles leg length
+ float fLongLegLength = fLongBaseLength * ( fShortLegLength / fShortBaseLength );
+
+ // Get the relative point on the large triangle
+ Vector ptNewCornerPos = ptLineIntersection + vShortLeg * fLongLegLength;
+
+ // Bump by the same amount the corner has to move to fit
+ return ptNewCornerPos - ptCorner1;
+ }
+ /*else
+ {
+ return Vector( 0.0f, 0.0f, 0.0f );
+ }*/
+}
+
+
+bool FitPortalOnSurface( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight,
+ const Vector &vTopEdge, const Vector &vBottomEdge, const Vector &vRightEdge, const Vector &vLeftEdge,
+ int iPlacedBy, ITraceFilter *pTraceFilterPortalShot,
+ int iRecursions /*= 0*/, const CPortalCornerFitData *pPortalCornerFitData /*= 0*/, const int *p_piIntersectionIndex /*= 0*/, const int *piIntersectionCount /*= 0*/ )
+{
+ // Don't infinitely recurse
+ if ( iRecursions >= 6 )
+ {
+ return false;
+ }
+
+ Vector pptCorner[ 4 ];
+
+ // Get corner points
+ pptCorner[ 0 ] = vOrigin + vTopEdge + vLeftEdge;
+ pptCorner[ 1 ] = vOrigin + vTopEdge + vRightEdge;
+ pptCorner[ 2 ] = vOrigin + vBottomEdge + vLeftEdge;
+ pptCorner[ 3 ] = vOrigin + vBottomEdge + vRightEdge;
+
+ // Corner data
+ CPortalCornerFitData sFitData[ 4 ];
+ int piIntersectionIndex[ 4 ];
+ int iIntersectionCount = 0;
+
+ // Gather data we already know
+ if ( pPortalCornerFitData )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ sFitData[ iIntersection ] = pPortalCornerFitData[ iIntersection ];
+ }
+ }
+ else
+ {
+ memset( sFitData, 0, sizeof( sFitData ) );
+ }
+
+ if ( p_piIntersectionIndex )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ piIntersectionIndex[ iIntersection ] = p_piIntersectionIndex[ iIntersection ];
+ }
+ }
+ else
+ {
+ memset( piIntersectionIndex, 0, sizeof( piIntersectionIndex ) );
+ }
+
+ if ( piIntersectionCount )
+ {
+ iIntersectionCount = *piIntersectionCount;
+ }
+
+ int iOldIntersectionCount = iIntersectionCount;
+
+ // Find intersections from center to each corner
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ // HACK: In weird cases intersection count can go over 3 and index outside of our arrays. Don't let this happen!
+ if ( iIntersectionCount < 4 )
+ {
+ // Don't recompute intersection data that we already have
+ if ( !sFitData[ iIntersection ].bCornerIntersection )
+ {
+ // Test intersection of the current corner
+ sFitData[ iIntersection ].bCornerIntersection = TracePortalCorner( pIgnorePortal, vOrigin, pptCorner[ iIntersection ], vForward, iPlacedBy, pTraceFilterPortalShot, sFitData[ iIntersection ].trCornerTrace, sFitData[ iIntersection ].bSoftBump );
+
+ // If the intersection has no normal, ignore it
+ if ( sFitData[ iIntersection ].trCornerTrace.plane.normal.IsZero() )
+ sFitData[ iIntersection ].bCornerIntersection = false;
+
+ // If it intersected
+ if ( sFitData[ iIntersection ].bCornerIntersection )
+ {
+ sFitData[ iIntersection ].ptIntersectionPoint = vOrigin + ( pptCorner[ iIntersection ] - vOrigin ) * sFitData[ iIntersection ].trCornerTrace.fraction;
+ VectorNormalize( sFitData[ iIntersection ].trCornerTrace.plane.normal );
+ sFitData[ iIntersection ].vIntersectionDirection = sFitData[ iIntersection ].trCornerTrace.plane.normal.Cross( vForward );
+ VectorNormalize( sFitData[ iIntersection ].vIntersectionDirection );
+ sFitData[ iIntersection ].vBumpDirection = vForward.Cross( sFitData[ iIntersection ].vIntersectionDirection );
+ VectorNormalize( sFitData[ iIntersection ].vBumpDirection );
+
+ piIntersectionIndex[ iIntersectionCount ] = iIntersection;
+
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ NDebugOverlay::Line( sFitData[ iIntersection ].ptIntersectionPoint - sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
+ sFitData[ iIntersection ].ptIntersectionPoint + sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
+ 0, 0, 255, true, 0.5f );
+ }
+ }
+
+ ++iIntersectionCount;
+ }
+ }
+ else
+ {
+ // We shouldn't be intersecting with any old corners
+ sFitData[ iIntersection ].trCornerTrace.fraction = 1.0f;
+ }
+ }
+ }
+
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ // Remember soft bumpers so we don't bump with it twice
+ if ( sFitData[ iIntersection ].bSoftBump )
+ {
+ g_FuncBumpingEntityList.AddToTail( sFitData[ iIntersection ].trCornerTrace.m_pEnt );
+ }
+ }
+
+ // If no new intersections were found then it already fits
+ if ( iOldIntersectionCount == iIntersectionCount )
+ {
+ return true;
+ }
+
+ switch ( iIntersectionCount )
+ {
+ case 0:
+ {
+ // If no corners intersect it already fits
+ return true;
+ }
+ break;
+
+ case 1:
+ {
+ float fBumpDistance = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ 0 ] ],
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection );
+
+ fBumpDistance += PORTAL_BUMP_FORGIVENESS;
+
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection * fBumpDistance;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ case 2:
+ {
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint == sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint )
+ {
+ return false;
+ }
+
+ float fDot = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+
+ // If there are parallel intersections try scooting it away from a near wall
+ if ( fDot < -0.9f )
+ {
+ // Check if perpendicular wall is near
+ trace_t trPerpWall1;
+ bool bSoftBump1;
+ bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
+
+ trace_t trPerpWall2;
+ bool bSoftBump2;
+ bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
+
+ // No fit if there's blocking walls on both sides it can't fit
+ if ( bDir1 && bDir2 )
+ {
+ if ( bSoftBump1 )
+ bDir1 = false;
+ else if ( bSoftBump2 )
+ bDir1 = true;
+ else
+ return false;
+ }
+
+ // If there's no assumption to make, just pick a direction.
+ if ( !bDir1 && !bDir2 )
+ {
+ bDir1 = true;
+ }
+
+ // Bump the portal
+ if ( bDir1 )
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
+ }
+ else
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
+ }
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+
+ // If they are the same there's an easy way
+ if ( fDot > 0.9f )
+ {
+ // Get the closest intersection to the portal's center
+ int iClosestIntersection = ( ( vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint ) < vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint ) ) ? ( 0 ) : ( 1 ) );
+
+ // Find the largest amount that the portal needs to bump for the corner to pass the intersection
+ float pfBumpDistance[ 2 ];
+
+ for ( int iIntersection = 0; iIntersection < 2; ++iIntersection )
+ {
+ pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
+
+ pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
+ }
+
+ int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
+
+ // Bump the portal
+ vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
+
+ // If they were parallel to the intersection line don't invalidate both before recursion
+ if ( pfBumpDistance[ 0 ] == pfBumpDistance[ 1 ] )
+ {
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ iIntersectionCount = 0;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ // Prepare data for recursion
+ if ( iLargestBump != iClosestIntersection )
+ {
+ sFitData[ piIntersectionIndex[ iLargestBump ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
+ }
+ sFitData[ piIntersectionIndex[ ( ( iLargestBump == 0 ) ? ( 1 ) : ( 0 ) ) ] ].bCornerIntersection = false;
+ piIntersectionIndex[ 0 ] = piIntersectionIndex[ iLargestBump ];
+ iIntersectionCount = 1;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ }
+
+ // Intersections are angled, bump based on math using the corner
+ vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ 0 ] ], pptCorner[ piIntersectionIndex[ 1 ] ],
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ 1 ] ].vIntersectionDirection,
+ sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ case 3:
+ {
+ // Get the relationships of the intersections
+ float fDot[ 3 ];
+ fDot[ 0 ] = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+ fDot[ 1 ] = sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection );
+ fDot[ 2 ] = sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection );
+
+ int iSimilarWalls = 0;
+
+ for ( int iDot = 0; iDot < 3; ++iDot )
+ {
+ // If there are parallel intersections try scooting it away from a near wall
+ if ( fDot[ iDot ] < -0.99f )
+ {
+ // Check if perpendicular wall is near
+ trace_t trPerpWall1;
+ bool bSoftBump1;
+ bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
+
+ trace_t trPerpWall2;
+ bool bSoftBump2;
+ bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
+
+ // No fit if there's blocking walls on both sides it can't fit
+ if ( bDir1 && bDir2 )
+ {
+ if ( bSoftBump1 )
+ bDir1 = false;
+ else if ( bSoftBump2 )
+ bDir1 = true;
+ else
+ return false;
+ }
+
+ // If there's no assumption to make, just pick a direction.
+ if ( !bDir1 && !bDir2 )
+ {
+ bDir1 = true;
+ }
+
+ // Bump the portal
+ if ( bDir1 )
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
+ }
+ else
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
+ }
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ // Count similar intersections
+ else if ( fDot[ iDot ] > 0.99f )
+ {
+ ++iSimilarWalls;
+ }
+ }
+
+ // If no intersections are similar
+ if ( iSimilarWalls == 0 )
+ {
+ // Total the angles between the intersections
+ float fAngleTotal = 0.0f;
+ for ( int iDot = 0; iDot < 3; ++iDot )
+ {
+ fAngleTotal += acosf( fDot[ iDot ] );
+ }
+
+ // If it's in a triangle, it can't be fit
+ if ( M_PI_F - 0.01f < fAngleTotal && fAngleTotal < M_PI_F + 0.01f )
+ {
+ // If any of the bumps are soft, give it another try
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump )
+ {
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ // If the intersections are all similar there's an easy way
+ if ( iSimilarWalls == 3 )
+ {
+ // Get the closest intersection to the portal's center
+ int iClosestIntersection = 0;
+ float fClosestDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint );
+
+ float fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint );
+ if ( fClosestDistance > fDistance )
+ {
+ iClosestIntersection = 1;
+ fClosestDistance = fDistance;
+ }
+
+ fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 2 ] ].ptIntersectionPoint );
+ if ( fClosestDistance > fDistance )
+ {
+ iClosestIntersection = 2;
+ fClosestDistance = fDistance;
+ }
+
+ // Find the largest amount that the portal needs to bump for the corner to pass the intersection
+ float pfBumpDistance[ 3 ];
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
+ pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
+ }
+
+ int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
+
+ iLargestBump = ( ( pfBumpDistance[ iLargestBump ] > pfBumpDistance[ 2 ] ) ? ( iLargestBump ) : ( 2 ) );
+
+ // Bump the portal
+ vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
+
+ // Prepare data for recursion
+ int iStillIntersecting = 0;
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ // Invalidate corners that were closer to the intersection line
+ if ( pfBumpDistance[ iIntersection ] != pfBumpDistance[ iLargestBump ] )
+ {
+ sFitData[ piIntersectionIndex[ iIntersection ] ].bCornerIntersection = false;
+ --iIntersectionCount;
+ }
+ else
+ {
+ sFitData[ piIntersectionIndex[ iIntersection ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
+ piIntersectionIndex[ iStillIntersecting ] = piIntersectionIndex[ iIntersection ];
+ ++iStillIntersecting;
+ }
+ }
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+
+ // Get info for which corners are diagonal from each other
+ float fLongestDist = 0.0f;
+ int iLongestDist = 0;
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ float fDist = pptCorner[ piIntersectionIndex[ iIntersection ] ].DistTo( pptCorner[ piIntersectionIndex[ ( iIntersection + 1 ) % 3 ] ] );
+
+ if ( fLongestDist < fDist )
+ {
+ fLongestDist = fDist;
+ iLongestDist = iIntersection;
+ }
+ }
+
+ int iIndex1, iIndex2, iIndex3;
+
+ switch ( iLongestDist )
+ {
+ case 0:
+ iIndex1 = 0;
+ iIndex2 = 1;
+ iIndex3 = 2;
+ break;
+
+ case 1:
+ iIndex1 = 1;
+ iIndex2 = 2;
+ iIndex3 = 0;
+ break;
+
+ default:
+ iIndex1 = 2;
+ iIndex2 = 0;
+ iIndex3 = 1;
+ break;
+ }
+
+ // If corner is 90 degrees there my be an easy way
+ float fCornerDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
+
+ if ( fCornerDot < 0.0001f && fCornerDot > -0.0001f )
+ {
+ // Check if portal is aligned perfectly with intersection normals
+ float fPortalDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( vRight );
+
+ if ( ( fPortalDot < 0.0001f && fPortalDot > -0.0001f ) || fPortalDot > 0.9999f || fPortalDot < -0.9999f )
+ {
+ float fBump1 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex1 ] ],
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection );
+
+ fBump1 += PORTAL_BUMP_FORGIVENESS;
+
+ float fBump2 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex2 ] ],
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
+
+ fBump2 += PORTAL_BUMP_FORGIVENESS;
+
+ // Bump portal
+ vOrigin += sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection * fBump1;
+ vOrigin += sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection * fBump2;
+
+ // Prepare recursion data
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ }
+
+ vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ iIndex1 ] ], pptCorner[ piIntersectionIndex[ iIndex2 ] ],
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection );
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ default:
+ {
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 3 ] ].bSoftBump )
+ {
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ // All corners intersect with no soft bumps, so it can't be fit
+ return false;
+ }
+ }
+ break;
+ }
+
+ return true;
+}
+
+void FitPortalAroundOtherPortals( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp )
+{
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ Vector vDiff = vOrigin - pTempPortal->GetLocalOrigin();
+
+ Vector vDiffProjRight = vDiff.Dot( vRight ) * vRight;
+ Vector vDiffProjUp = vDiff.Dot( vUp ) * vUp;
+
+ float fProjRightLength = VectorNormalize( vDiffProjRight );
+ float fProjUpLength = VectorNormalize( vDiffProjUp );
+
+ if ( fProjRightLength < 1.0f )
+ {
+ vDiffProjRight = vRight;
+ }
+
+ if ( fProjUpLength < PORTAL_HALF_HEIGHT && fProjRightLength < PORTAL_HALF_WIDTH )
+ {
+ vOrigin += vDiffProjRight * ( PORTAL_HALF_WIDTH - fProjRightLength + 1.0f );
+ }
+ }
+ }
+ }
+}
+
+bool IsPortalIntersectingNoPortalVolume( const Vector &vOrigin, const QAngle &qAngles, const Vector &vForward )
+{
+ // Walk the no portal volume list, check each with box-box intersection
+ for ( CFuncNoPortalVolume *pNoPortalEnt = GetNoPortalVolumeList(); pNoPortalEnt != NULL; pNoPortalEnt = pNoPortalEnt->m_pNext )
+ {
+ // Skip inactive no portal zones
+ if ( !pNoPortalEnt->IsActive() )
+ {
+ continue;
+ }
+
+ Vector vMin;
+ Vector vMax;
+ pNoPortalEnt->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
+
+ Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
+ Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
+
+ // Take bump forgiveness into account on non major axies
+ vBoxExtents += Vector( ( ( vForward.x > 0.5f || vForward.x < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
+ ( ( vForward.y > 0.5f || vForward.y < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
+ ( ( vForward.z > 0.5f || vForward.z < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ) );
+
+ if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, vOrigin, qAngles ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ NDebugOverlay::Box( Vector( 0.0f, 0.0f, 0.0f ), vMin, vMax, 0, 255, 0, 128, 0.5f );
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+
+ DevMsg( "Portal placed in no portal volume.\n" );
+ }
+
+ return true;
+ }
+ }
+
+ // Passed the list, so we didn't hit any func_noportal_volumes
+ return false;
+}
+
+bool IsPortalOverlappingOtherPortals( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const QAngle &qAngles, bool bFizzle /*= false*/ )
+{
+ bool bOverlappedOtherPortal = false;
+
+ Vector vForward;
+ AngleVectors( qAngles, &vForward, NULL, NULL );
+
+ Vector vPortalOBBMin = CProp_Portal_Shared::vLocalMins + Vector( 1.0f, 1.0f, 1.0f );
+ Vector vPortalOBBMax = CProp_Portal_Shared::vLocalMaxs - Vector( 1.0f, 1.0f, 1.0f );
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ if ( IsOBBIntersectingOBB( vOrigin, qAngles, vPortalOBBMin, vPortalOBBMax,
+ vOtherOrigin, qOtherAngles, vPortalOBBMin, vPortalOBBMax, 0.0f ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ UTIL_Portal_NDebugOverlay( pTempPortal, 255, 0, 0, 128, false, 0.5f );
+
+ DevMsg( "Portal overlapped another portal.\n" );
+ }
+
+ if ( bFizzle )
+ {
+ pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pTempPortal->Fizzle();
+ bOverlappedOtherPortal = true;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return bOverlappedOtherPortal;
+}
+
+bool IsPortalOnValidSurface( const Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp, ITraceFilter *traceFilterPortalShot )
+{
+ trace_t tr;
+
+ // Check if corners are on a no portal material
+ for ( int iCorner = 0; iCorner < 5; ++iCorner )
+ {
+ Vector ptCorner = vOrigin;
+
+ if ( iCorner < 4 )
+ {
+ if ( iCorner / 2 == 0 )
+ ptCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //top
+ else
+ ptCorner += vUp * -( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //bottom
+
+ if ( iCorner % 2 == 0 )
+ ptCorner += vRight * -( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //left
+ else
+ ptCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //right
+ }
+
+ Ray_t ray;
+ ray.Init( ptCorner + vForward, ptCorner - vForward );
+ enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, traceFilterPortalShot, &tr );
+
+ if ( tr.startsolid )
+ {
+ // Portal center/corner in solid
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal center or corner placed inside solid.\n" );
+ }
+
+ return false;
+ }
+
+ if ( tr.fraction == 1.0f )
+ {
+ // Check if there's a portal bumper to act as a surface
+ TraceBumpingEntities( ptCorner + vForward, ptCorner - vForward, tr );
+
+ if ( tr.fraction == 1.0f )
+ {
+ // No surface behind the portal
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal corner has no surface behind it.\n" );
+ }
+
+ return false;
+ }
+ }
+
+ if ( tr.m_pEnt && FClassnameIs( tr.m_pEnt, "func_door" ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on func_door.\n" );
+ }
+
+ return false;
+ }
+
+ if ( IsPassThroughMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on a pass through material.\n" );
+ }
+
+ return false;
+ }
+
+ if ( IsNoPortalMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on a no portal material.\n" );
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+float VerifyPortalPlacement( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, int iPlacedBy, bool bTest /*= false*/ )
+{
+ Vector vOriginalOrigin = vOrigin;
+
+ Vector vForward, vRight, vUp;
+ AngleVectors( qAngles, &vForward, &vRight, &vUp );
+
+ VectorNormalize( vForward );
+ VectorNormalize( vRight );
+ VectorNormalize( vUp );
+
+ trace_t tr;
+ CTraceFilterSimpleClassnameList baseFilter( pIgnorePortal, COLLISION_GROUP_NONE );
+ UTIL_Portal_Trace_Filter( &baseFilter );
+ baseFilter.AddClassnameToIgnore( "prop_portal" );
+ CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
+
+ // Check if center is on a surface
+ Ray_t ray;
+ ray.Init( vOrigin + vForward, vOrigin - vForward );
+ enginetrace->TraceRay( ray, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ if ( tr.fraction == 1.0f )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal center has no surface behind it.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Check if the surface is moving
+ Vector vVelocityCheck;
+ AngularImpulse vAngularImpulseCheck;
+
+ IPhysicsObject *pPhysicsObject = tr.m_pEnt->VPhysicsGetObject();
+
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
+ }
+ else
+ {
+ tr.m_pEnt->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
+ }
+
+ if ( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal was on moving surface.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Check for invalid materials
+ if ( IsPassThroughMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal placed on a pass through material.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE;
+ }
+
+ if ( IsNoPortalMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal placed on a no portal material.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Get pointer to liked portal if it might be in the way
+ g_bBumpedByLinkedPortal = false;
+
+ if ( iPlacedBy == PORTAL_PLACED_BY_PLAYER && !sv_portal_placement_never_bump.GetBool() )
+ {
+ // Bump away from linked portal so it can be fit next to it
+ FitPortalAroundOtherPortals( pIgnorePortal, vOrigin, vForward, vRight, vUp );
+ }
+
+ float fBumpDistance = 0.0f;
+
+ if ( !sv_portal_placement_never_bump.GetBool() )
+ {
+ // Fit onto surface and auto bump
+ g_FuncBumpingEntityList.RemoveAll();
+
+ Vector vTopEdge = vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS );
+ Vector vBottomEdge = -vTopEdge;
+ Vector vRightEdge = vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS );
+ Vector vLeftEdge = -vRightEdge;
+
+ if ( !FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, &traceFilterPortalShot ) )
+ {
+ if ( g_bBumpedByLinkedPortal )
+ {
+ return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
+ }
+
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal was unable to fit on surface.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CANT_FIT;
+ }
+
+ // Check if it's moved too far from it's original location
+ fBumpDistance = vOrigin.DistToSqr( vOriginalOrigin );
+
+ if ( fBumpDistance > MAXIMUM_BUMP_DISTANCE )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal adjusted too far from it's original location.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CANT_FIT;
+ }
+
+ //if we're less than a unit from floor, we're going to bump to match it exactly and help game movement code run smoothly
+ if( vUp.z > 0.7f )
+ {
+ Vector vSmallForward = vForward * 0.05f;
+ trace_t FloorTrace;
+ UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT + 1.5f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace );
+ if( FloorTrace.fraction < 1.0f )
+ {
+ //we hit floor in that 1 extra unit, now doublecheck to make sure we didn't hit something else
+ trace_t FloorTrace_Verify;
+ UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT - 0.1f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace_Verify );
+ if( FloorTrace_Verify.fraction == 1.0f )
+ {
+ //if we're in here, we're definitely in a floor matching configuration, bump down to match the floor better
+ vOrigin = FloorTrace.endpos + (vUp * PORTAL_HALF_HEIGHT) - vSmallForward;// - vUp * PORTAL_WALL_MIN_THICKNESS;
+ }
+ }
+ }
+ }
+
+ // Fail if it's in a no portal volume
+ if ( IsPortalIntersectingNoPortalVolume( vOrigin, qAngles, vForward ) )
+ {
+ return PORTAL_ANALOG_SUCCESS_INVALID_VOLUME;
+ }
+
+ // Fail if it's overlapping the linked portal
+ if ( bTest && IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles ) )
+ {
+ return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
+ }
+
+ // Fail if it's on a flagged surface material
+ if ( !IsPortalOnValidSurface( vOrigin, vForward, vRight, vUp, &traceFilterPortalShot ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ }
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ float fAnalogSuccessMultiplier = 1.0f - ( fBumpDistance / MAXIMUM_BUMP_DISTANCE );
+ fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
+ fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
+
+ return fAnalogSuccessMultiplier * ( PORTAL_ANALOG_SUCCESS_NO_BUMP - PORTAL_ANALOG_SUCCESS_BUMPED ) + PORTAL_ANALOG_SUCCESS_BUMPED;
+}
diff --git a/game/server/portal/portal_placement.h b/game/server/portal/portal_placement.h
new file mode 100644
index 0000000..22f3e4c
--- /dev/null
+++ b/game/server/portal/portal_placement.h
@@ -0,0 +1,25 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef PORTAL_PLACEMENT_H
+#define PORTAL_PLACEMENT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+struct CPortalCornerFitData;
+
+bool FitPortalOnSurface( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight,
+ const Vector &vTopEdge, const Vector &vBottomEdge, const Vector &vRightEdge, const Vector &vLeftEdge,
+ int iPlacedBy, ITraceFilter *pTraceFilterPortalShot,
+ int iRecursions = 0, const CPortalCornerFitData *pPortalCornerFitData = 0, const int *p_piIntersectionIndex = 0, const int *piIntersectionCount = 0 );
+bool IsPortalIntersectingNoPortalVolume( const Vector &vOrigin, const QAngle &qAngles, const Vector &vForward );
+bool IsPortalOverlappingOtherPortals( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const QAngle &qAngles, bool bFizzle = false );
+float VerifyPortalPlacement( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, int iPlacedBy, bool bTest = false );
+
+
+#endif // PORTAL_PLACEMENT_H
diff --git a/game/server/portal/portal_player.cpp b/game/server/portal/portal_player.cpp
new file mode 100644
index 0000000..72cc063
--- /dev/null
+++ b/game/server/portal/portal_player.cpp
@@ -0,0 +1,2333 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for Portal.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "portal_player.h"
+#include "globalstate.h"
+#include "trains.h"
+#include "game.h"
+#include "portal_player_shared.h"
+#include "predicted_viewmodel.h"
+#include "in_buttons.h"
+#include "portal_gamerules.h"
+#include "weapon_portalgun.h"
+#include "portal/weapon_physcannon.h"
+#include "KeyValues.h"
+#include "team.h"
+#include "eventqueue.h"
+#include "weapon_portalbase.h"
+#include "engine/IEngineSound.h"
+#include "ai_basenpc.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "prop_portal_shared.h"
+#include "player_pickup.h" // for player pickup code
+#include "vphysics/player_controller.h"
+#include "datacache/imdlcache.h"
+#include "bone_setup.h"
+#include "portal_gamestats.h"
+#include "physicsshadowclone.h"
+#include "physics_prop_ragdoll.h"
+#include "soundenvelope.h"
+#include "ai_speech.h" // For expressors, vcd playing
+#include "sceneentity.h" // has the VCD precache function
+
+// Max mass the player can lift with +use
+#define PORTAL_PLAYER_MAX_LIFT_MASS 85
+#define PORTAL_PLAYER_MAX_LIFT_SIZE 128
+
+extern CBaseEntity *g_pLastSpawn;
+
+extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse);
+
+
+// -------------------------------------------------------------------------------- //
+// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
+// -------------------------------------------------------------------------------- //
+
+class CTEPlayerAnimEvent : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
+ {
+ }
+
+ CNetworkHandle( CBasePlayer, m_hPlayer );
+ CNetworkVar( int, m_iEvent );
+ CNetworkVar( int, m_nData );
+};
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
+SendPropEHandle( SENDINFO( m_hPlayer ) ),
+SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
+SendPropInt( SENDINFO( m_nData ), 32 ),
+END_SEND_TABLE()
+
+static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
+
+void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
+{
+ CPVSFilter filter( (const Vector&)pPlayer->EyePosition() );
+
+ g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
+ g_TEPlayerAnimEvent.m_iEvent = event;
+ g_TEPlayerAnimEvent.m_nData = nData;
+ g_TEPlayerAnimEvent.Create( filter, 0 );
+}
+
+
+
+//=================================================================================
+//
+// Ragdoll Entity
+//
+class CPortalRagdoll : public CBaseAnimatingOverlay, public CDefaultPlayerPickupVPhysics
+{
+public:
+
+ DECLARE_CLASS( CPortalRagdoll, CBaseAnimatingOverlay );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CPortalRagdoll()
+ {
+ m_hPlayer.Set( NULL );
+ m_vecRagdollOrigin.Init();
+ m_vecRagdollVelocity.Init();
+ }
+
+ // Transmit ragdolls to everyone.
+ virtual int UpdateTransmitState()
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+ // In case the client has the player entity, we transmit the player index.
+ // In case the client doesn't have it, we transmit the player's model index, origin, and angles
+ // so they can create a ragdoll in the right place.
+ CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
+ CNetworkVector( m_vecRagdollVelocity );
+ CNetworkVector( m_vecRagdollOrigin );
+};
+
+LINK_ENTITY_TO_CLASS( portal_ragdoll, CPortalRagdoll );
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CPortalRagdoll, DT_PortalRagdoll )
+SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ),
+SendPropEHandle( SENDINFO( m_hPlayer ) ),
+SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
+SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
+SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
+SendPropVector( SENDINFO( m_vecRagdollVelocity ) ),
+END_SEND_TABLE()
+
+
+BEGIN_DATADESC( CPortalRagdoll )
+
+ DEFINE_FIELD( m_vecRagdollOrigin, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecRagdollVelocity, FIELD_VECTOR ),
+
+END_DATADESC()
+
+
+
+
+
+LINK_ENTITY_TO_CLASS( player, CPortal_Player );
+
+IMPLEMENT_SERVERCLASS_ST(CPortal_Player, DT_Portal_Player)
+SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
+SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
+SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
+SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
+SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
+SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
+SendPropExclude( "DT_BaseFlex", "m_viewtarget" ),
+SendPropExclude( "DT_BaseFlex", "m_flexWeight" ),
+SendPropExclude( "DT_BaseFlex", "m_blinktoggle" ),
+
+// portal_playeranimstate and clientside animation takes care of these on the client
+SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
+SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
+
+
+SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ),
+SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ),
+SendPropEHandle( SENDINFO( m_hRagdoll ) ),
+SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ),
+SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ),
+SendPropBool( SENDINFO( m_bHeldObjectOnOppositeSideOfPortal) ),
+SendPropEHandle( SENDINFO( m_pHeldObjectPortal ) ),
+SendPropBool( SENDINFO( m_bPitchReorientation ) ),
+SendPropEHandle( SENDINFO( m_hPortalEnvironment ) ),
+SendPropEHandle( SENDINFO( m_hSurroundingLiquidPortal ) ),
+SendPropBool( SENDINFO( m_bSuppressingCrosshair ) ),
+SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
+
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CPortal_Player )
+
+ DEFINE_SOUNDPATCH( m_pWooshSound ),
+
+ DEFINE_FIELD( m_bHeldObjectOnOppositeSideOfPortal, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_pHeldObjectPortal, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bIntersectingPortalPlane, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bStuckOnPortalCollisionObject, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fTimeLastHurt, FIELD_TIME ),
+ DEFINE_FIELD( m_StatsThisLevel.iNumPortalsPlaced, FIELD_INTEGER ),
+ DEFINE_FIELD( m_StatsThisLevel.iNumStepsTaken, FIELD_INTEGER ),
+ DEFINE_FIELD( m_StatsThisLevel.fNumSecondsTaken, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fTimeLastNumSecondsUpdate, FIELD_TIME ),
+ DEFINE_FIELD( m_iNumCamerasDetatched, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bPitchReorientation, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIsRegenerating, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fNeuroToxinDamageTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hPortalEnvironment, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flExpressionLoopTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iszExpressionScene, FIELD_STRING ),
+ DEFINE_FIELD( m_hExpressionSceneEnt, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecTotalBulletForce, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bSilentDropAndPickup, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_angEyeAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iPlayerSoundType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_qPrePortalledViewAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bFixEyeAnglesFromPortalling, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_matLastPortalled, FIELD_VMATRIX_WORLDSPACE ),
+ DEFINE_FIELD( m_vWorldSpaceCenterHolder, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hSurroundingLiquidPortal, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bSuppressingCrosshair, FIELD_BOOLEAN ),
+ //DEFINE_FIELD ( m_PlayerAnimState, CPortalPlayerAnimState ),
+ //DEFINE_FIELD ( m_StatsThisLevel, PortalPlayerStatistics_t ),
+
+ DEFINE_EMBEDDEDBYREF( m_pExpresser ),
+
+END_DATADESC()
+
+ConVar sv_regeneration_wait_time ("sv_regeneration_wait_time", "1.0", FCVAR_REPLICATED );
+
+const char *g_pszChellModel = "models/player/chell.mdl";
+const char *g_pszPlayerModel = g_pszChellModel;
+
+
+#define MAX_COMBINE_MODELS 4
+#define MODEL_CHANGE_INTERVAL 5.0f
+#define TEAM_CHANGE_INTERVAL 5.0f
+
+#define PORTALPLAYER_PHYSDAMAGE_SCALE 4.0f
+
+extern ConVar sv_turbophysics;
+
+//----------------------------------------------------
+// Player Physics Shadow
+//----------------------------------------------------
+#define VPHYS_MAX_DISTANCE 2.0
+#define VPHYS_MAX_VEL 10
+#define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE)
+#define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL)
+
+
+extern float IntervalDistance( float x, float x0, float x1 );
+
+//disable 'this' : used in base member initializer list
+#pragma warning( disable : 4355 )
+
+CPortal_Player::CPortal_Player()
+{
+
+ m_PlayerAnimState = CreatePortalPlayerAnimState( this );
+ CreateExpresser();
+
+ UseClientSideAnimation();
+
+ m_angEyeAngles.Init();
+
+ m_iLastWeaponFireUsercmd = 0;
+
+ m_iSpawnInterpCounter = 0;
+
+ m_bHeldObjectOnOppositeSideOfPortal = false;
+ m_pHeldObjectPortal = 0;
+
+ m_bIntersectingPortalPlane = false;
+
+ m_bPitchReorientation = false;
+
+ m_bSilentDropAndPickup = false;
+
+ m_iszExpressionScene = NULL_STRING;
+ m_hExpressionSceneEnt = NULL;
+ m_flExpressionLoopTime = 0.0f;
+ m_bSuppressingCrosshair = false;
+}
+
+CPortal_Player::~CPortal_Player( void )
+{
+ ClearSceneEvents( NULL, true );
+
+ if ( m_PlayerAnimState )
+ m_PlayerAnimState->Release();
+
+ CPortalRagdoll *pRagdoll = dynamic_cast<CPortalRagdoll*>( m_hRagdoll.Get() );
+ if( pRagdoll )
+ {
+ UTIL_Remove( pRagdoll );
+ }
+}
+
+void CPortal_Player::UpdateOnRemove( void )
+{
+ BaseClass::UpdateOnRemove();
+}
+
+void CPortal_Player::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "PortalPlayer.EnterPortal" );
+ PrecacheScriptSound( "PortalPlayer.ExitPortal" );
+
+ PrecacheScriptSound( "PortalPlayer.Woosh" );
+ PrecacheScriptSound( "PortalPlayer.FallRecover" );
+
+ PrecacheModel ( "sprites/glow01.vmt" );
+
+ //Precache Citizen models
+ PrecacheModel( g_pszPlayerModel );
+ PrecacheModel( g_pszChellModel );
+
+ PrecacheScriptSound( "NPC_Citizen.die" );
+}
+
+void CPortal_Player::CreateSounds()
+{
+ if ( !m_pWooshSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pWooshSound = controller.SoundCreate( filter, entindex(), "PortalPlayer.Woosh" );
+ controller.Play( m_pWooshSound, 0, 100 );
+ }
+}
+
+void CPortal_Player::StopLoopingSounds()
+{
+ if ( m_pWooshSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pWooshSound );
+ m_pWooshSound = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+void CPortal_Player::GiveAllItems( void )
+{
+ EquipSuit();
+
+ CBasePlayer::GiveAmmo( 255, "Pistol");
+ CBasePlayer::GiveAmmo( 32, "357" );
+
+ CBasePlayer::GiveAmmo( 255, "AR2" );
+ CBasePlayer::GiveAmmo( 3, "AR2AltFire" );
+ CBasePlayer::GiveAmmo( 255, "SMG1");
+ CBasePlayer::GiveAmmo( 3, "smg1_grenade");
+
+ CBasePlayer::GiveAmmo( 255, "Buckshot");
+ CBasePlayer::GiveAmmo( 16, "XBowBolt" );
+
+ CBasePlayer::GiveAmmo( 3, "rpg_round");
+ CBasePlayer::GiveAmmo( 6, "grenade" );
+
+ GiveNamedItem( "weapon_crowbar" );
+ GiveNamedItem( "weapon_physcannon" );
+
+ GiveNamedItem( "weapon_pistol" );
+ GiveNamedItem( "weapon_357" );
+
+ GiveNamedItem( "weapon_smg1" );
+ GiveNamedItem( "weapon_ar2" );
+
+ GiveNamedItem( "weapon_shotgun" );
+ GiveNamedItem( "weapon_crossbow" );
+
+ GiveNamedItem( "weapon_rpg" );
+ GiveNamedItem( "weapon_frag" );
+
+ GiveNamedItem( "weapon_bugbait" );
+
+ //GiveNamedItem( "weapon_physcannon" );
+ CWeaponPortalgun *pPortalGun = static_cast<CWeaponPortalgun*>( GiveNamedItem( "weapon_portalgun" ) );
+
+ if ( !pPortalGun )
+ {
+ pPortalGun = static_cast<CWeaponPortalgun*>( Weapon_OwnsThisType( "weapon_portalgun" ) );
+ }
+
+ if ( pPortalGun )
+ {
+ pPortalGun->SetCanFirePortal1();
+ pPortalGun->SetCanFirePortal2();
+ }
+}
+
+void CPortal_Player::GiveDefaultItems( void )
+{
+ castable_string_t st( "suit_no_sprint" );
+ GlobalEntity_SetState( st, GLOBAL_OFF );
+ inputdata_t in;
+ InputDisableFlashlight( in );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets specific defaults.
+//-----------------------------------------------------------------------------
+void CPortal_Player::Spawn(void)
+{
+ SetPlayerModel();
+
+ BaseClass::Spawn();
+
+ CreateSounds();
+
+ pl.deadflag = false;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ RemoveEffects( EF_NODRAW );
+ StopObserverMode();
+
+ GiveDefaultItems();
+
+ m_nRenderFX = kRenderNormal;
+
+ m_Local.m_iHideHUD = 0;
+
+ AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round.
+
+ m_impactEnergyScale = PORTALPLAYER_PHYSDAMAGE_SCALE;
+
+ RemoveFlag( FL_FROZEN );
+
+ m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8;
+
+ m_Local.m_bDucked = false;
+
+ SetPlayerUnderwater(false);
+
+#ifdef PORTAL_MP
+ PickTeam();
+#endif
+}
+
+void CPortal_Player::Activate( void )
+{
+ BaseClass::Activate();
+ m_fTimeLastNumSecondsUpdate = gpGlobals->curtime;
+}
+
+void CPortal_Player::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // On teleport, we send event for tracking fling achievements
+ if ( eventType == NOTIFY_EVENT_TELEPORT )
+ {
+ CProp_Portal *pEnteredPortal = dynamic_cast<CProp_Portal*>( pNotify );
+ IGameEvent *event = gameeventmanager->CreateEvent( "portal_player_portaled" );
+ if ( event )
+ {
+ event->SetInt( "userid", GetUserID() );
+ event->SetBool( "portal2", pEnteredPortal->m_bIsPortal2 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ BaseClass::NotifySystemEvent( pNotify, eventType, params );
+}
+
+void CPortal_Player::OnRestore( void )
+{
+ BaseClass::OnRestore();
+ if ( m_pExpresser )
+ {
+ m_pExpresser->SetOuter ( this );
+ }
+}
+
+//bool CPortal_Player::StartObserverMode( int mode )
+//{
+// //Do nothing.
+//
+// return false;
+//}
+
+bool CPortal_Player::ValidatePlayerModel( const char *pModel )
+{
+ if ( !Q_stricmp( g_pszPlayerModel, pModel ) )
+ {
+ return true;
+ }
+
+ if ( !Q_stricmp( g_pszChellModel, pModel ) )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void CPortal_Player::SetPlayerModel( void )
+{
+ const char *szModelName = NULL;
+ const char *pszCurrentModelName = modelinfo->GetModelName( GetModel());
+
+ szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" );
+
+ if ( ValidatePlayerModel( szModelName ) == false )
+ {
+ char szReturnString[512];
+
+ if ( ValidatePlayerModel( pszCurrentModelName ) == false )
+ {
+ pszCurrentModelName = g_pszPlayerModel;
+ }
+
+ Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName );
+ engine->ClientCommand ( edict(), szReturnString );
+
+ szModelName = pszCurrentModelName;
+ }
+
+ int modelIndex = modelinfo->GetModelIndex( szModelName );
+
+ if ( modelIndex == -1 )
+ {
+ szModelName = g_pszPlayerModel;
+
+ char szReturnString[512];
+
+ Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName );
+ engine->ClientCommand ( edict(), szReturnString );
+ }
+
+ SetModel( szModelName );
+ m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN;
+}
+
+
+bool CPortal_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex )
+{
+ bool bRet = BaseClass::Weapon_Switch( pWeapon, viewmodelindex );
+
+ return bRet;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPortal_Player::UpdateExpression( void )
+{
+ if ( !m_pExpresser )
+ return;
+
+ int iConcept = CONCEPT_CHELL_IDLE;
+ if ( GetHealth() <= 0 )
+ {
+ iConcept = CONCEPT_CHELL_DEAD;
+ }
+
+ GetExpresser()->SetOuter( this );
+
+ ClearExpression();
+ AI_Response response;
+ bool result = SpeakFindResponse( response, g_pszChellConcepts[iConcept] );
+ if ( !result )
+ {
+ m_flExpressionLoopTime = gpGlobals->curtime + RandomFloat(30,40);
+ return;
+ }
+
+ char const *szScene = response.GetResponsePtr();
+
+ // Ignore updates that choose the same scene
+ if ( m_iszExpressionScene != NULL_STRING && stricmp( STRING(m_iszExpressionScene), szScene ) == 0 )
+ return;
+
+ if ( m_hExpressionSceneEnt )
+ {
+ ClearExpression();
+ }
+
+ m_iszExpressionScene = AllocPooledString( szScene );
+ float flDuration = InstancedScriptedScene( this, szScene, &m_hExpressionSceneEnt, 0.0, true, NULL );
+ m_flExpressionLoopTime = gpGlobals->curtime + flDuration;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPortal_Player::ClearExpression( void )
+{
+ if ( m_hExpressionSceneEnt != NULL )
+ {
+ StopScriptedScene( this, m_hExpressionSceneEnt );
+ }
+ m_flExpressionLoopTime = gpGlobals->curtime;
+}
+
+
+void CPortal_Player::PreThink( void )
+{
+ QAngle vOldAngles = GetLocalAngles();
+ QAngle vTempAngles = GetLocalAngles();
+
+ vTempAngles = EyeAngles();
+
+ if ( vTempAngles[PITCH] > 180.0f )
+ {
+ vTempAngles[PITCH] -= 360.0f;
+ }
+
+ SetLocalAngles( vTempAngles );
+
+ BaseClass::PreThink();
+
+ if( (m_afButtonPressed & IN_JUMP) )
+ {
+ Jump();
+ }
+
+ //Reset bullet force accumulator, only lasts one frame
+ m_vecTotalBulletForce = vec3_origin;
+
+ SetLocalAngles( vOldAngles );
+}
+
+void CPortal_Player::PostThink( void )
+{
+ BaseClass::PostThink();
+
+ // Store the eye angles pitch so the client can compute its animation state correctly.
+ m_angEyeAngles = EyeAngles();
+
+ QAngle angles = GetLocalAngles();
+ angles[PITCH] = 0;
+ SetLocalAngles( angles );
+
+ // Regenerate heath after 3 seconds
+ if ( IsAlive() && GetHealth() < GetMaxHealth() )
+ {
+ // Color to overlay on the screen while the player is taking damage
+ color32 hurtScreenOverlay = {64,0,0,64};
+
+ if ( gpGlobals->curtime > m_fTimeLastHurt + sv_regeneration_wait_time.GetFloat() )
+ {
+ TakeHealth( 1, DMG_GENERIC );
+ m_bIsRegenerating = true;
+
+ if ( GetHealth() >= GetMaxHealth() )
+ {
+ m_bIsRegenerating = false;
+ }
+ }
+ else
+ {
+ m_bIsRegenerating = false;
+ UTIL_ScreenFade( this, hurtScreenOverlay, 1.0f, 0.1f, FFADE_IN|FFADE_PURGE );
+ }
+ }
+
+ UpdatePortalPlaneSounds();
+ UpdateWooshSounds();
+
+ m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
+
+ if ( IsAlive() && m_flExpressionLoopTime >= 0 && gpGlobals->curtime > m_flExpressionLoopTime )
+ {
+ // Random expressions need to be cleared, because they don't loop. So if we
+ // pick the same one again, we want to restart it.
+ ClearExpression();
+ m_iszExpressionScene = NULL_STRING;
+ UpdateExpression();
+ }
+
+ UpdateSecondsTaken();
+
+ // Try to fix the player if they're stuck
+ if ( m_bStuckOnPortalCollisionObject )
+ {
+ Vector vForward = ((CProp_Portal*)m_hPortalEnvironment.Get())->m_vPrevForward;
+ Vector vNewPos = GetAbsOrigin() + vForward * gpGlobals->frametime * -1000.0f;
+ Teleport( &vNewPos, NULL, &vForward );
+ m_bStuckOnPortalCollisionObject = false;
+ }
+}
+
+void CPortal_Player::PlayerDeathThink(void)
+{
+ float flForward;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if (GetFlags() & FL_ONGROUND)
+ {
+ flForward = GetAbsVelocity().Length() - 20;
+ if (flForward <= 0)
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+ else
+ {
+ Vector vecNewVelocity = GetAbsVelocity();
+ VectorNormalize( vecNewVelocity );
+ vecNewVelocity *= flForward;
+ SetAbsVelocity( vecNewVelocity );
+ }
+ }
+
+ if ( HasWeapons() )
+ {
+ // we drop the guns here because weapons that have an area effect and can kill their user
+ // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
+ // player class sometimes is freed. It's safer to manipulate the weapons once we know
+ // we aren't calling into any of their code anymore through the player pointer.
+ PackDeadPlayerItems();
+ }
+
+ if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING))
+ {
+ StudioFrameAdvance( );
+
+ m_iRespawnFrames++;
+ if ( m_iRespawnFrames < 60 ) // animations should be no longer than this
+ return;
+ }
+
+ if (m_lifeState == LIFE_DYING)
+ m_lifeState = LIFE_DEAD;
+
+ StopAnimation();
+
+ IncrementInterpolationFrame();
+ m_flPlaybackRate = 0.0;
+
+ int fAnyButtonDown = (m_nButtons & ~IN_SCORE);
+
+ // Strip out the duck key from this check if it's toggled
+ if ( (fAnyButtonDown & IN_DUCK) && GetToggledDuckState())
+ {
+ fAnyButtonDown &= ~IN_DUCK;
+ }
+
+ // wait for all buttons released
+ if ( m_lifeState == LIFE_DEAD )
+ {
+ if ( fAnyButtonDown || gpGlobals->curtime < m_flDeathTime + DEATH_ANIMATION_TIME )
+ return;
+
+ if ( g_pGameRules->FPlayerCanRespawn( this ) )
+ {
+ m_lifeState = LIFE_RESPAWNABLE;
+ }
+
+ return;
+ }
+
+ // if the player has been dead for one second longer than allowed by forcerespawn,
+ // forcerespawn isn't on. Send the player off to an intermission camera until they
+ // choose to respawn.
+ if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() )
+ {
+ // go to dead camera.
+ StartObserverMode( m_iObserverLastMode );
+ }
+
+ // wait for any button down, or mp_forcerespawn is set and the respawn time is up
+ if (!fAnyButtonDown
+ && !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) )
+ return;
+
+ m_nButtons = 0;
+ m_iRespawnFrames = 0;
+
+ //Msg( "Respawn\n");
+
+ respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam.
+ SetNextThink( TICK_NEVER_THINK );
+}
+
+void CPortal_Player::UpdatePortalPlaneSounds( void )
+{
+ CProp_Portal *pPortal = m_hPortalEnvironment;
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ Vector vVelocity;
+ GetVelocity( &vVelocity, NULL );
+
+ if ( !vVelocity.IsZero() )
+ {
+ Vector vMin, vMax;
+ CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
+
+ Vector vEarCenter = ( vMax + vMin ) / 2.0f;
+ Vector vDiagonal = vMax - vMin;
+
+ if ( !m_bIntersectingPortalPlane )
+ {
+ vDiagonal *= 0.25f;
+
+ if ( UTIL_IsBoxIntersectingPortal( vEarCenter, vDiagonal, pPortal ) )
+ {
+ m_bIntersectingPortalPlane = true;
+
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "PortalPlayer.EnterPortal", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
+ ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+ }
+ else
+ {
+ vDiagonal *= 0.30f;
+
+ if ( !UTIL_IsBoxIntersectingPortal( vEarCenter, vDiagonal, pPortal ) )
+ {
+ m_bIntersectingPortalPlane = false;
+
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "PortalPlayer.ExitPortal", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
+ ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+ }
+ }
+ }
+ else if ( m_bIntersectingPortalPlane )
+ {
+ m_bIntersectingPortalPlane = false;
+
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "PortalPlayer.ExitPortal", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ Vector vVelocity;
+ GetVelocity( &vVelocity, NULL );
+ ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
+ ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+}
+
+void CPortal_Player::UpdateWooshSounds( void )
+{
+ if ( m_pWooshSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ float fWooshVolume = GetAbsVelocity().Length() - MIN_FLING_SPEED;
+
+ if ( fWooshVolume < 0.0f )
+ {
+ controller.SoundChangeVolume( m_pWooshSound, 0.0f, 0.1f );
+ return;
+ }
+
+ fWooshVolume /= 2000.0f;
+ if ( fWooshVolume > 1.0f )
+ fWooshVolume = 1.0f;
+
+ controller.SoundChangeVolume( m_pWooshSound, fWooshVolume, 0.1f );
+ // controller.SoundChangePitch( m_pWooshSound, fWooshVolume + 0.5f, 0.1f );
+ }
+}
+
+void CPortal_Player::FireBullets ( const FireBulletsInfo_t &info )
+{
+ NoteWeaponFired();
+
+ BaseClass::FireBullets( info );
+}
+
+void CPortal_Player::NoteWeaponFired( void )
+{
+ Assert( m_pCurrentCommand );
+ if( m_pCurrentCommand )
+ {
+ m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number;
+ }
+}
+
+extern ConVar sv_maxunlag;
+
+bool CPortal_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
+{
+ // No need to lag compensate at all if we're not attacking in this command and
+ // we haven't attacked recently.
+ if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
+ return false;
+
+ // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it.
+ if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) )
+ return false;
+
+ const Vector &vMyOrigin = GetAbsOrigin();
+ const Vector &vHisOrigin = pPlayer->GetAbsOrigin();
+
+ // get max distance player could have moved within max lag compensation time,
+ // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value)
+ float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat();
+
+ // If the player is within this distance, lag compensate them in case they're running past us.
+ if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance )
+ return true;
+
+ // If their origin is not within a 45 degree cone in front of us, no need to lag compensate.
+ Vector vForward;
+ AngleVectors( pCmd->viewangles, &vForward );
+
+ Vector vDiff = vHisOrigin - vMyOrigin;
+ VectorNormalize( vDiff );
+
+ float flCosAngle = 0.707107f; // 45 degree angle
+ if ( vForward.Dot( vDiff ) < flCosAngle )
+ return false;
+
+ return true;
+}
+
+
+void CPortal_Player::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
+{
+ m_PlayerAnimState->DoAnimationEvent( event, nData );
+ TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override setup bones so that is uses the render angles from
+// the Portal animation state to setup the hitboxes.
+//-----------------------------------------------------------------------------
+void CPortal_Player::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
+{
+ VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM );
+
+ // Set the mdl cache semaphore.
+ MDLCACHE_CRITICAL_SECTION();
+
+ // Get the studio header.
+ Assert( GetModelPtr() );
+ CStudioHdr *pStudioHdr = GetModelPtr( );
+
+ Vector pos[MAXSTUDIOBONES];
+ Quaternion q[MAXSTUDIOBONES];
+
+ // Adjust hit boxes based on IK driven offset.
+ Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset );
+
+ // FIXME: pass this into Studio_BuildMatrices to skip transforms
+ CBoneBitList boneComputed;
+ if ( m_pIk )
+ {
+ m_iIKCounter++;
+ m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask );
+ GetSkeleton( pStudioHdr, pos, q, boneMask );
+
+ m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed );
+ CalculateIKLocks( gpGlobals->curtime );
+ m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed );
+ }
+ else
+ {
+ GetSkeleton( pStudioHdr, pos, q, boneMask );
+ }
+
+ CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() );
+ if ( pParent )
+ {
+ // We're doing bone merging, so do special stuff here.
+ CBoneCache *pParentCache = pParent->GetBoneCache();
+ if ( pParentCache )
+ {
+ BuildMatricesWithBoneMerge(
+ pStudioHdr,
+ m_PlayerAnimState->GetRenderAngles(),
+ adjOrigin,
+ pos,
+ q,
+ pBoneToWorld,
+ pParent,
+ pParentCache );
+
+ return;
+ }
+ }
+
+ Studio_BuildMatrices(
+ pStudioHdr,
+ m_PlayerAnimState->GetRenderAngles(),
+ adjOrigin,
+ pos,
+ q,
+ -1,
+ GetModelScale(), // Scaling
+ pBoneToWorld,
+ boneMask );
+}
+
+
+// Set the activity based on an event or current state
+void CPortal_Player::SetAnimation( PLAYER_ANIM playerAnim )
+{
+ return;
+}
+
+CAI_Expresser *CPortal_Player::CreateExpresser()
+{
+ Assert( !m_pExpresser );
+
+ if ( m_pExpresser )
+ {
+ delete m_pExpresser;
+ }
+
+ m_pExpresser = new CAI_Expresser(this);
+ if ( !m_pExpresser)
+ {
+ return NULL;
+ }
+ m_pExpresser->Connect(this);
+
+ return m_pExpresser;
+}
+
+//-----------------------------------------------------------------------------
+
+CAI_Expresser *CPortal_Player::GetExpresser()
+{
+ if ( m_pExpresser )
+ {
+ m_pExpresser->Connect(this);
+ }
+ return m_pExpresser;
+}
+
+
+extern int gEvilImpulse101;
+//-----------------------------------------------------------------------------
+// Purpose: Player reacts to bumping a weapon.
+// Input : pWeapon - the weapon that the player bumped into.
+// Output : Returns true if player picked up the weapon
+//-----------------------------------------------------------------------------
+bool CPortal_Player::BumpWeapon( CBaseCombatWeapon *pWeapon )
+{
+ CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
+
+ // Can I have this weapon type?
+ if ( !IsAllowedToPickupWeapons() )
+ return false;
+
+ if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
+ {
+ if ( gEvilImpulse101 )
+ {
+ UTIL_Remove( pWeapon );
+ }
+ return false;
+ }
+
+ // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows)
+ if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) )
+ {
+ return false;
+ }
+
+ CWeaponPortalgun *pPickupPortalgun = dynamic_cast<CWeaponPortalgun*>( pWeapon );
+
+ bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType());
+
+ if ( bOwnsWeaponAlready == true )
+ {
+ // If we picked up a second portal gun set the bool to alow secondary fire
+ if ( pPickupPortalgun )
+ {
+ CWeaponPortalgun *pPortalGun = static_cast<CWeaponPortalgun*>( Weapon_OwnsThisType( pWeapon->GetClassname() ) );
+
+ if ( pPickupPortalgun->CanFirePortal1() )
+ pPortalGun->SetCanFirePortal1();
+
+ if ( pPickupPortalgun->CanFirePortal2() )
+ pPortalGun->SetCanFirePortal2();
+
+ UTIL_Remove( pWeapon );
+ return true;
+ }
+
+ //If we have room for the ammo, then "take" the weapon too.
+ if ( Weapon_EquipAmmoOnly( pWeapon ) )
+ {
+ pWeapon->CheckRespawn();
+
+ UTIL_Remove( pWeapon );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ pWeapon->CheckRespawn();
+ Weapon_Equip( pWeapon );
+
+ // If we're holding and object before picking up portalgun, drop it
+ if ( pPickupPortalgun )
+ {
+ ForceDropOfCarriedPhysObjects( GetPlayerHeldEntity( this ) );
+ }
+
+ return true;
+}
+
+void CPortal_Player::ShutdownUseEntity( void )
+{
+ ShutdownPickupController( m_hUseEntity );
+}
+
+const Vector& CPortal_Player::WorldSpaceCenter( ) const
+{
+ m_vWorldSpaceCenterHolder = GetAbsOrigin();
+ m_vWorldSpaceCenterHolder.z += ( (IsDucked()) ? (VEC_DUCK_HULL_MAX_SCALED( this ).z) : (VEC_HULL_MAX_SCALED( this ).z) ) * 0.5f;
+ return m_vWorldSpaceCenterHolder;
+}
+
+void CPortal_Player::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
+{
+ Vector oldOrigin = GetLocalOrigin();
+ QAngle oldAngles = GetLocalAngles();
+ BaseClass::Teleport( newPosition, newAngles, newVelocity );
+ m_angEyeAngles = pl.v_angle;
+
+ m_PlayerAnimState->Teleport( newPosition, newAngles, this );
+}
+
+void CPortal_Player::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
+{
+ if( m_hPortalEnvironment.Get() == NULL )
+ return BaseClass::VPhysicsShadowUpdate( pPhysics );
+
+
+ //below is mostly a cut/paste of existing CBasePlayer::VPhysicsShadowUpdate code with some minor tweaks to avoid getting stuck in stuff when in a portal environment
+ if ( sv_turbophysics.GetBool() )
+ return;
+
+ Vector newPosition;
+
+ bool physicsUpdated = m_pPhysicsController->GetShadowPosition( &newPosition, NULL ) > 0 ? true : false;
+
+ // UNDONE: If the player is penetrating, but the player's game collisions are not stuck, teleport the physics shadow to the game position
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING )
+ {
+ CUtlVector<CBaseEntity *> list;
+ PhysGetListOfPenetratingEntities( this, list );
+ for ( int i = list.Count()-1; i >= 0; --i )
+ {
+ // filter out anything that isn't simulated by vphysics
+ // UNDONE: Filter out motion disabled objects?
+ if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ // I'm currently stuck inside a moving object, so allow vphysics to
+ // apply velocity to the player in order to separate these objects
+ m_touchedPhysObject = true;
+ }
+ }
+ }
+
+ if ( m_pPhysicsController->IsInContact() || (m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER) )
+ {
+ m_touchedPhysObject = true;
+ }
+
+ if ( IsFollowingPhysics() )
+ {
+ m_touchedPhysObject = true;
+ }
+
+ if ( GetMoveType() == MOVETYPE_NOCLIP )
+ {
+ m_oldOrigin = GetAbsOrigin();
+ return;
+ }
+
+ if ( phys_timescale.GetFloat() == 0.0f )
+ {
+ physicsUpdated = false;
+ }
+
+ if ( !physicsUpdated )
+ return;
+
+ IPhysicsObject *pPhysGround = GetGroundVPhysics();
+
+ Vector newVelocity;
+ pPhysics->GetPosition( &newPosition, 0 );
+ m_pPhysicsController->GetShadowVelocity( &newVelocity );
+
+
+
+ Vector tmp = GetAbsOrigin() - newPosition;
+ if ( !m_touchedPhysObject && !(GetFlags() & FL_ONGROUND) )
+ {
+ tmp.z *= 0.5f; // don't care about z delta as much
+ }
+
+ float dist = tmp.LengthSqr();
+ float deltaV = (newVelocity - GetAbsVelocity()).LengthSqr();
+
+ float maxDistErrorSqr = VPHYS_MAX_DISTSQR;
+ float maxVelErrorSqr = VPHYS_MAX_VELSQR;
+ if ( IsRideablePhysics(pPhysGround) )
+ {
+ maxDistErrorSqr *= 0.25;
+ maxVelErrorSqr *= 0.25;
+ }
+
+ if ( dist >= maxDistErrorSqr || deltaV >= maxVelErrorSqr || (pPhysGround && !m_touchedPhysObject) )
+ {
+ if ( m_touchedPhysObject || pPhysGround )
+ {
+ // BUGBUG: Rewrite this code using fixed timestep
+ if ( deltaV >= maxVelErrorSqr )
+ {
+ Vector dir = GetAbsVelocity();
+ float len = VectorNormalize(dir);
+ float dot = DotProduct( newVelocity, dir );
+ if ( dot > len )
+ {
+ dot = len;
+ }
+ else if ( dot < -len )
+ {
+ dot = -len;
+ }
+
+ VectorMA( newVelocity, -dot, dir, newVelocity );
+
+ if ( m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER )
+ {
+ float val = Lerp( 0.1f, len, dot );
+ VectorMA( newVelocity, val - len, dir, newVelocity );
+ }
+
+ if ( !IsRideablePhysics(pPhysGround) )
+ {
+ if ( !(m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) && IsSimulatingOnAlternateTicks() )
+ {
+ newVelocity *= 0.5f;
+ }
+ ApplyAbsVelocityImpulse( newVelocity );
+ }
+ }
+
+ trace_t trace;
+ UTIL_TraceEntity( this, newPosition, newPosition, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
+ if ( !trace.allsolid && !trace.startsolid )
+ {
+ SetAbsOrigin( newPosition );
+ }
+ }
+ else
+ {
+ trace_t trace;
+
+ Ray_t ray;
+ ray.Init( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
+
+ CTraceFilterSimple OriginalTraceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT );
+ CTraceFilterTranslateClones traceFilter( &OriginalTraceFilter );
+ UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace );
+
+ // current position is not ok, fixup
+ if ( trace.allsolid || trace.startsolid )
+ {
+ //try again with new position
+ ray.Init( newPosition, newPosition, WorldAlignMins(), WorldAlignMaxs() );
+ UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace );
+
+ if( trace.startsolid == false )
+ {
+ SetAbsOrigin( newPosition );
+ }
+ else
+ {
+ if( !FindClosestPassableSpace( this, newPosition - GetAbsOrigin(), MASK_PLAYERSOLID ) )
+ {
+ // Try moving the player closer to the center of the portal
+ CProp_Portal *pPortal = m_hPortalEnvironment.Get();
+ newPosition += ( pPortal->GetAbsOrigin() - WorldSpaceCenter() ) * 0.1f;
+ SetAbsOrigin( newPosition );
+
+ DevMsg( "Hurting the player for FindClosestPassableSpaceFailure!" );
+
+ // Deal 1 damage per frame... this will kill a player very fast, but allow for the above correction to fix some cases
+ CTakeDamageInfo info( this, this, vec3_origin, vec3_origin, 1, DMG_CRUSH );
+ OnTakeDamage( info );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( m_touchedPhysObject )
+ {
+ // check my position (physics object could have simulated into my position
+ // physics is not very far away, check my position
+ trace_t trace;
+ UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(),
+ MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
+
+ // is current position ok?
+ if ( trace.allsolid || trace.startsolid )
+ {
+ // stuck????!?!?
+ //Msg("Stuck on %s\n", trace.m_pEnt->GetClassname());
+ SetAbsOrigin( newPosition );
+ UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(),
+ MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
+ if ( trace.allsolid || trace.startsolid )
+ {
+ //Msg("Double Stuck\n");
+ SetAbsOrigin( m_oldOrigin );
+ }
+ }
+ }
+ }
+ m_oldOrigin = GetAbsOrigin();
+}
+
+bool CPortal_Player::UseFoundEntity( CBaseEntity *pUseEntity )
+{
+ bool usedSomething = false;
+
+ //!!!UNDONE: traceline here to prevent +USEing buttons through walls
+ int caps = pUseEntity->ObjectCaps();
+ variant_t emptyVariant;
+
+ if ( m_afButtonPressed & IN_USE )
+ {
+ // Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech.
+ if ( !pUseEntity->MyNPCPointer() )
+ {
+ EmitSound( "HL2Player.Use" );
+ }
+ }
+
+ if ( ( (m_nButtons & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) ||
+ ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) )
+ {
+ if ( caps & FCAP_CONTINUOUS_USE )
+ m_afPhysicsFlags |= PFLAG_USING;
+
+ pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
+
+ usedSomething = true;
+ }
+ // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away
+ else if ( (m_afButtonReleased & IN_USE) && (pUseEntity->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use
+ {
+ pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
+
+ usedSomething = true;
+ }
+
+#if HL2_SINGLE_PRIMARY_WEAPON_MODE
+
+ //Check for weapon pick-up
+ if ( m_afButtonPressed & IN_USE )
+ {
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>(pUseEntity);
+
+ if ( ( pWeapon != NULL ) && ( Weapon_CanSwitchTo( pWeapon ) ) )
+ {
+ //Try to take ammo or swap the weapon
+ if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) )
+ {
+ Weapon_EquipAmmoOnly( pWeapon );
+ }
+ else
+ {
+ Weapon_DropSlot( pWeapon->GetSlot() );
+ Weapon_Equip( pWeapon );
+ }
+
+ usedSomething = true;
+ }
+ }
+#endif
+
+ return usedSomething;
+}
+
+//bool CPortal_Player::StartReplayMode( float fDelay, float fDuration, int iEntity )
+//{
+// if ( !BaseClass::StartReplayMode( fDelay, fDuration, 1 ) )
+// return false;
+//
+// CSingleUserRecipientFilter filter( this );
+// filter.MakeReliable();
+//
+// UserMessageBegin( filter, "KillCam" );
+//
+// EHANDLE hPlayer = this;
+//
+// if ( m_hObserverTarget.Get() )
+// {
+// WRITE_EHANDLE( m_hObserverTarget ); // first target
+// WRITE_EHANDLE( hPlayer ); //second target
+// }
+// else
+// {
+// WRITE_EHANDLE( hPlayer ); // first target
+// WRITE_EHANDLE( 0 ); //second target
+// }
+// MessageEnd();
+//
+// return true;
+//}
+//
+//void CPortal_Player::StopReplayMode()
+//{
+// BaseClass::StopReplayMode();
+//
+// CSingleUserRecipientFilter filter( this );
+// filter.MakeReliable();
+//
+// UserMessageBegin( filter, "KillCam" );
+// WRITE_EHANDLE( 0 );
+// WRITE_EHANDLE( 0 );
+// MessageEnd();
+//}
+
+void CPortal_Player::PlayerUse( void )
+{
+ // Was use pressed or released?
+ if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) )
+ return;
+
+ if ( m_afButtonPressed & IN_USE )
+ {
+ // Currently using a latched entity?
+ if ( ClearUseEntity() )
+ {
+ return;
+ }
+ else
+ {
+ if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
+ {
+ m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
+ m_iTrain = TRAIN_NEW|TRAIN_OFF;
+ return;
+ }
+ else
+ { // Start controlling the train!
+ CBaseEntity *pTrain = GetGroundEntity();
+ if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) )
+ {
+ m_afPhysicsFlags |= PFLAG_DIROVERRIDE;
+ m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed());
+ m_iTrain |= TRAIN_NEW;
+ EmitSound( "HL2Player.TrainUse" );
+ return;
+ }
+ }
+ }
+
+ // Tracker 3926: We can't +USE something if we're climbing a ladder
+ if ( GetMoveType() == MOVETYPE_LADDER )
+ {
+ return;
+ }
+ }
+
+ CBaseEntity *pUseEntity = FindUseEntity();
+
+ bool usedSomething = false;
+
+ // Found an object
+ if ( pUseEntity )
+ {
+ SetHeldObjectOnOppositeSideOfPortal( false );
+
+ // TODO: Removed because we no longer have ghost animatings. May need to rework this code.
+ //// If we found a ghost animating then it needs to be held across a portal
+ //CGhostAnimating *pGhostAnimating = dynamic_cast<CGhostAnimating*>( pUseEntity );
+ //if ( pGhostAnimating )
+ //{
+ // CProp_Portal *pPortal = NULL;
+
+ // CPortalSimulator *pPortalSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pGhostAnimating->GetSourceEntity() );
+
+ // //HACKHACK: This assumes all portal simulators are a member of a prop_portal
+ // pPortal = (CProp_Portal *)(((char *)pPortalSimulator) - ((int)&(((CProp_Portal *)0)->m_PortalSimulator)));
+ // Assert( (&(pPortal->m_PortalSimulator)) == pPortalSimulator ); //doublechecking the hack
+
+ // if ( pPortal )
+ // {
+ // SetHeldObjectPortal( pPortal->m_hLinkedPortal );
+ // SetHeldObjectOnOppositeSideOfPortal( true );
+ // }
+ //}
+ usedSomething = UseFoundEntity( pUseEntity );
+ }
+ else
+ {
+ Vector forward;
+ EyeVectors( &forward, NULL, NULL );
+ Vector start = EyePosition();
+
+ Ray_t rayPortalTest;
+ rayPortalTest.Init( start, start + forward * PLAYER_USE_RADIUS );
+
+ float fMustBeCloserThan = 2.0f;
+
+ CProp_Portal *pPortal = UTIL_Portal_FirstAlongRay( rayPortalTest, fMustBeCloserThan );
+
+ if ( pPortal )
+ {
+ SetHeldObjectPortal( pPortal );
+ pUseEntity = FindUseEntityThroughPortal();
+ }
+
+ if ( pUseEntity )
+ {
+ SetHeldObjectOnOppositeSideOfPortal( true );
+ usedSomething = UseFoundEntity( pUseEntity );
+ }
+ else if ( m_afButtonPressed & IN_USE )
+ {
+ // Signal that we want to play the deny sound, unless the user is +USEing on a ladder!
+ // The sound is emitted in ItemPostFrame, since that occurs after GameMovement::ProcessMove which
+ // lets the ladder code unset this flag.
+ m_bPlayUseDenySound = true;
+ }
+ }
+
+ // Debounce the use key
+ if ( usedSomething && pUseEntity )
+ {
+ m_Local.m_nOldButtons |= IN_USE;
+ m_afButtonPressed &= ~IN_USE;
+ }
+}
+
+void CPortal_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper)
+{
+ if( m_bFixEyeAnglesFromPortalling )
+ {
+ //the idea here is to handle the notion that the player has portalled, but they sent us an angle update before receiving that message.
+ //If we don't handle this here, we end up sending back their old angles which makes them hiccup their angles for a frame
+ float fOldAngleDiff = fabs( AngleDistance( ucmd->viewangles.x, m_qPrePortalledViewAngles.x ) );
+ fOldAngleDiff += fabs( AngleDistance( ucmd->viewangles.y, m_qPrePortalledViewAngles.y ) );
+ fOldAngleDiff += fabs( AngleDistance( ucmd->viewangles.z, m_qPrePortalledViewAngles.z ) );
+
+ float fCurrentAngleDiff = fabs( AngleDistance( ucmd->viewangles.x, pl.v_angle.x ) );
+ fCurrentAngleDiff += fabs( AngleDistance( ucmd->viewangles.y, pl.v_angle.y ) );
+ fCurrentAngleDiff += fabs( AngleDistance( ucmd->viewangles.z, pl.v_angle.z ) );
+
+ if( fCurrentAngleDiff > fOldAngleDiff )
+ ucmd->viewangles = TransformAnglesToWorldSpace( ucmd->viewangles, m_matLastPortalled.As3x4() );
+
+ m_bFixEyeAnglesFromPortalling = false;
+ }
+
+ BaseClass::PlayerRunCommand( ucmd, moveHelper );
+}
+
+
+bool CPortal_Player::ClientCommand( const CCommand &args )
+{
+ if ( FStrEq( args[0], "spectate" ) )
+ {
+ // do nothing.
+ return true;
+ }
+
+ return BaseClass::ClientCommand( args );
+}
+
+void CPortal_Player::CheatImpulseCommands( int iImpulse )
+{
+ switch ( iImpulse )
+ {
+ case 101:
+ {
+ if( sv_cheats->GetBool() )
+ {
+ GiveAllItems();
+ }
+ }
+ break;
+
+ default:
+ BaseClass::CheatImpulseCommands( iImpulse );
+ }
+}
+
+void CPortal_Player::CreateViewModel( int index /*=0*/ )
+{
+ BaseClass::CreateViewModel( index );
+ return;
+ Assert( index >= 0 && index < MAX_VIEWMODELS );
+
+ if ( GetViewModel( index ) )
+ return;
+
+ CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" );
+ if ( vm )
+ {
+ vm->SetAbsOrigin( GetAbsOrigin() );
+ vm->SetOwner( this );
+ vm->SetIndex( index );
+ DispatchSpawn( vm );
+ vm->FollowEntity( this, false );
+ m_hViewModel.Set( index, vm );
+ }
+}
+
+bool CPortal_Player::BecomeRagdollOnClient( const Vector &force )
+{
+ return true;//BaseClass::BecomeRagdollOnClient( force );
+}
+
+void CPortal_Player::CreateRagdollEntity( const CTakeDamageInfo &info )
+{
+ if ( m_hRagdoll )
+ {
+ UTIL_RemoveImmediate( m_hRagdoll );
+ m_hRagdoll = NULL;
+ }
+
+#if PORTAL_HIDE_PLAYER_RAGDOLL
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW | EF_NOSHADOW );
+ AddEFlags( EFL_NO_DISSOLVE );
+#endif // PORTAL_HIDE_PLAYER_RAGDOLL
+ CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
+ pRagdoll->m_takedamage = DAMAGE_NO;
+ m_hRagdoll = pRagdoll;
+
+/*
+ // If we already have a ragdoll destroy it.
+ CPortalRagdoll *pRagdoll = dynamic_cast<CPortalRagdoll*>( m_hRagdoll.Get() );
+ if( pRagdoll )
+ {
+ UTIL_Remove( pRagdoll );
+ pRagdoll = NULL;
+ }
+ Assert( pRagdoll == NULL );
+
+ // Create a ragdoll.
+ pRagdoll = dynamic_cast<CPortalRagdoll*>( CreateEntityByName( "portal_ragdoll" ) );
+ if ( pRagdoll )
+ {
+
+
+ pRagdoll->m_hPlayer = this;
+ pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
+ pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
+ pRagdoll->m_nModelIndex = m_nModelIndex;
+ pRagdoll->m_nForceBone = m_nForceBone;
+ pRagdoll->CopyAnimationDataFrom( this );
+ pRagdoll->SetOwnerEntity( this );
+ pRagdoll->m_flAnimTime = gpGlobals->curtime;
+ pRagdoll->m_flPlaybackRate = 0.0;
+ pRagdoll->SetCycle( 0 );
+ pRagdoll->ResetSequence( 0 );
+
+ float fSequenceDuration = SequenceDuration( GetSequence() );
+ float fPreviousCycle = clamp(GetCycle()-( 0.1 * ( 1 / fSequenceDuration ) ),0.f,1.f);
+ float fCurCycle = GetCycle();
+ matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES];
+ SetupBones( pBoneToWorldNext, BONE_USED_BY_ANYTHING );
+ SetCycle( fPreviousCycle );
+ SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
+ SetCycle( fCurCycle );
+
+ pRagdoll->InitRagdoll( info.GetDamageForce(), m_nForceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorldNext, 0.1f, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
+ pRagdoll->SetMoveType( MOVETYPE_VPHYSICS );
+ pRagdoll->SetSolid( SOLID_VPHYSICS );
+ if ( IsDissolving() )
+ {
+ pRagdoll->TransferDissolveFrom( this );
+ }
+
+ Vector mins, maxs;
+ mins = CollisionProp()->OBBMins();
+ maxs = CollisionProp()->OBBMaxs();
+ pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs );
+ pRagdoll->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ }
+
+ // Turn off the player.
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW | EF_NOSHADOW );
+ SetMoveType( MOVETYPE_NONE );
+
+ // Save ragdoll handle.
+ m_hRagdoll = pRagdoll;
+*/
+}
+
+void CPortal_Player::Jump( void )
+{
+ g_PortalGameStats.Event_PlayerJump( GetAbsOrigin(), GetAbsVelocity() );
+ BaseClass::Jump();
+}
+
+void CPortal_Player::Event_Killed( const CTakeDamageInfo &info )
+{
+ //update damage info with our accumulated physics force
+ CTakeDamageInfo subinfo = info;
+ subinfo.SetDamageForce( m_vecTotalBulletForce );
+
+ // show killer in death cam mode
+ // chopped down version of SetObserverTarget without the team check
+ //if( info.GetAttacker() )
+ //{
+ // // set new target
+ // m_hObserverTarget.Set( info.GetAttacker() );
+ //}
+ //else
+ // m_hObserverTarget.Set( NULL );
+
+ UpdateExpression();
+
+ // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
+ // because we still want to transmit to the clients in our PVS.
+ CreateRagdollEntity( info );
+
+ BaseClass::Event_Killed( subinfo );
+
+#if PORTAL_HIDE_PLAYER_RAGDOLL
+ // Fizzle all portals so they don't see the player disappear
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+
+ if( pTempPortal && pTempPortal->m_bActivated )
+ {
+ pTempPortal->Fizzle();
+ }
+ }
+#endif // PORTAL_HIDE_PLAYER_RAGDOLL
+
+ if ( (info.GetDamageType() & DMG_DISSOLVE) && !(m_hRagdoll.Get()->GetEFlags() & EFL_NO_DISSOLVE) )
+ {
+ if ( m_hRagdoll )
+ {
+ m_hRagdoll->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL );
+ }
+ }
+
+ m_lifeState = LIFE_DYING;
+ StopZooming();
+
+ if ( GetObserverTarget() )
+ {
+ //StartReplayMode( 3, 3, GetObserverTarget()->entindex() );
+ //StartObserverMode( OBS_MODE_DEATHCAM );
+ }
+}
+
+int CPortal_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo inputInfoCopy( inputInfo );
+
+ // If you shoot yourself, make it hurt but push you less
+ if ( inputInfoCopy.GetAttacker() == this && inputInfoCopy.GetDamageType() == DMG_BULLET )
+ {
+ inputInfoCopy.ScaleDamage( 5.0f );
+ inputInfoCopy.ScaleDamageForce( 0.05f );
+ }
+
+ CBaseEntity *pAttacker = inputInfoCopy.GetAttacker();
+ CBaseEntity *pInflictor = inputInfoCopy.GetInflictor();
+
+ bool bIsTurret = false;
+
+ if ( pAttacker && FClassnameIs( pAttacker, "npc_portal_turret_floor" ) )
+ bIsTurret = true;
+
+ // Refuse damage from prop_glados_core.
+ if ( (pAttacker && FClassnameIs( pAttacker, "prop_glados_core" )) ||
+ (pInflictor && FClassnameIs( pInflictor, "prop_glados_core" )) )
+ {
+ inputInfoCopy.SetDamage(0.0f);
+ }
+
+ if ( bIsTurret && ( inputInfoCopy.GetDamageType() & DMG_BULLET ) )
+ {
+ Vector vLateralForce = inputInfoCopy.GetDamageForce();
+ vLateralForce.z = 0.0f;
+
+ // Push if the player is moving against the force direction
+ if ( GetAbsVelocity().Dot( vLateralForce ) < 0.0f )
+ ApplyAbsVelocityImpulse( vLateralForce );
+ }
+ else if ( ( inputInfoCopy.GetDamageType() & DMG_CRUSH ) )
+ {
+ if ( bIsTurret )
+ {
+ inputInfoCopy.SetDamage( inputInfoCopy.GetDamage() * 0.5f );
+ }
+
+ if ( inputInfoCopy.GetDamage() >= 10.0f )
+ {
+ EmitSound( "PortalPlayer.BonkYelp" );
+ }
+ }
+ else if ( ( inputInfoCopy.GetDamageType() & DMG_SHOCK ) || ( inputInfoCopy.GetDamageType() & DMG_BURN ) )
+ {
+ EmitSound( "PortalPortal.PainYelp" );
+ }
+
+ int ret = BaseClass::OnTakeDamage( inputInfoCopy );
+
+ // Copy the multidamage damage origin over what the base class wrote, because
+ // that gets translated correctly though portals.
+ m_DmgOrigin = inputInfo.GetDamagePosition();
+
+ if ( GetHealth() < 100 )
+ {
+ m_fTimeLastHurt = gpGlobals->curtime;
+ }
+
+ return ret;
+}
+
+int CPortal_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // set damage type sustained
+ m_bitsDamageType |= info.GetDamageType();
+
+ if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) )
+ return 0;
+
+ CBaseEntity * attacker = info.GetAttacker();
+
+ if ( !attacker )
+ return 0;
+
+ Vector vecDir = vec3_origin;
+ if ( info.GetInflictor() )
+ {
+ vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
+ VectorNormalize( vecDir );
+ }
+
+ if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) &&
+ ( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) )
+ {
+ Vector force = vecDir;// * -DamageForce( WorldAlignSize(), info.GetBaseDamage() );
+ if ( force.z > 250.0f )
+ {
+ force.z = 250.0f;
+ }
+ ApplyAbsVelocityImpulse( force );
+ }
+
+ // fire global game event
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
+ if ( event )
+ {
+ event->SetInt("userid", GetUserID() );
+ event->SetInt("health", MAX(0, m_iHealth) );
+ event->SetInt("priority", 5 ); // HLTV event priority, not transmitted
+
+ if ( attacker->IsPlayer() )
+ {
+ CBasePlayer *player = ToBasePlayer( attacker );
+ event->SetInt("attacker", player->GetUserID() ); // hurt by other player
+ }
+ else
+ {
+ event->SetInt("attacker", 0 ); // hurt by "world"
+ }
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Insert a combat sound so that nearby NPCs hear battle
+ if ( attacker->IsNPC() )
+ {
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<<TODO>>//magic number
+ }
+
+ return 1;
+}
+
+
+void CPortal_Player::ForceDuckThisFrame( void )
+{
+ if( m_Local.m_bDucked != true )
+ {
+ //m_Local.m_bDucking = false;
+ m_Local.m_bDucked = true;
+ ForceButtons( IN_DUCK );
+ AddFlag( FL_DUCKING );
+ SetVCollisionState( GetAbsOrigin(), GetAbsVelocity(), VPHYS_CROUCH );
+ }
+}
+
+void CPortal_Player::UnDuck( void )
+{
+ if( m_Local.m_bDucked != false )
+ {
+ m_Local.m_bDucked = false;
+ UnforceButtons( IN_DUCK );
+ RemoveFlag( FL_DUCKING );
+ SetVCollisionState( GetAbsOrigin(), GetAbsVelocity(), VPHYS_WALK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overload for portal-- Our player can lift his own mass.
+// Input : *pObject - The object to lift
+// bLimitMassAndSize - check for mass/size limits
+//-----------------------------------------------------------------------------
+void CPortal_Player::PickupObject(CBaseEntity *pObject, bool bLimitMassAndSize )
+{
+ // can't pick up what you're standing on
+ if ( GetGroundEntity() == pObject )
+ return;
+
+ if ( bLimitMassAndSize == true )
+ {
+ if ( CBasePlayer::CanPickupObject( pObject, PORTAL_PLAYER_MAX_LIFT_MASS, PORTAL_PLAYER_MAX_LIFT_SIZE ) == false )
+ return;
+ }
+
+ // Can't be picked up if NPCs are on me
+ if ( pObject->HasNPCsOnIt() )
+ return;
+
+ PlayerPickupObject( this, pObject );
+}
+
+void CPortal_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis )
+{
+ m_bHeldObjectOnOppositeSideOfPortal = false;
+ BaseClass::ForceDropOfCarriedPhysObjects( pOnlyIfHoldingThis );
+}
+
+void CPortal_Player::IncrementPortalsPlaced( void )
+{
+ m_StatsThisLevel.iNumPortalsPlaced++;
+
+ if ( m_iBonusChallenge == PORTAL_CHALLENGE_PORTALS )
+ SetBonusProgress( static_cast<int>( m_StatsThisLevel.iNumPortalsPlaced ) );
+}
+
+void CPortal_Player::IncrementStepsTaken( void )
+{
+ m_StatsThisLevel.iNumStepsTaken++;
+
+ if ( m_iBonusChallenge == PORTAL_CHALLENGE_STEPS )
+ SetBonusProgress( static_cast<int>( m_StatsThisLevel.iNumStepsTaken ) );
+}
+
+void CPortal_Player::UpdateSecondsTaken( void )
+{
+ float fSecondsSinceLastUpdate = ( gpGlobals->curtime - m_fTimeLastNumSecondsUpdate );
+ m_StatsThisLevel.fNumSecondsTaken += fSecondsSinceLastUpdate;
+ m_fTimeLastNumSecondsUpdate = gpGlobals->curtime;
+
+ if ( m_iBonusChallenge == PORTAL_CHALLENGE_TIME )
+ SetBonusProgress( static_cast<int>( m_StatsThisLevel.fNumSecondsTaken ) );
+
+ if ( m_fNeuroToxinDamageTime > 0.0f )
+ {
+ float fTimeRemaining = m_fNeuroToxinDamageTime - gpGlobals->curtime;
+
+ if ( fTimeRemaining < 0.0f )
+ {
+ CTakeDamageInfo info;
+ info.SetDamage( gpGlobals->frametime * 50.0f );
+ info.SetDamageType( DMG_NERVEGAS );
+ TakeDamage( info );
+ fTimeRemaining = 0.0f;
+ }
+
+ PauseBonusProgress( false );
+ SetBonusProgress( static_cast<int>( fTimeRemaining ) );
+ }
+}
+
+void CPortal_Player::ResetThisLevelStats( void )
+{
+ m_StatsThisLevel.iNumPortalsPlaced = 0;
+ m_StatsThisLevel.iNumStepsTaken = 0;
+ m_StatsThisLevel.fNumSecondsTaken = 0.0f;
+
+ if ( m_iBonusChallenge != PORTAL_CHALLENGE_NONE )
+ SetBonusProgress( 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the area bits variable which is networked down to the client to determine
+// which area portals should be closed based on visibility.
+// Input : *pvs - pvs to be used to determine visibility of the portals
+//-----------------------------------------------------------------------------
+void CPortal_Player::UpdatePortalViewAreaBits( unsigned char *pvs, int pvssize )
+{
+ Assert ( pvs );
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount == 0 )
+ return;
+
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ int *portalArea = (int *)stackalloc( sizeof( int ) * iPortalCount );
+ bool *bUsePortalForVis = (bool *)stackalloc( sizeof( bool ) * iPortalCount );
+
+ unsigned char *portalTempBits = (unsigned char *)stackalloc( sizeof( unsigned char ) * 32 * iPortalCount );
+ COMPILE_TIME_ASSERT( (sizeof( unsigned char ) * 32) >= sizeof( ((CPlayerLocalData*)0)->m_chAreaBits ) );
+
+ // setup area bits for these portals
+ for ( int i = 0; i < iPortalCount; ++i )
+ {
+ CProp_Portal* pLocalPortal = pPortals[ i ];
+ // Make sure this portal is active before adding it's location to the pvs
+ if ( pLocalPortal && pLocalPortal->m_bActivated )
+ {
+ CProp_Portal* pRemotePortal = pLocalPortal->m_hLinkedPortal.Get();
+
+ // Make sure this portal's linked portal is in the PVS before we add what it can see
+ if ( pRemotePortal && pRemotePortal->m_bActivated && pRemotePortal->NetworkProp() &&
+ pRemotePortal->NetworkProp()->IsInPVS( edict(), pvs, pvssize ) )
+ {
+ portalArea[ i ] = engine->GetArea( pPortals[ i ]->GetAbsOrigin() );
+
+ if ( portalArea [ i ] >= 0 )
+ {
+ bUsePortalForVis[ i ] = true;
+ }
+
+ engine->GetAreaBits( portalArea[ i ], &portalTempBits[ i * 32 ], sizeof( unsigned char ) * 32 );
+ }
+ }
+ }
+
+ // Use the union of player-view area bits and the portal-view area bits of each portal
+ for ( int i = 0; i < m_Local.m_chAreaBits.Count(); i++ )
+ {
+ for ( int j = 0; j < iPortalCount; ++j )
+ {
+ // If this portal is active, in PVS and it's location is valid
+ if ( bUsePortalForVis[ j ] )
+ {
+ m_Local.m_chAreaBits.Set( i, m_Local.m_chAreaBits[ i ] | portalTempBits[ (j * 32) + i ] );
+ }
+ }
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// AddPortalCornersToEnginePVS
+// Subroutine to wrap the adding of portal corners to the PVS which is called once for the setup of each portal.
+// input - pPortal: the portal we are viewing 'out of' which needs it's corners added to the PVS
+//////////////////////////////////////////////////////////////////////////
+void AddPortalCornersToEnginePVS( CProp_Portal* pPortal )
+{
+ Assert ( pPortal );
+
+ if ( !pPortal )
+ return;
+
+ Vector vForward, vRight, vUp;
+ pPortal->GetVectors( &vForward, &vRight, &vUp );
+
+ // Center of the remote portal
+ Vector ptOrigin = pPortal->GetAbsOrigin();
+
+ // Distance offsets to the different edges of the portal... Used in the placement checks
+ Vector vToTopEdge = vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS );
+ Vector vToBottomEdge = -vToTopEdge;
+ Vector vToRightEdge = vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS );
+ Vector vToLeftEdge = -vToRightEdge;
+
+ // Distance to place PVS points away from portal, to avoid being in solid
+ Vector vForwardBump = vForward * 1.0f;
+
+ // Add center and edges to the engine PVS
+ engine->AddOriginToPVS( ptOrigin + vForwardBump);
+ engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToLeftEdge + vForwardBump );
+ engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToRightEdge + vForwardBump );
+ engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToLeftEdge + vForwardBump );
+ engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToRightEdge + vForwardBump );
+}
+
+void PortalSetupVisibility( CBaseEntity *pPlayer, int area, unsigned char *pvs, int pvssize )
+{
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount == 0 )
+ return;
+
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pPortal = pPortals[i];
+
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ if ( pPortal->NetworkProp()->IsInPVS( pPlayer->edict(), pvs, pvssize ) )
+ {
+ if ( engine->CheckAreasConnected( area, pPortal->NetworkProp()->AreaNum() ) )
+ {
+ CProp_Portal *pLinkedPortal = static_cast<CProp_Portal*>( pPortal->m_hLinkedPortal.Get() );
+ if ( pLinkedPortal )
+ {
+ AddPortalCornersToEnginePVS ( pLinkedPortal );
+ }
+ }
+ }
+ }
+ }
+}
+
+void CPortal_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
+{
+ BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
+
+ int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
+
+ // At this point the EyePosition has been added as a view origin, but if we are currently stuck
+ // in a portal, our EyePosition may return a point in solid. Find the reflected eye position
+ // and use that as a vis origin instead.
+ if ( m_hPortalEnvironment )
+ {
+ CProp_Portal *pPortal = NULL, *pRemotePortal = NULL;
+ pPortal = m_hPortalEnvironment;
+ pRemotePortal = pPortal->m_hLinkedPortal;
+
+ if ( pPortal && pRemotePortal && pPortal->m_bActivated && pRemotePortal->m_bActivated )
+ {
+ Vector ptPortalCenter = pPortal->GetAbsOrigin();
+ Vector vPortalForward;
+ pPortal->GetVectors( &vPortalForward, NULL, NULL );
+
+ Vector eyeOrigin = EyePosition();
+ Vector vEyeToPortalCenter = ptPortalCenter - eyeOrigin;
+
+ float fPortalDist = vPortalForward.Dot( vEyeToPortalCenter );
+ if( fPortalDist > 0.0f ) //eye point is behind portal
+ {
+ // Move eye origin to it's transformed position on the other side of the portal
+ UTIL_Portal_PointTransform( pPortal->MatrixThisToLinked(), eyeOrigin, eyeOrigin );
+
+ // Use this as our view origin (as this is where the client will be displaying from)
+ engine->AddOriginToPVS( eyeOrigin );
+ if ( !pViewEntity || pViewEntity->IsPlayer() )
+ {
+ area = engine->GetArea( eyeOrigin );
+ }
+ }
+ }
+ }
+
+ PortalSetupVisibility( this, area, pvs, pvssize );
+}
+
+
+#ifdef PORTAL_MP
+
+CBaseEntity* CPortal_Player::EntSelectSpawnPoint( void )
+{
+ CBaseEntity *pSpot = NULL;
+ CBaseEntity *pLastSpawnPoint = g_pLastSpawn;
+ edict_t *player = edict();
+ const char *pSpawnpointName = "info_player_start";
+
+ /*if ( HL2MPRules()->IsTeamplay() == true )
+ {
+ if ( GetTeamNumber() == TEAM_COMBINE )
+ {
+ pSpawnpointName = "info_player_combine";
+ pLastSpawnPoint = g_pLastCombineSpawn;
+ }
+ else if ( GetTeamNumber() == TEAM_REBELS )
+ {
+ pSpawnpointName = "info_player_rebel";
+ pLastSpawnPoint = g_pLastRebelSpawn;
+ }
+
+ if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL )
+ {
+ pSpawnpointName = "info_player_deathmatch";
+ pLastSpawnPoint = g_pLastSpawn;
+ }
+ }*/
+
+ pSpot = pLastSpawnPoint;
+ // Randomize the start spot
+ for ( int i = random->RandomInt(1,5); i > 0; i-- )
+ pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
+ if ( !pSpot ) // skip over the null point
+ pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
+
+ CBaseEntity *pFirstSpot = pSpot;
+
+ do
+ {
+ if ( pSpot )
+ {
+ // check if pSpot is valid
+ if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) )
+ {
+ if ( pSpot->GetLocalOrigin() == vec3_origin )
+ {
+ pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
+ continue;
+ }
+
+ // if so, go to pSpot
+ goto ReturnSpot;
+ }
+ }
+ // increment pSpot
+ pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
+ } while ( pSpot != pFirstSpot ); // loop if we're not back to the start
+
+ // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there
+ if ( pSpot )
+ {
+ CBaseEntity *ent = NULL;
+ for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
+ {
+ // if ent is a client, kill em (unless they are ourselves)
+ if ( ent->IsPlayer() && !(ent->edict() == player) )
+ ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) );
+ }
+ goto ReturnSpot;
+ }
+
+ if ( !pSpot )
+ {
+ pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_start" );
+
+ if ( pSpot )
+ goto ReturnSpot;
+ }
+
+ReturnSpot:
+
+ /*if ( HL2MPRules()->IsTeamplay() == true )
+ {
+ if ( GetTeamNumber() == TEAM_COMBINE )
+ {
+ g_pLastCombineSpawn = pSpot;
+ }
+ else if ( GetTeamNumber() == TEAM_REBELS )
+ {
+ g_pLastRebelSpawn = pSpot;
+ }
+ }*/
+
+ g_pLastSpawn = pSpot;
+
+ //m_flSlamProtectTime = gpGlobals->curtime + 0.5;
+
+ return pSpot;
+}
+
+void CPortal_Player::PickTeam( void )
+{
+ //picks lowest or random
+ CTeam *pCombine = g_Teams[TEAM_COMBINE];
+ CTeam *pRebels = g_Teams[TEAM_REBELS];
+ if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() )
+ {
+ ChangeTeam( TEAM_REBELS );
+ }
+ else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() )
+ {
+ ChangeTeam( TEAM_COMBINE );
+ }
+ else
+ {
+ ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) );
+ }
+}
+
+#endif
+
+CON_COMMAND( startadmiregloves, "Starts the admire gloves animation." )
+{
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+ if( pPlayer == NULL )
+ pPlayer = GetPortalPlayer( 1 ); //last ditch effort
+
+ if( pPlayer )
+ pPlayer->StartAdmireGlovesAnimation();
+}
+
+CON_COMMAND( displayportalplayerstats, "Displays current level stats for portals placed, steps taken, and seconds taken." )
+{
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+ if( pPlayer == NULL )
+ pPlayer = GetPortalPlayer( 1 ); //last ditch effort
+
+ if( pPlayer )
+ {
+ int iMinutes = static_cast<int>( pPlayer->NumSecondsTaken() / 60.0f );
+ int iSeconds = static_cast<int>( pPlayer->NumSecondsTaken() ) % 60;
+
+ CFmtStr msg;
+ NDebugOverlay::ScreenText( 0.5f, 0.5f, msg.sprintf( "Portals Placed: %d\nSteps Taken: %d\nTime: %d:%d", pPlayer->NumPortalsPlaced(), pPlayer->NumStepsTaken(), iMinutes, iSeconds ), 255, 255, 255, 150, 5.0f );
+ }
+}
+
+CON_COMMAND( startneurotoxins, "Starts the nerve gas timer." )
+{
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+ if( pPlayer == NULL )
+ pPlayer = GetPortalPlayer( 1 ); //last ditch effort
+
+ float fCoundownTime = 180.0f;
+
+ if ( args.ArgC() > 1 )
+ fCoundownTime = atof( args[ 1 ] );
+
+ if( pPlayer )
+ pPlayer->SetNeuroToxinDamageTime( fCoundownTime );
+}
diff --git a/game/server/portal/portal_player.h b/game/server/portal/portal_player.h
new file mode 100644
index 0000000..0950f3f
--- /dev/null
+++ b/game/server/portal/portal_player.h
@@ -0,0 +1,253 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef PORTAL_PLAYER_H
+#define PORTAL_PLAYER_H
+#pragma once
+
+class CPortal_Player;
+
+#include "player.h"
+#include "portal_playeranimstate.h"
+#include "hl2_playerlocaldata.h"
+#include "hl2_player.h"
+#include "simtimer.h"
+#include "soundenvelope.h"
+#include "portal_player_shared.h"
+#include "prop_portal.h"
+#include "weapon_portalbase.h"
+#include "in_buttons.h"
+#include "func_liquidportal.h"
+#include "ai_speech.h" // For expresser host
+
+struct PortalPlayerStatistics_t
+{
+ int iNumPortalsPlaced;
+ int iNumStepsTaken;
+ float fNumSecondsTaken;
+};
+
+//=============================================================================
+// >> Portal_Player
+//=============================================================================
+class CPortal_Player : public CAI_ExpresserHost<CHL2_Player>
+{
+public:
+ DECLARE_CLASS( CPortal_Player, CHL2_Player );
+
+ CPortal_Player();
+ ~CPortal_Player( void );
+
+ static CPortal_Player *CreatePlayer( const char *className, edict_t *ed )
+ {
+ CPortal_Player::s_PlayerEdict = ed;
+ return (CPortal_Player*)CreateEntityByName( className );
+ }
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ virtual void Precache( void );
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds( void );
+ virtual void Spawn( void );
+ virtual void OnRestore( void );
+ virtual void Activate( void );
+
+ virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
+
+ virtual void PostThink( void );
+ virtual void PreThink( void );
+ virtual void PlayerDeathThink( void );
+
+ void UpdatePortalPlaneSounds( void );
+ void UpdateWooshSounds( void );
+
+ Activity TranslateActivity( Activity ActToTranslate, bool *pRequired = NULL );
+ virtual void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity );
+
+ Activity TranslateTeamActivity( Activity ActToTranslate );
+
+ virtual void SetAnimation( PLAYER_ANIM playerAnim );
+
+ virtual CAI_Expresser* GetExpresser( void );
+
+ virtual void PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper);
+
+ virtual bool ClientCommand( const CCommand &args );
+ virtual void CreateViewModel( int viewmodelindex = 0 );
+ virtual bool BecomeRagdollOnClient( const Vector &force );
+ virtual int OnTakeDamage( const CTakeDamageInfo &inputInfo );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual bool WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const;
+ virtual void FireBullets ( const FireBulletsInfo_t &info );
+ virtual bool Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex = 0);
+ virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon );
+ virtual void ShutdownUseEntity( void );
+
+ virtual const Vector& WorldSpaceCenter( ) const;
+
+ virtual void VPhysicsShadowUpdate( IPhysicsObject *pPhysics );
+
+ //virtual bool StartReplayMode( float fDelay, float fDuration, int iEntity );
+ //virtual void StopReplayMode();
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual void Jump( void );
+
+ bool UseFoundEntity( CBaseEntity *pUseEntity );
+ CBaseEntity* FindUseEntity( void );
+ CBaseEntity* FindUseEntityThroughPortal( void );
+
+ virtual void PlayerUse( void );
+ //virtual bool StartObserverMode( int mode );
+ virtual void GetStepSoundVelocities( float *velwalk, float *velrun );
+ virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force );
+ virtual void UpdateOnRemove( void );
+
+ virtual void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize );
+ virtual void UpdatePortalViewAreaBits( unsigned char *pvs, int pvssize );
+
+ bool ValidatePlayerModel( const char *pModel );
+
+ QAngle GetAnimEyeAngles( void ) { return m_angEyeAngles.Get(); }
+
+ Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget = NULL );
+
+ void CheatImpulseCommands( int iImpulse );
+ void CreateRagdollEntity( const CTakeDamageInfo &info );
+ void GiveAllItems( void );
+ void GiveDefaultItems( void );
+
+ void NoteWeaponFired( void );
+
+ void ResetAnimation( void );
+
+ void SetPlayerModel( void );
+
+ void UpdateExpression ( void );
+ void ClearExpression ( void );
+
+ int GetPlayerModelType( void ) { return m_iPlayerSoundType; }
+
+ void ForceDuckThisFrame( void );
+ void UnDuck ( void );
+ inline void ForceJumpThisFrame( void ) { ForceButtons( IN_JUMP ); }
+
+ void DoAnimationEvent( PlayerAnimEvent_t event, int nData );
+ void SetupBones( matrix3x4_t *pBoneToWorld, int boneMask );
+
+ // physics interactions
+ virtual void PickupObject(CBaseEntity *pObject, bool bLimitMassAndSize );
+ virtual void ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis );
+
+ void ToggleHeldObjectOnOppositeSideOfPortal( void ) { m_bHeldObjectOnOppositeSideOfPortal = !m_bHeldObjectOnOppositeSideOfPortal; }
+ void SetHeldObjectOnOppositeSideOfPortal( bool p_bHeldObjectOnOppositeSideOfPortal ) { m_bHeldObjectOnOppositeSideOfPortal = p_bHeldObjectOnOppositeSideOfPortal; }
+ bool IsHeldObjectOnOppositeSideOfPortal( void ) { return m_bHeldObjectOnOppositeSideOfPortal; }
+ CProp_Portal *GetHeldObjectPortal( void ) { return m_pHeldObjectPortal; }
+ void SetHeldObjectPortal( CProp_Portal *pPortal ) { m_pHeldObjectPortal = pPortal; }
+
+ void SetStuckOnPortalCollisionObject( void ) { m_bStuckOnPortalCollisionObject = true; }
+
+ CWeaponPortalBase* GetActivePortalWeapon() const;
+
+ void IncrementPortalsPlaced( void );
+ void IncrementStepsTaken( void );
+ void UpdateSecondsTaken( void );
+ void ResetThisLevelStats( void );
+ int NumPortalsPlaced( void ) const { return m_StatsThisLevel.iNumPortalsPlaced; }
+ int NumStepsTaken( void ) const { return m_StatsThisLevel.iNumStepsTaken; }
+ float NumSecondsTaken( void ) const { return m_StatsThisLevel.fNumSecondsTaken; }
+
+ void SetNeuroToxinDamageTime( float fCountdownSeconds ) { m_fNeuroToxinDamageTime = gpGlobals->curtime + fCountdownSeconds; }
+
+ void IncNumCamerasDetatched( void ) { ++m_iNumCamerasDetatched; }
+ int GetNumCamerasDetatched( void ) const { return m_iNumCamerasDetatched; }
+
+ Vector m_vecTotalBulletForce; //Accumulator for bullet force in a single frame
+
+ bool m_bSilentDropAndPickup;
+
+ // Tracks our ragdoll entity.
+ CNetworkHandle( CBaseEntity, m_hRagdoll ); // networked entity handle
+
+ void SuppressCrosshair( bool bState ) { m_bSuppressingCrosshair = bState; }
+
+private:
+
+ virtual CAI_Expresser* CreateExpresser( void );
+
+ CSoundPatch *m_pWooshSound;
+
+ CNetworkQAngle( m_angEyeAngles );
+
+ CPortalPlayerAnimState* m_PlayerAnimState;
+
+ int m_iLastWeaponFireUsercmd;
+ CNetworkVar( int, m_iSpawnInterpCounter );
+ CNetworkVar( int, m_iPlayerSoundType );
+ CNetworkVar( bool, m_bSuppressingCrosshair );
+
+ CNetworkVar( bool, m_bHeldObjectOnOppositeSideOfPortal );
+ CNetworkHandle( CProp_Portal, m_pHeldObjectPortal ); // networked entity handle
+
+ bool m_bIntersectingPortalPlane;
+ bool m_bStuckOnPortalCollisionObject;
+
+ float m_fTimeLastHurt;
+ bool m_bIsRegenerating; // Is the player currently regaining health
+
+ float m_fNeuroToxinDamageTime;
+
+ PortalPlayerStatistics_t m_StatsThisLevel;
+ float m_fTimeLastNumSecondsUpdate;
+
+ int m_iNumCamerasDetatched;
+
+ QAngle m_qPrePortalledViewAngles;
+ bool m_bFixEyeAnglesFromPortalling;
+ VMatrix m_matLastPortalled;
+ CAI_Expresser *m_pExpresser;
+ string_t m_iszExpressionScene;
+ EHANDLE m_hExpressionSceneEnt;
+ float m_flExpressionLoopTime;
+
+
+
+ mutable Vector m_vWorldSpaceCenterHolder; //WorldSpaceCenter() returns a reference, need an actual value somewhere
+
+
+
+public:
+
+ CNetworkVar( bool, m_bPitchReorientation );
+ CNetworkHandle( CProp_Portal, m_hPortalEnvironment ); //if the player is in a portal environment, this is the associated portal
+ CNetworkHandle( CFunc_LiquidPortal, m_hSurroundingLiquidPortal ); //if the player is standing in a liquid portal, this will point to it
+
+ friend class CProp_Portal;
+
+
+#ifdef PORTAL_MP
+public:
+ virtual CBaseEntity* EntSelectSpawnPoint( void );
+ void PickTeam( void );
+#endif
+};
+
+inline CPortal_Player *ToPortalPlayer( CBaseEntity *pEntity )
+{
+ if ( !pEntity || !pEntity->IsPlayer() )
+ return NULL;
+
+ return dynamic_cast<CPortal_Player*>( pEntity );
+}
+
+inline CPortal_Player *GetPortalPlayer( int iPlayerIndex )
+{
+ return static_cast<CPortal_Player*>( UTIL_PlayerByIndex( iPlayerIndex ) );
+}
+
+#endif //PORTAL_PLAYER_H
diff --git a/game/server/portal/portal_radio.cpp b/game/server/portal/portal_radio.cpp
new file mode 100644
index 0000000..184aa54
--- /dev/null
+++ b/game/server/portal/portal_radio.cpp
@@ -0,0 +1,618 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//=====================================================================================//
+
+#include "cbase.h" // for pch
+#include "props.h"
+#include "filters.h"
+#include "achievementmgr.h"
+
+extern CAchievementMgr g_AchievementMgrPortal;
+
+#define RADIO_MODEL_NAME "models/props/radio_reference.mdl"
+//#define RADIO_DEBUG_SERVER
+
+class CDinosaurSignal : public CBaseEntity
+{
+public:
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+ DECLARE_CLASS( CDinosaurSignal, CBaseEntity );
+ void Spawn();
+ int UpdateTransmitState();
+#if RADIO_DEBUG_SERVER
+ int DrawDebugTextOverlays( void );
+#endif
+
+ CNetworkString( m_szSoundName, 128 );
+ CNetworkVar( float, m_flInnerRadius );
+ CNetworkVar( float, m_flOuterRadius );
+ CNetworkVar( int, m_nSignalID );
+};
+
+LINK_ENTITY_TO_CLASS( updateitem1, CDinosaurSignal );
+
+BEGIN_DATADESC( CDinosaurSignal )
+ DEFINE_AUTO_ARRAY( m_szSoundName, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_flOuterRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flInnerRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nSignalID, FIELD_INTEGER ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CDinosaurSignal, DT_DinosaurSignal )
+ SendPropString( SENDINFO(m_szSoundName) ),
+ SendPropFloat( SENDINFO(m_flOuterRadius) ),
+ SendPropFloat( SENDINFO(m_flInnerRadius) ),
+ SendPropInt( SENDINFO(m_nSignalID) ),
+END_SEND_TABLE()
+
+void CDinosaurSignal::Spawn()
+{
+ PrecacheScriptSound( m_szSoundName.Get() );
+ BaseClass::Spawn();
+ SetTransmitState( FL_EDICT_ALWAYS );
+}
+
+int CDinosaurSignal::UpdateTransmitState()
+{
+ // ALWAYS transmit to all clients.
+ return SetTransmitState( FL_EDICT_ALWAYS );
+}
+
+#if RADIO_DEBUG_SERVER
+int CDinosaurSignal::DrawDebugTextOverlays( void )
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ NDebugOverlay::Sphere( GetAbsOrigin(), GetAbsAngles(), m_flInnerRadius, 255, 0, 0, 64, false, 0.1f );
+ NDebugOverlay::Sphere( GetAbsOrigin(), GetAbsAngles(), m_flOuterRadius, 0, 255, 0, 64, false, 0.1f );
+ }
+ return text_offset;
+}
+#endif
+
+
+class CPortal_Dinosaur : public CPhysicsProp
+{
+public:
+ DECLARE_CLASS( CPortal_Dinosaur, CPhysicsProp );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Spawn();
+ virtual void Precache();
+ virtual QAngle PreferredCarryAngles( void ) { return QAngle( 0, 180, 0 ); }
+ virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
+ virtual void Activate();
+
+
+ CNetworkHandle( CDinosaurSignal, m_hDinosaur_Signal );
+ CNetworkVar( bool, m_bAlreadyDiscovered );
+};
+
+LINK_ENTITY_TO_CLASS( updateitem2, CPortal_Dinosaur );
+
+BEGIN_DATADESC( CPortal_Dinosaur )
+ DEFINE_FIELD( m_hDinosaur_Signal, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bAlreadyDiscovered, FIELD_BOOLEAN ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CPortal_Dinosaur, DT_PropDinosaur )
+ SendPropEHandle( SENDINFO( m_hDinosaur_Signal ) ),
+ SendPropBool( SENDINFO( m_bAlreadyDiscovered ) ),
+END_SEND_TABLE()
+
+void CPortal_Dinosaur::Precache()
+{
+ PrecacheModel( RADIO_MODEL_NAME );
+
+ PrecacheScriptSound( "Portal.room1_radio" );
+ PrecacheScriptSound( "UpdateItem.Static" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur01" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur02" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur03" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur04" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur05" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur06" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur07" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur08" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur09" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur10" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur11" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur12" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur13" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur14" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur15" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur16" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur17" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur18" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur19" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur20" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur21" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur22" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur23" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur24" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur25" );
+ PrecacheScriptSound( "UpdateItem.Dinosaur26" );
+
+ PrecacheScriptSound( "UpdateItem.Fizzle" );
+}
+
+
+void CPortal_Dinosaur::Spawn()
+{
+ Precache();
+ KeyValue( "model", RADIO_MODEL_NAME );
+ m_spawnflags |= SF_PHYSPROP_START_ASLEEP;
+ BaseClass::Spawn();
+}
+
+void CPortal_Dinosaur::Activate( void )
+{
+ // Find the current completion status of the dinosaurs
+ uint64 fStateFlags = 0;
+ CBaseAchievement *pTransmissionRecvd = dynamic_cast<CBaseAchievement *>(g_AchievementMgrPortal.GetAchievementByName("PORTAL_TRANSMISSION_RECEIVED"));
+ if ( pTransmissionRecvd )
+ {
+ fStateFlags = pTransmissionRecvd->GetComponentBits();
+ }
+
+ if ( m_hDinosaur_Signal != NULL )
+ {
+ uint64 nId = m_hDinosaur_Signal.Get()->m_nSignalID;
+ // See if we're already tripped
+ if ( fStateFlags & ((uint64)1<<nId) )
+ {
+ m_bAlreadyDiscovered = true;
+ }
+ }
+
+ BaseClass::Activate();
+}
+
+struct radiolocs
+{
+ const char *mapname;
+ const char *soundname;
+ int id;
+ float radiopos[3];
+ float radioang[3];
+ float soundpos[3];
+ float soundouterrad;
+ float soundinnerrad;
+};
+static const radiolocs s_radiolocs[] =
+{
+ {
+ "testchmb_a_00",
+ "UpdateItem.Dinosaur01",
+ 0,
+ { 0, 0, 0 },
+ { 0, 0, 0 },
+ { -506, -924, 161 },
+ 200,
+ 64
+ },
+ {
+ "testchmb_a_00",
+ "UpdateItem.Dinosaur02",
+ 1,
+ { -960, -634, 783 },
+ { 0, 90, 0 },
+ { -926.435, -256.323, 583 },
+ 200,
+ 64,
+ },
+ {
+ "testchmb_a_01",
+ "UpdateItem.Dinosaur03",
+ 2,
+ { 233, 393, 130 },
+ { 0, 225, 0 },
+ { 96, 160, -108 },
+ 224,
+ 128,
+ },
+ {
+ "testchmb_a_01",
+ "UpdateItem.Dinosaur04",
+ 3,
+ { -1439.89, 1076.04, 779.102 },
+ { 0, 270, 0 },
+ { -731, 735, 888 },
+ 400,
+ 64,
+ },
+ {
+ // new entry
+ "testchmb_a_02",
+ "UpdateItem.Dinosaur05",
+ 4,
+ { 2, 65, 390 },
+ { 0, 270, 0 },
+ { -864, 192, 64},
+ 192,
+ 96,
+ },
+ {
+ "testchmb_a_02",
+ "UpdateItem.Dinosaur06",
+ 21,
+ { 111, 832, 577 },
+ { 0, 0, 0 },
+ { 918, 831, 512},
+ 192,
+ 96,
+ },
+ {
+ "testchmb_a_03",
+ "UpdateItem.Dinosaur07",
+ 5,
+ { -53.2337, 78.181, 236 },
+ { 0, 225, 0 },
+ { 304, 0, -96 },
+ 256,
+ 128
+ },
+ // new entry
+ {
+ "testchmb_a_03",
+ "UpdateItem.Dinosaur08",
+ 6,
+ { 428.112, 0.22326, 1201 },
+ { 0, 180, 0 },
+ { -581.096, 193.694, 1351 },
+ 165,
+ 128
+ },
+ {
+ "testchmb_a_04",
+ "UpdateItem.Dinosaur09",
+ 7,
+ { 118, -56.6, -38.8 },
+ { 0, 180, 0 },
+ { -640, 256, 8 },
+ 512,
+ 128
+ },
+ // new entry
+ {
+ "testchmb_a_05",
+ "UpdateItem.Dinosaur10",
+ 8,
+ { 64, 144, 160 },
+ { 0, 270, 0 },
+ { 64, 740, 7 },
+ 350,
+ 128
+ },
+ {
+ "testchmb_a_06",
+ "UpdateItem.Dinosaur11",
+ 9,
+ { 529, 315, 320 },
+ { 0, 270, 0 },
+ { 608, 128, -184 },
+ 384,
+ 160
+ },
+ {
+ "testchmb_a_07",
+ "UpdateItem.Dinosaur12",
+ 10,
+ { 192, -1546, 1425 },
+ { 0, 113, 0 },
+ { 272, -496, 1328 },
+ 432,
+ 88
+ },
+ // new entry
+ {
+ "testchmb_a_07",
+ "UpdateItem.Dinosaur13",
+ 11,
+ { -144, -768, 256 },
+ { 0, 90, 0 },
+ { -192, -384, 176 },
+ 256,
+ 128
+ },
+ {
+ "testchmb_a_08",
+ "UpdateItem.Dinosaur14",
+ 12,
+ { 267, -378, 256 },
+ { 0, 90, 0 },
+ { -560, 96, 320 },
+ 288,
+ 128,
+ },
+ {
+ "testchmb_a_09",
+ "UpdateItem.Dinosaur15",
+ 13,
+ { 634, 1308, 256 },
+ { 0, 180, 0 },
+ { 386.699, 1792.43, 7},
+ 548,
+ 64
+ },
+ {
+ "testchmb_a_10",
+ "UpdateItem.Dinosaur16",
+ 14,
+ { -1420, -2752, 76 },
+ { 0, 0, 0 },
+ { -1968, -2880, -334 },
+ 448,
+ 196,
+ },
+ // new entry
+ {
+ "testchmb_a_10",
+ "UpdateItem.Dinosaur17",
+ 15,
+ { 112, 1392, -63 },
+ { 0, 260, 0 },
+ { -189, 1220, 65 },
+ 192,
+ 128,
+ },
+ {
+ "testchmb_a_11",
+ "UpdateItem.Dinosaur18",
+ 16,
+ {0,0,0},
+ {0,0,0},
+ {-512,644,64},
+ 192,
+ 96,
+ },
+ {
+ "testchmb_a_13",
+ "UpdateItem.Dinosaur19",
+ 17,
+ {955,931,-267},
+ {-90,0,0},
+ {1472,-191,-12},
+ 256,
+ 128,
+ },
+ {
+ "testchmb_a_14",
+ "UpdateItem.Dinosaur20",
+ 18,
+ {0,0,0},
+ {0,0,0},
+ {144,192,1288},
+ 807,
+ 128,
+ },
+ {
+ "testchmb_a_14",
+ "UpdateItem.Dinosaur21",
+ 22,
+ {1285, 1344, 1412},
+ {0,0,0},
+ {2712, 894, 1011},
+ 200,
+ 120,
+ },
+ {
+ "testchmb_a_14",
+ "UpdateItem.Dinosaur22",
+ 23,
+ {-952, 336, -256},
+ {0,0,0},
+ {-1144, -249, 3336},
+ 400,
+ 128,
+ },
+ {
+ "testchmb_a_15",
+ "UpdateItem.Dinosaur23",
+ 19,
+ {-1529,293,-283},
+ {0,90,0},
+ {761,443,810},
+ 256,
+ 128,
+ },
+ {
+ "escape_00",
+ "UpdateItem.Dinosaur24",
+ 24,
+ {192, -1344, -832},
+ {0, 135, 0},
+ {891, 322, -184},
+ 285,
+ 150,
+ },
+ {
+ "escape_01",
+ "UpdateItem.Dinosaur25",
+ 20,
+ {0,0,0},
+ {0,0,0},
+ {-624, 1440, -464},
+ 512,
+ 128,
+ },
+ {
+ "escape_02",
+ "UpdateItem.Dinosaur26",
+ 25,
+ {5504, 131, -1422},
+ {0, 90, 0},
+ {4218, 674, 8},
+ 300,
+ 100,
+ },
+};
+
+class CSpawnDinosaurHack : CAutoGameSystem
+{
+public:
+ virtual void LevelInitPreEntity();
+ virtual void LevelInitPostEntity();
+
+ CPortal_Dinosaur *SpawnDinosaur( radiolocs& loc );
+ CDinosaurSignal *SpawnSignal( radiolocs& loc );
+
+ void ApplyMapSpecificHacks();
+};
+
+static CSpawnDinosaurHack g_SpawnRadioHack;
+
+void CSpawnDinosaurHack::LevelInitPreEntity()
+{
+ UTIL_PrecacheOther( "updateitem2", RADIO_MODEL_NAME );
+
+ ApplyMapSpecificHacks();
+}
+
+// Spawn all the Dinosaurs and sstv images
+void CSpawnDinosaurHack::LevelInitPostEntity()
+{
+ if ( gpGlobals->eLoadType == MapLoad_LoadGame )
+ {
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "Not spawning any Dinosaurs: Detected a map load\n" );
+#endif
+
+ return;
+ }
+
+ IAchievement *pHeartbreaker = g_AchievementMgrPortal.GetAchievementByName("PORTAL_BEAT_GAME");
+ if ( pHeartbreaker == NULL || pHeartbreaker->IsAchieved() == false )
+ {
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "Not spawning any Dinosaurs: Player has not beat the game, or failed to get heartbreaker achievement from mgr\n" );
+#endif
+
+ return;
+ }
+
+ for ( int i = 0; i < ARRAYSIZE( s_radiolocs ); ++i )
+ {
+ radiolocs loc = s_radiolocs[i];
+ if ( V_strcmp( STRING(gpGlobals->mapname), loc.mapname ) == 0 )
+ {
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "Found Dinosaur and signal info for %s, spawning.\n", loc.mapname );
+ Msg( "Dinosaur pos: %f %f %f, ang: %f %f %f\n", loc.radiopos[0], loc.radiopos[1], loc.radiopos[2], loc.radioang[0], loc.radioang[1], loc.radioang[2] );
+ Msg( "Signal pos: %f %f %f, inner rad: %f, outter rad: %f\n", loc.soundpos[0], loc.soundpos[1], loc.soundpos[2], loc.soundinnerrad, loc.soundouterrad );
+#endif
+
+ CPortal_Dinosaur *pDinosaur = SpawnDinosaur( loc );
+ CDinosaurSignal *pSignal = SpawnSignal( loc );
+
+ Assert ( pDinosaur && pSignal );
+ if ( pDinosaur && pSignal )
+ {
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "SUCCESS: Spawned Dinosaur and signal and linked them.\n" );
+#endif
+ // OK, so these really could have been the same class... not worth changing it now though.
+ pDinosaur->m_hDinosaur_Signal.Set( pSignal );
+ pDinosaur->Activate();
+ }
+ }
+ }
+}
+
+CPortal_Dinosaur *CSpawnDinosaurHack::SpawnDinosaur( radiolocs& loc )
+{
+ Vector vSpawnPos ( loc.radiopos[0], loc.radiopos[1], loc.radiopos[2] );
+ QAngle vSpawnAng ( loc.radioang[0], loc.radioang[1], loc.radioang[2] );
+
+ // origin and angles of zero means skip this Dinosaur creation and look for an existing radio
+ if ( loc.radiopos[0] == 0 &&
+ loc.radiopos[1] == 0 &&
+ loc.radiopos[2] == 0 &&
+ loc.radioang[0] == 0 &&
+ loc.radioang[1] == 0 &&
+ loc.radioang[2] == 0 )
+ {
+
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "Dinosaur found with zero angles and origin. Replacing existing radio.\n" );
+#endif
+ // Find existing Dinosaur, kill it and spawn at its position
+ CPhysicsProp *pOldDinosaur = (CPhysicsProp*)gEntList.FindEntityByClassname( NULL, "prop_physics" );
+ while ( pOldDinosaur )
+ {
+ if ( V_strcmp( STRING( pOldDinosaur->GetModelName() ), RADIO_MODEL_NAME ) == 0 )
+ {
+ vSpawnPos = pOldDinosaur->GetAbsOrigin();
+ vSpawnAng = pOldDinosaur->GetAbsAngles();
+
+ UTIL_Remove( pOldDinosaur );
+
+#if defined ( RADIO_DEBUG_SERVER )
+ Msg( "Found Dinosaur exiting in level, replacing with %f, %f %f and %f %f %f.\n", XYZ(vSpawnPos), XYZ(vSpawnAng) );
+#endif
+ break;
+ }
+
+ pOldDinosaur = (CPhysicsProp*)gEntList.FindEntityByClassname( pOldDinosaur, "prop_physics" );
+ }
+ }
+
+ Assert( vSpawnPos != vec3_origin );
+
+ CPortal_Dinosaur *pDinosaur = (CPortal_Dinosaur*)CreateEntityByName( "updateitem2" );
+ Assert ( pDinosaur );
+ if ( pDinosaur )
+ {
+ pDinosaur->SetAbsOrigin( vSpawnPos );
+ pDinosaur->SetAbsAngles( vSpawnAng );
+ DispatchSpawn( pDinosaur );
+ }
+
+ return pDinosaur;
+}
+
+CDinosaurSignal *CSpawnDinosaurHack::SpawnSignal( radiolocs& loc )
+{
+ CDinosaurSignal *pSignal = (CDinosaurSignal*)CreateEntityByName( "updateitem1" );
+ Assert ( pSignal );
+ if ( pSignal )
+ {
+#if defined ( RADIO_DEBUG_SERVER )
+ if ( loc.soundinnerrad > loc.soundouterrad )
+ {
+ Assert( 0 );
+ Warning( "Dinosaur BUG: Inner radius is greater than outer radius. Will swap them.\n" );
+ swap( loc.soundinnerrad, loc.soundouterrad );
+ }
+#endif
+ pSignal->SetAbsOrigin( Vector( loc.soundpos[0], loc.soundpos[1], loc.soundpos[2] ) );
+ pSignal->m_flInnerRadius = loc.soundinnerrad;
+ pSignal->m_flOuterRadius = loc.soundouterrad;
+ V_strncpy( pSignal->m_szSoundName.GetForModify(), loc.soundname, 128 );
+ pSignal->m_nSignalID = loc.id;
+ DispatchSpawn( pSignal );
+ }
+
+ return pSignal;
+}
+
+
+void CSpawnDinosaurHack::ApplyMapSpecificHacks()
+{
+ if ( V_strcmp( STRING(gpGlobals->mapname), "testchmb_a_02" ) == 0 )
+ {
+ CBaseEntity *pFilter = CreateEntityByName( "filter_activator_name" );
+ Assert( pFilter );
+ if ( pFilter )
+ {
+ pFilter->KeyValue( "filtername", "box_2" );
+ pFilter->KeyValue( "targetname", "filter_weight_box" );
+ DispatchSpawn( pFilter );
+ }
+ }
+}
+
diff --git a/game/server/portal/prop_energy_ball.cpp b/game/server/portal/prop_energy_ball.cpp
new file mode 100644
index 0000000..3e6742b
--- /dev/null
+++ b/game/server/portal/prop_energy_ball.cpp
@@ -0,0 +1,620 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Defines a combine ball and a combine ball launcher which have certain properties
+// overwritten to make use of them in portal game play.
+//
+//=====================================================================================//
+
+#include "cbase.h" // for pch
+#include "prop_combine_ball.h" // for base class
+#include "te_effect_dispatch.h" // for the explosion/impact effects
+#include "prop_portal.h" // Special case code for passing through portals. We need the class definition.
+#include "soundenvelope.h"
+#include "physicsshadowclone.h"
+
+// resource file names
+#define IMPACT_DECAL_NAME "decals/smscorch1model"
+
+// context think
+#define UPDATE_THINK_CONTEXT "UpdateThinkContext"
+
+class CPropEnergyBall : public CPropCombineBall
+{
+public:
+ DECLARE_CLASS( CPropEnergyBall, CPropCombineBall );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual void Precache();
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds( void );
+ virtual void Spawn();
+ virtual void Activate( void );
+
+ // Overload for unlimited bounces and predictable movement
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+ // Overload for less sound, no shake.
+ virtual void ExplodeThink( void );
+ // Update in a time till death update
+ virtual void Think ( void );
+ virtual void EndTouch( CBaseEntity *pOther );
+ virtual void StartTouch( CBaseEntity *pOther );
+ virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
+
+ CHandle<CProp_Portal> m_hTouchedPortal; // Pointer to the portal we are touched most recently
+ bool m_bTouchingPortal1; // Are we touching portal 1
+ bool m_bTouchingPortal2; // Are we touching portal 2
+
+ // Remember the last known direction of travel, incase our velocity is cleared.
+ Vector m_vLastKnownDirection;
+
+ // After portal teleports, we force the life to be at least this number.
+ float m_fMinLifeAfterPortal;
+
+ CNetworkVar( bool, m_bIsInfiniteLife );
+ CNetworkVar( float, m_fTimeTillDeath );
+
+ CSoundPatch *m_pAmbientSound;
+
+};
+
+LINK_ENTITY_TO_CLASS( prop_energy_ball, CPropEnergyBall );
+
+
+BEGIN_DATADESC( CPropEnergyBall )
+
+ DEFINE_FIELD( m_hTouchedPortal, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bTouchingPortal1, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bTouchingPortal2, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vLastKnownDirection, FIELD_VECTOR ),
+ DEFINE_FIELD( m_fMinLifeAfterPortal, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bIsInfiniteLife, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fTimeTillDeath, FIELD_FLOAT ),
+
+ DEFINE_SOUNDPATCH( m_pAmbientSound ),
+
+ DEFINE_THINKFUNC( Think ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CPropEnergyBall, DT_PropEnergyBall )
+
+ SendPropBool( SENDINFO( m_bIsInfiniteLife ) ),
+ SendPropFloat ( SENDINFO( m_fTimeTillDeath ) ),
+
+END_SEND_TABLE()
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+// Input : -
+//-----------------------------------------------------------------------------
+void CPropEnergyBall::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "EnergyBall.Explosion" );
+ PrecacheScriptSound( "EnergyBall.Launch" );
+ PrecacheScriptSound( "EnergyBall.Impact" );
+ PrecacheScriptSound( "EnergyBall.AmbientLoop" );
+ UTIL_PrecacheDecal( IMPACT_DECAL_NAME, false );
+
+}
+
+
+void CPropEnergyBall::CreateSounds()
+{
+ if (!m_pAmbientSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pAmbientSound = controller.SoundCreate( filter, entindex(), "EnergyBall.AmbientLoop" );
+ controller.Play( m_pAmbientSound, 1.0, 100 );
+ }
+}
+
+
+void CPropEnergyBall::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pAmbientSound );
+ m_pAmbientSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+void CPropEnergyBall::Spawn()
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ m_bTouchingPortal1 = false;
+ m_bTouchingPortal2 = false;
+ m_bIsInfiniteLife = false;
+ m_fTimeTillDeath = -1;
+ m_fMinLifeAfterPortal = 5;
+ // Init last known direction to our initial direction
+ GetVelocity( &m_vLastKnownDirection, NULL );
+}
+
+void CPropEnergyBall::Activate( void )
+{
+ BaseClass::Activate();
+
+ CreateSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Keep a constant velocity despite collisions, make impact sounds and effects
+//-----------------------------------------------------------------------------
+void CPropEnergyBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ // Skip combine ball's collision, but do everything below it.
+
+ BaseClass::BaseClass::VPhysicsCollision( index, pEvent );
+
+ Vector preVelocity = pEvent->preVelocity[index];
+// float flSpeed = VectorNormalize( preVelocity );
+
+ // It's ok to change direction, but maintain speed = m_flSpeed.
+ Vector vecFinalVelocity = pEvent->postVelocity[index];
+ VectorNormalize( vecFinalVelocity );
+
+ if ( m_bTouchingPortal2 || m_bTouchingPortal1 )
+ {
+ AssertMsg ( m_hTouchedPortal.Get(), "Touching a portal, but recorded an invalid handle." );
+ }
+
+ // Used for deciding if we play our impact effects/sounds
+ bool bIsEnteringPortalAndLockingAxisForward = false;
+
+ // Fixed bounce axis when in a portal environment
+ if ( (m_bTouchingPortal2 || m_bTouchingPortal1) && m_hTouchedPortal.Get() )
+ {
+ // Force our velocity to be either towards or away from the portal, no bouncing at odd angles allowed
+ CProp_Portal* pPortal = m_hTouchedPortal.Get();
+
+ // Only lock to the portal's forward axis if we're in it's world bounds
+ // We use a tolerance of four, because the render bounds thickness for a portal is 4, and this function
+ // intersects with a plane.
+ bool bHitPortal = UTIL_IsBoxIntersectingPortal( GetAbsOrigin(), WorldAlignSize(), pPortal, 4.0f );
+
+ // We definitely hit a portal
+ if ( bHitPortal && pPortal && pPortal->IsActivedAndLinked() )
+ {
+ Vector vecTouchedPortalFace;
+ pPortal->GetVectors( &vecTouchedPortalFace, NULL, NULL );
+ vecTouchedPortalFace.NormalizeInPlace();
+ float fDot = vecTouchedPortalFace.Dot( vecFinalVelocity );
+
+ // closer to 'towards' the portal, force it to go that direction
+ if ( fDot < 0 )
+ {
+ vecFinalVelocity = -vecTouchedPortalFace;
+
+ // Since we're going 'through', don't do surfaceprop based collision effects
+ // because the it will look like we didn't hit anything.
+ pEvent->surfaceProps[0] = pEvent->surfaceProps[1] = physprops->GetSurfaceIndex( "default" );
+ bIsEnteringPortalAndLockingAxisForward = true;
+ }
+ else // Closer to 'away from' the portal. Force the energy ball to go that direction
+ {
+ vecFinalVelocity = vecTouchedPortalFace;
+ }
+ }
+ }
+
+
+ // Plant a decal on any solid brushes we hit
+ if ( !bIsEnteringPortalAndLockingAxisForward )
+ {
+ trace_t tr;
+ UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + 60*preVelocity, MASK_SHOT,
+ this, COLLISION_GROUP_NONE, &tr);
+
+ // Only place decals and draw effects if we hit something valid
+ if ( tr.m_pEnt )
+ {
+
+ // Cball impact effect (using same trace as the decal placement above)
+ CEffectData data;
+ data.m_flRadius = 16;
+ data.m_vNormal = tr.plane.normal;
+ data.m_vOrigin = tr.endpos + tr.plane.normal * 1.0f;
+
+
+ DispatchEffect( "cball_bounce", data );
+
+ if ( tr.m_pEnt )
+ {
+ UTIL_DecalTrace( &tr, "EnergyBall.Impact" );
+ }
+ }
+
+ EmitSound( "EnergyBall.Impact" );
+ }
+
+ // Record our direction so our fixed direction hacks know we have changed direction immediately
+ m_vLastKnownDirection = vecFinalVelocity;
+
+ // Scale new velocity to our fixed speed
+ vecFinalVelocity *= GetSpeed();
+
+ // Try to update the velocity now, however I'm told this rarely works.
+ // We will spam updates in our think function to help get us in the direction we want to go.
+ PhysCallbackSetVelocity( pEvent->pObjects[index], vecFinalVelocity );
+}
+
+void CPropEnergyBall::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
+{
+ // On teleport, we record a pointer to the portal we are arriving at
+ if ( eventType == NOTIFY_EVENT_TELEPORT )
+ {
+ CProp_Portal *pEnteredPortal = dynamic_cast<CProp_Portal*>( pNotify );
+ if( pEnteredPortal )
+ {
+ m_vLastKnownDirection = pEnteredPortal->m_matrixThisToLinked.ApplyRotation( m_vLastKnownDirection );
+ m_vLastKnownDirection.NormalizeInPlace();
+
+ IPhysicsObject *pPhysObject = VPhysicsGetObject();
+ if( pPhysObject )
+ {
+ Vector vNewVelocity = m_vLastKnownDirection * GetSpeed();
+ pPhysObject->SetVelocityInstantaneous( &vNewVelocity, NULL );
+ }
+
+ // Record the new portal for the purposes of locking our movement
+ m_hTouchedPortal = pEnteredPortal->m_hLinkedPortal;
+ }
+ else
+ {
+ m_hTouchedPortal = NULL;
+ }
+
+ // If an energy ball passes a portal (teleports), add a make sure its life is >= sk_energy_ball_min_life_after_portal
+ float fCurTimeTillDeath = GetNextThink( "ExplodeTimerContext" );
+ // If we are set to die, then refresh that time if it is below a set threshold
+ if ( fCurTimeTillDeath > 0 )
+ {
+ float fTimeLeft = fCurTimeTillDeath - gpGlobals->curtime;
+ float fMinLife = m_fMinLifeAfterPortal;
+ float fTimeToDie = (fTimeLeft > fMinLife) ? (fTimeLeft) : (fMinLife);
+ SetContextThink( &CPropCombineBall::ExplodeThink, gpGlobals->curtime + fTimeToDie, "ExplodeTimerContext" );
+ }
+ }
+
+ //BaseClass::NotifySystemEvent( pNotify, eventType, params );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send down the time till death to the client code to help indicate when the ball will detonate
+// Input : -
+//-----------------------------------------------------------------------------
+void CPropEnergyBall::Think()
+{
+ // Finite life energy balls send the time till death down to the client for display purposes
+ if ( !m_bIsInfiniteLife )
+ {
+ m_fTimeTillDeath = GetNextThink( "ExplodeTimerContext" ) - gpGlobals->curtime;
+ SetNextThink ( gpGlobals->curtime + 0.5f );
+ }
+
+ // Force our movement to be at desired speed
+ IPhysicsObject* pMyObject = VPhysicsGetObject();
+ if ( pMyObject )
+ {
+ // get our current speed
+ Vector vCurVelocity, vNewVelocity;
+ pMyObject->GetVelocity( &vCurVelocity, NULL );
+ float fCurSpeed = vCurVelocity.Length();
+
+ if ( fCurSpeed < GetSpeed() )
+ {
+ m_vLastKnownDirection.NormalizeInPlace();
+ vNewVelocity = m_vLastKnownDirection * GetSpeed();
+ pMyObject->SetVelocityInstantaneous( &vNewVelocity, NULL );
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Make a sound/effect for the removal of the energy ball, and switch to the cleanup think
+// Input : -
+//-----------------------------------------------------------------------------
+void CPropEnergyBall::ExplodeThink( )
+{
+ // Tell the respawner to make a new one
+ if ( GetSpawner() )
+ {
+ GetSpawner()->RespawnBallPostExplosion();
+ }
+
+ //Destruction effect
+ CBroadcastRecipientFilter filter2;
+ CEffectData data;
+ data.m_vOrigin = GetAbsOrigin();
+ DispatchEffect( "ManhackSparks", data );
+ EmitSound( "EnergyBall.Explosion" );
+
+ // Turn us off and wait because we need our trails to finish up properly
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ SetEmitState( false );
+
+ SetContextThink( &CPropCombineBall::SUB_Remove, gpGlobals->curtime + 0.5f, "RemoveContext" );
+ StopLoopingSounds();
+}
+
+void CPropEnergyBall::StartTouch( CBaseEntity *pOther )
+{
+ Assert( pOther );
+
+ if( CPhysicsShadowClone::IsShadowClone( pOther ) )
+ {
+ CBaseEntity *pCloned = ((CPhysicsShadowClone *)pOther)->GetClonedEntity();
+ if( pCloned )
+ pOther = pCloned;
+ }
+
+ // Kill the player on hit.
+ if ( pOther->IsPlayer() )
+ {
+ CTakeDamageInfo info( this, GetOwnerEntity(), GetAbsVelocity(), GetAbsOrigin(), 1500.0f, DMG_DISSOLVE );
+ pOther->OnTakeDamage( info );
+
+ // Destruct when we hit the player
+ SetContextThink( &CPropCombineBall::ExplodeThink, gpGlobals->curtime, "ExplodeTimerContext" );
+ }
+
+ CProp_Portal* pPortal = dynamic_cast<CProp_Portal*>(pOther);
+ // If toucher is a prop portal
+ if ( pPortal )
+ {
+ // Record the touched portal for locking collision movements.
+ // The forward direction we want to follow is the forward vector of the portal we've touched most recently
+ m_hTouchedPortal = pPortal;
+
+ // record that we touched this portal
+ if ( pPortal->m_bIsPortal2 == false )
+ {
+ m_bTouchingPortal1 = true;
+ }
+ else //if ( pPortal->m_bIsPortal2 == true )
+ {
+ m_bTouchingPortal2 = true;
+ }
+ }
+
+ BaseClass::StartTouch( pOther );
+}
+
+void CPropEnergyBall::EndTouch( CBaseEntity *pOther )
+{
+ CProp_Portal* pPortal = dynamic_cast<CProp_Portal*>(pOther);
+
+ if ( pPortal )
+ {
+ // We are no longer touching this portal
+ if ( pPortal->m_bIsPortal2 == false )
+ {
+ m_bTouchingPortal1 = false;
+ }
+ else //if ( pPortal->m_bIsPortal2 == true )
+ {
+ m_bTouchingPortal2 = false;
+ }
+ }
+
+ BaseClass::EndTouch( pOther );
+
+}
+
+class CEnergyBallLauncher : public CPointCombineBallLauncher
+{
+public:
+ DECLARE_CLASS( CEnergyBallLauncher, CPointCombineBallLauncher );
+ DECLARE_DATADESC();
+
+ virtual void SpawnBall();
+ virtual void Precache();
+ virtual void Spawn();
+
+private:
+ float m_fBallLifetime;
+ float m_fMinBallLifeAfterPortal;
+
+ COutputEvent m_OnPostSpawnBall;
+
+
+};
+
+LINK_ENTITY_TO_CLASS( point_energy_ball_launcher, CEnergyBallLauncher );
+
+BEGIN_DATADESC( CEnergyBallLauncher )
+
+ DEFINE_KEYFIELD( m_fBallLifetime, FIELD_FLOAT, "BallLifetime" ),
+ DEFINE_KEYFIELD( m_fMinBallLifeAfterPortal, FIELD_FLOAT, "MinLifeAfterPortal" ),
+
+ DEFINE_OUTPUT ( m_OnPostSpawnBall, "OnPostSpawnBall" ),
+
+END_DATADESC()
+
+void CEnergyBallLauncher::Precache()
+{
+ BaseClass::Precache();
+
+ UTIL_PrecacheDecal( IMPACT_DECAL_NAME, false );
+}
+
+void CEnergyBallLauncher::Spawn()
+{
+ Precache();
+
+ BaseClass::Spawn();
+}
+
+
+void CEnergyBallLauncher::SpawnBall()
+{
+ CPropEnergyBall *pBall = static_cast<CPropEnergyBall*>( CreateEntityByName( "prop_energy_ball" ) );
+
+ if ( pBall == NULL )
+ return;
+
+ pBall->SetRadius( m_flBallRadius );
+ Vector vecAbsOrigin = GetAbsOrigin();
+ Vector zaxis;
+
+ pBall->SetAbsOrigin( vecAbsOrigin );
+ pBall->SetSpawner( this );
+
+ pBall->SetSpeed( m_flMaxSpeed );
+ float flSpeed = m_flMaxSpeed;
+
+ Vector vDirection;
+ QAngle qAngle = GetAbsAngles();
+ AngleVectors( qAngle, &vDirection, NULL, NULL );
+
+ vDirection *= flSpeed;
+ pBall->SetAbsVelocity( vDirection );
+
+ DispatchSpawn(pBall);
+ pBall->Activate();
+ pBall->SetState( CPropCombineBall::STATE_LAUNCHED );
+ pBall->SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+ pBall->m_fMinLifeAfterPortal = m_fMinBallLifeAfterPortal;
+
+ // Additional setup of the physics object for energy ball uses
+ IPhysicsObject *pBallObj = pBall->VPhysicsGetObject();
+
+ if ( pBallObj )
+ {
+ // Make sure we dont use air drag
+ pBallObj->EnableDrag( false );
+
+ // Remove damping
+ float speed, rot;
+ speed = rot = 0.0f;
+ pBallObj->SetDamping( &speed, &rot );
+
+ // HUGE rotational inertia, don't allow the ball to have any spin
+ Vector vInertia( 1e30, 1e30, 1e30 );
+ pBallObj->SetInertia( vInertia );
+
+ // Low mass to let it bounce off of obstructions for certain puzzles.
+ pBallObj->SetMass( 1.0f );
+ }
+
+ // Only expire if the lifetme field is positive
+ if ( m_fBallLifetime >=0 )
+ {
+ pBall->StartLifetime( m_fBallLifetime );
+ pBall->m_bIsInfiniteLife = false;
+ }
+ else
+ {
+ pBall->m_bIsInfiniteLife = true;
+ }
+
+ // Think function, used to update time till death and avoid sleeping
+ pBall->SetNextThink ( gpGlobals->curtime + 0.1f );
+
+ EmitSound( "EnergyBall.Launch" );
+
+ m_OnPostSpawnBall.FireOutput( this, this );
+}
+
+
+
+
+
+
+
+
+
+
+static void fire_energy_ball_f( void )
+{
+ if( sv_cheats->GetBool() == false ) //heavy handed version since setting the concommand with FCVAR_CHEATS isn't working like I thought
+ return;
+
+ CBasePlayer *pPlayer = (CBasePlayer *)UTIL_GetCommandClient();
+
+ Vector ptEyes, vForward;
+ ptEyes = pPlayer->EyePosition();
+ pPlayer->EyeVectors( &vForward );
+
+
+
+
+ {
+ CPropEnergyBall *pBall = static_cast<CPropEnergyBall*>( CreateEntityByName( "prop_energy_ball" ) );
+
+ if ( pBall == NULL )
+ return;
+
+ pBall->SetRadius( 12.0f );
+
+ pBall->SetAbsOrigin( ptEyes + (vForward * 50.0f) );
+ pBall->SetSpawner( NULL );
+
+ pBall->SetSpeed( 400.0f );
+
+
+ pBall->SetAbsVelocity( vForward * 400.0f );
+
+ DispatchSpawn(pBall);
+ pBall->Activate();
+ pBall->SetState( CPropCombineBall::STATE_LAUNCHED );
+ pBall->SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+ pBall->m_fMinLifeAfterPortal = 5.0f;
+
+ // Additional setup of the physics object for energy ball uses
+ IPhysicsObject *pBallObj = pBall->VPhysicsGetObject();
+
+ if ( pBallObj )
+ {
+ // Make sure we dont use air drag
+ pBallObj->EnableDrag( false );
+
+ // Remove damping
+ float speed, rot;
+ speed = rot = 0.0f;
+ pBallObj->SetDamping( &speed, &rot );
+
+ // HUGE rotational inertia, don't allow the ball to have any spin
+ Vector vInertia( 1e30, 1e30, 1e30 );
+ pBallObj->SetInertia( vInertia );
+
+ // Low mass to let it bounce off of obstructions for certain puzzles.
+ pBallObj->SetMass( 1.0f );
+ }
+
+ pBall->StartLifetime( 10.0f );
+ pBall->m_bIsInfiniteLife = false;
+
+ // Think function, used to update time till death and avoid sleeping
+ pBall->SetNextThink ( gpGlobals->curtime + 0.1f );
+
+ }
+
+
+}
+
+ConCommand fire_energy_ball( "fire_energy_ball", fire_energy_ball_f, "Fires a test energy ball out of your face", FCVAR_CHEAT ); \ No newline at end of file
diff --git a/game/server/portal/prop_glados_core.cpp b/game/server/portal/prop_glados_core.cpp
new file mode 100644
index 0000000..2ba320f
--- /dev/null
+++ b/game/server/portal/prop_glados_core.cpp
@@ -0,0 +1,480 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Core of the GlaDOS computer.
+//
+//=====================================================================================//
+
+#include "cbase.h"
+#include "baseentity.h"
+#include "te_effect_dispatch.h" // Sprite effect
+#include "props.h" // CPhysicsProp base class
+#include "saverestore_utlvector.h"
+
+#define GLADOS_CORE_MODEL_NAME "models/props_bts/glados_ball_reference.mdl"
+
+static const char *s_pAnimateThinkContext = "Animate";
+
+#define DEFAULT_LOOK_ANINAME "look_01"
+#define CURIOUS_LOOK_ANINAME "look_02"
+#define AGGRESSIVE_LOOK_ANINAME "look_03"
+#define CRAZY_LOOK_ANINAME "look_04"
+
+#define DEFAULT_SKIN 0
+#define CURIOUS_SKIN 1
+#define AGGRESSIVE_SKIN 2
+#define CRAZY_SKIN 3
+
+class CPropGladosCore : public CPhysicsProp
+{
+public:
+ DECLARE_CLASS( CPropGladosCore, CPhysicsProp );
+ DECLARE_DATADESC();
+
+ CPropGladosCore();
+ ~CPropGladosCore();
+
+ typedef enum
+ {
+ CORETYPE_CURIOUS,
+ CORETYPE_AGGRESSIVE,
+ CORETYPE_CRAZY,
+ CORETYPE_NONE,
+ CORETYPE_TOTAL,
+
+ } CORETYPE;
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+
+ virtual QAngle PreferredCarryAngles( void ) { return QAngle( 180, -90, 180 ); }
+ virtual bool HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return true; }
+
+ void InputPanic( inputdata_t &inputdata );
+ void InputStartTalking( inputdata_t &inputdata );
+
+ void StartPanic ( void );
+ void StartTalking ( float flDelay );
+
+ void TalkingThink ( void );
+ void PanicThink ( void );
+ void AnimateThink ( void );
+
+ void SetupVOList ( void );
+
+ void OnPhysGunPickup( CBasePlayer* pPhysGunUser, PhysGunPickup_t reason );
+
+private:
+ int m_iTotalLines;
+ int m_iEyeballAttachment;
+ float m_flBetweenVOPadding; // Spacing (in seconds) between VOs
+ bool m_bFirstPickup;
+
+ // Names of sound scripts for this core's personality
+ CUtlVector<string_t> m_speechEvents;
+ int m_iSpeechIter;
+
+ string_t m_iszPanicSoundScriptName;
+ string_t m_iszDeathSoundScriptName;
+ string_t m_iszLookAnimationName; // Different animations for each personality
+
+ CORETYPE m_iCoreType;
+};
+
+LINK_ENTITY_TO_CLASS( prop_glados_core, CPropGladosCore );
+
+//-----------------------------------------------------------------------------
+// Save/load
+//-----------------------------------------------------------------------------
+BEGIN_DATADESC( CPropGladosCore )
+
+ DEFINE_FIELD( m_iEyeballAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iTotalLines, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iSpeechIter, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iszDeathSoundScriptName, FIELD_STRING ),
+ DEFINE_FIELD( m_iszPanicSoundScriptName, FIELD_STRING ),
+ DEFINE_FIELD( m_iszLookAnimationName, FIELD_STRING ),
+ DEFINE_UTLVECTOR( m_speechEvents, FIELD_STRING ),
+ DEFINE_FIELD( m_bFirstPickup, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_iCoreType, FIELD_INTEGER, "CoreType" ),
+ DEFINE_KEYFIELD( m_flBetweenVOPadding, FIELD_FLOAT, "DelayBetweenLines" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Panic", InputPanic ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartTalking", InputStartTalking ),
+
+ DEFINE_THINKFUNC( TalkingThink ),
+ DEFINE_THINKFUNC( PanicThink ),
+ DEFINE_THINKFUNC( AnimateThink ),
+
+END_DATADESC()
+
+CPropGladosCore::CPropGladosCore()
+{
+ m_iTotalLines = m_iSpeechIter = 0;
+ m_iszLookAnimationName = m_iszPanicSoundScriptName = m_iszDeathSoundScriptName = NULL_STRING;
+ m_flBetweenVOPadding = 2.5f;
+ m_bFirstPickup = true;
+}
+
+CPropGladosCore::~CPropGladosCore()
+{
+ m_speechEvents.Purge();
+}
+
+void CPropGladosCore::Spawn( void )
+{
+ SetupVOList();
+
+ Precache();
+ KeyValue( "model", GLADOS_CORE_MODEL_NAME );
+ BaseClass::Spawn();
+
+ //Default to 'dropped' animation
+ ResetSequence(LookupSequence("drop"));
+ SetCycle( 1.0f );
+
+ DisableAutoFade();
+ m_iEyeballAttachment = LookupAttachment( "eyeball" );
+
+ SetContextThink( &CPropGladosCore::AnimateThink, gpGlobals->curtime + 0.1f, s_pAnimateThinkContext );
+}
+
+void CPropGladosCore::Precache( void )
+{
+ BaseClass::Precache();
+
+ // Personality VOs -- Curiosity
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_1" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_2" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_3" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_4" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_5" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_6" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_7" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_8" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_9" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_10" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_11" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_12" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_13" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_15" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_16" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_17" );
+ PrecacheScriptSound ( "Portal.Glados_core.Curiosity_18" );
+
+ // Aggressive
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_00" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_01" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_02" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_03" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_04" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_05" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_06" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_07" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_08" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_09" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_10" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_11" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_12" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_13" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_14" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_15" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_16" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_17" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_18" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_19" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_20" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_21" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_panic_01" );
+ PrecacheScriptSound ( "Portal.Glados_core.Aggressive_panic_02" );
+
+ // Crazy
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_01" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_02" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_03" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_04" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_05" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_06" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_07" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_08" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_09" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_10" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_11" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_12" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_13" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_14" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_15" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_16" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_17" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_18" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_19" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_20" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_21" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_22" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_23" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_24" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_25" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_26" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_27" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_28" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_29" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_30" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_31" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_32" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_33" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_34" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_35" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_36" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_37" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_38" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_39" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_40" );
+ PrecacheScriptSound ( "Portal.Glados_core.Crazy_41" );
+
+ PrecacheModel( GLADOS_CORE_MODEL_NAME );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Switch to panic think, play panic vo and animations
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CPropGladosCore::InputPanic( inputdata_t &inputdata )
+{
+ StartPanic();
+}
+
+void CPropGladosCore::StartPanic( void )
+{
+ ResetSequence( LookupSequence( STRING(m_iszLookAnimationName) ) );
+ SetThink( &CPropGladosCore::PanicThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play panic vo and animations, then return to talking
+// Output :
+//-----------------------------------------------------------------------------
+void CPropGladosCore::PanicThink ( void )
+{
+ if ( m_speechEvents.Count() <= 0 || !m_speechEvents.IsValidIndex( m_iSpeechIter ) || m_iszPanicSoundScriptName == NULL_STRING )
+ {
+ SetThink ( NULL );
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ StopSound( m_speechEvents[m_iSpeechIter].ToCStr() );
+ EmitSound( m_iszPanicSoundScriptName.ToCStr() );
+ float flCurDuration = GetSoundDuration( m_iszPanicSoundScriptName.ToCStr(), GLADOS_CORE_MODEL_NAME );
+
+ SetThink( &CPropGladosCore::TalkingThink );
+ SetNextThink( gpGlobals->curtime + m_flBetweenVOPadding + flCurDuration );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start playing personality VO list
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CPropGladosCore::InputStartTalking ( inputdata_t &inputdata )
+{
+ StartTalking( 0.0f );
+}
+
+void CPropGladosCore::StartTalking( float flDelay )
+{
+ if ( m_speechEvents.IsValidIndex( m_iSpeechIter ) && m_speechEvents.Count() > 0 )
+ {
+ StopSound( m_speechEvents[m_iSpeechIter].ToCStr() );
+ }
+
+ m_iSpeechIter = 0;
+ SetThink( &CPropGladosCore::TalkingThink );
+ SetNextThink( gpGlobals->curtime + m_flBetweenVOPadding + flDelay );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start playing personality VO list
+//-----------------------------------------------------------------------------
+void CPropGladosCore::TalkingThink( void )
+{
+ if ( m_speechEvents.Count() <= 0 || !m_speechEvents.IsValidIndex( m_iSpeechIter ) )
+ {
+ SetThink ( NULL );
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ // Loop the 'look around' animation after the first line.
+ int iCurSequence = GetSequence();
+ int iLookSequence = LookupSequence( STRING(m_iszLookAnimationName) );
+ if ( iCurSequence != iLookSequence && m_iSpeechIter > 0 )
+ {
+ ResetSequence( iLookSequence );
+ }
+
+ int iPrevIter = m_iSpeechIter-1;
+ if ( iPrevIter < 0 )
+ iPrevIter = 0;
+
+ StopSound( m_speechEvents[iPrevIter].ToCStr() );
+
+ float flCurDuration = GetSoundDuration( m_speechEvents[m_iSpeechIter].ToCStr(), GLADOS_CORE_MODEL_NAME );
+
+ EmitSound( m_speechEvents[m_iSpeechIter].ToCStr() );
+ SetNextThink( gpGlobals->curtime + m_flBetweenVOPadding + flCurDuration );
+
+ // wrap if we hit the end of the list
+ m_iSpeechIter = (m_iSpeechIter+1)%m_speechEvents.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+void CPropGladosCore::AnimateThink()
+{
+ StudioFrameAdvance();
+ SetContextThink( &CPropGladosCore::AnimateThink, gpGlobals->curtime + 0.1f, s_pAnimateThinkContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup list of lines based on core personality
+//-----------------------------------------------------------------------------
+void CPropGladosCore::SetupVOList( void )
+{
+ m_speechEvents.RemoveAll();
+
+ switch ( m_iCoreType )
+ {
+ case CORETYPE_CURIOUS:
+ {
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_1" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_2" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_3" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_4" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_5" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_6" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_7" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_8" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_9" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_10" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_11" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_12" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_13" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_16" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Curiosity_17" ) );
+ m_iszPanicSoundScriptName = AllocPooledString( "Portal.Glados_core.Curiosity_15" );
+ m_iszLookAnimationName = AllocPooledString( CURIOUS_LOOK_ANINAME );
+ m_nSkin = CURIOUS_SKIN;
+
+ }
+ break;
+ case CORETYPE_AGGRESSIVE:
+ {
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_01" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_02" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_03" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_04" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_05" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_06" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_07" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_08" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_09" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_10" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_11" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_12" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_13" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_14" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_15" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_16" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_17" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_18" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_19" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_20" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Aggressive_21" ) );
+ m_iszPanicSoundScriptName = AllocPooledString( "Portal.Glados_core.Aggressive_panic_01" );
+ m_iszLookAnimationName = AllocPooledString( AGGRESSIVE_LOOK_ANINAME );
+ m_nSkin = AGGRESSIVE_SKIN;
+ }
+ break;
+ case CORETYPE_CRAZY:
+ {
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_01" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_02" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_03" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_04" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_05" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_06" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_07" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_08" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_09" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_10" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_11" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_12" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_13" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_14" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_15" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_16" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_17" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_18" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_19" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_20" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_21" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_22" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_23" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_24" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_25" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_26" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_27" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_28" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_29" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_30" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_31" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_32" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_33" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_34" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_35" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_36" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_37" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_38" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_39" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_40" ) );
+ m_speechEvents.AddToTail( AllocPooledString( "Portal.Glados_core.Crazy_41" ) );
+ m_iszLookAnimationName = AllocPooledString( CRAZY_LOOK_ANINAME );
+ m_nSkin = CRAZY_SKIN;
+ }
+ break;
+ default:
+ {
+ m_iszLookAnimationName = AllocPooledString( DEFAULT_LOOK_ANINAME );
+ m_nSkin = DEFAULT_SKIN;
+ }
+ break;
+ };
+
+ m_iszDeathSoundScriptName = AllocPooledString( "Portal.Glados_core.Death" );
+ m_iTotalLines = m_speechEvents.Count();
+ m_iSpeechIter = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cores play a special animation when picked up and dropped
+// Input : pPhysGunUser - player picking up object
+// reason - type of pickup
+//-----------------------------------------------------------------------------
+void CPropGladosCore::OnPhysGunPickup( CBasePlayer* pPhysGunUser, PhysGunPickup_t reason )
+{
+ if ( m_bFirstPickup )
+ {
+ float flTalkingDelay = (CORETYPE_CURIOUS == m_iCoreType) ? (2.0f) : (0.0f);
+ StartTalking ( flTalkingDelay );
+ }
+
+ m_bFirstPickup = false;
+ ResetSequence(LookupSequence("turn"));
+
+ // +use always enables motion on these props
+ EnableMotion();
+
+ BaseClass::OnPhysGunPickup ( pPhysGunUser, reason );
+} \ No newline at end of file
diff --git a/game/server/portal/prop_portal.cpp b/game/server/portal/prop_portal.cpp
new file mode 100644
index 0000000..aaa5780
--- /dev/null
+++ b/game/server/portal/prop_portal.cpp
@@ -0,0 +1,2315 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#include "cbase.h"
+#include "prop_portal.h"
+#include "portal_player.h"
+#include "portal/weapon_physcannon.h"
+#include "physics_npc_solver.h"
+#include "envmicrophone.h"
+#include "env_speaker.h"
+#include "func_portal_detector.h"
+#include "model_types.h"
+#include "te_effect_dispatch.h"
+#include "collisionutils.h"
+#include "physobj.h"
+#include "world.h"
+#include "hierarchy.h"
+#include "physics_saverestore.h"
+#include "PhysicsCloneArea.h"
+#include "portal_gamestats.h"
+#include "prop_portal_shared.h"
+#include "weapon_portalgun.h"
+#include "portal_placement.h"
+#include "physicsshadowclone.h"
+#include "particle_parse.h"
+#include "rumble_shared.h"
+#include "func_portal_orientation.h"
+#include "env_debughistory.h"
+#include "tier1/callqueue.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+#define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY 50.0f
+#define MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY 225.0f
+#define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER 300.0f
+#define MAXIMUM_PORTAL_EXIT_VELOCITY 1000.0f
+
+CCallQueue *GetPortalCallQueue();
+
+
+ConVar sv_portal_debug_touch("sv_portal_debug_touch", "0", FCVAR_REPLICATED );
+ConVar sv_portal_placement_never_fail("sv_portal_placement_never_fail", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar sv_portal_new_velocity_check("sv_portal_new_velocity_check", "1", FCVAR_CHEAT );
+
+static CUtlVector<CProp_Portal *> s_PortalLinkageGroups[256];
+
+
+BEGIN_DATADESC( CProp_Portal )
+ //saving
+ DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
+ DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_CHARACTER, "LinkageGroupID" ),
+ DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
+ DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
+ DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
+ DEFINE_FIELD( m_vPrevForward, FIELD_VECTOR ),
+ DEFINE_FIELD( m_hMicrophone, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hSpeaker, FIELD_EHANDLE ),
+
+ DEFINE_SOUNDPATCH( m_pAmbientSound ),
+
+ DEFINE_FIELD( m_vAudioOrigin, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vDelayedPosition, FIELD_VECTOR ),
+ DEFINE_FIELD( m_qDelayedAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iDelayedFailure, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hPlacedBy, FIELD_EHANDLE ),
+
+ // DEFINE_FIELD( m_plane_Origin, cplane_t ),
+ // DEFINE_FIELD( m_pAttachedCloningArea, CPhysicsCloneArea ),
+ // DEFINE_FIELD( m_PortalSimulator, CPortalSimulator ),
+ // DEFINE_FIELD( m_pCollisionShape, CPhysCollide ),
+
+ DEFINE_FIELD( m_bSharedEnvironmentConfiguration, FIELD_BOOLEAN ),
+ DEFINE_ARRAY( m_vPortalCorners, FIELD_POSITION_VECTOR, 4 ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( DelayedPlacementThink ),
+ DEFINE_THINKFUNC( TestRestingSurfaceThink ),
+ DEFINE_THINKFUNC( FizzleThink ),
+
+ DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetActivatedState", InputSetActivatedState ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Fizzle", InputFizzle ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "NewLocation", InputNewLocation ),
+
+ DEFINE_OUTPUT( m_OnPlacedSuccessfully, "OnPlacedSuccessfully" ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CProp_Portal, DT_Prop_Portal )
+ SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
+ SendPropBool( SENDINFO(m_bActivated) ),
+ SendPropBool( SENDINFO(m_bIsPortal2) ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( prop_portal, CProp_Portal );
+
+
+
+
+
+
+CProp_Portal::CProp_Portal( void )
+{
+ m_vPrevForward = Vector( 0.0f, 0.0f, 0.0f );
+ m_PortalSimulator.SetPortalSimulatorCallbacks( this );
+
+ // Init to something safe
+ for ( int i = 0; i < 4; ++i )
+ {
+ m_vPortalCorners[i] = Vector(0,0,0);
+ }
+
+ //create the collision shape.... TODO: consider having one shared collideable between all portals
+ float fPlanes[6*4];
+ fPlanes[(0*4) + 0] = 1.0f;
+ fPlanes[(0*4) + 1] = 0.0f;
+ fPlanes[(0*4) + 2] = 0.0f;
+ fPlanes[(0*4) + 3] = CProp_Portal_Shared::vLocalMaxs.x;
+
+ fPlanes[(1*4) + 0] = -1.0f;
+ fPlanes[(1*4) + 1] = 0.0f;
+ fPlanes[(1*4) + 2] = 0.0f;
+ fPlanes[(1*4) + 3] = -CProp_Portal_Shared::vLocalMins.x;
+
+ fPlanes[(2*4) + 0] = 0.0f;
+ fPlanes[(2*4) + 1] = 1.0f;
+ fPlanes[(2*4) + 2] = 0.0f;
+ fPlanes[(2*4) + 3] = CProp_Portal_Shared::vLocalMaxs.y;
+
+ fPlanes[(3*4) + 0] = 0.0f;
+ fPlanes[(3*4) + 1] = -1.0f;
+ fPlanes[(3*4) + 2] = 0.0f;
+ fPlanes[(3*4) + 3] = -CProp_Portal_Shared::vLocalMins.y;
+
+ fPlanes[(4*4) + 0] = 0.0f;
+ fPlanes[(4*4) + 1] = 0.0f;
+ fPlanes[(4*4) + 2] = 1.0f;
+ fPlanes[(4*4) + 3] = CProp_Portal_Shared::vLocalMaxs.z;
+
+ fPlanes[(5*4) + 0] = 0.0f;
+ fPlanes[(5*4) + 1] = 0.0f;
+ fPlanes[(5*4) + 2] = -1.0f;
+ fPlanes[(5*4) + 3] = -CProp_Portal_Shared::vLocalMins.z;
+
+ CPolyhedron *pPolyhedron = GeneratePolyhedronFromPlanes( fPlanes, 6, 0.00001f, true );
+ Assert( pPolyhedron != NULL );
+ CPhysConvex *pConvex = physcollision->ConvexFromConvexPolyhedron( *pPolyhedron );
+ pPolyhedron->Release();
+ Assert( pConvex != NULL );
+ m_pCollisionShape = physcollision->ConvertConvexToCollide( &pConvex, 1 );
+
+ CProp_Portal_Shared::AllPortals.AddToTail( this );
+}
+
+CProp_Portal::~CProp_Portal( void )
+{
+ CProp_Portal_Shared::AllPortals.FindAndRemove( this );
+ s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
+}
+
+
+void CProp_Portal::UpdateOnRemove( void )
+{
+ m_PortalSimulator.ClearEverything();
+
+ RemovePortalMicAndSpeaker();
+
+ CProp_Portal *pRemote = m_hLinkedPortal;
+ if( pRemote != NULL )
+ {
+ m_PortalSimulator.DetachFromLinked();
+ m_hLinkedPortal = NULL;
+ m_bActivated = false;
+ pRemote->UpdatePortalLinkage();
+ pRemote->UpdatePortalTeleportMatrix();
+ }
+
+ if( m_pAttachedCloningArea )
+ {
+ UTIL_Remove( m_pAttachedCloningArea );
+ m_pAttachedCloningArea = NULL;
+ }
+
+
+ BaseClass::UpdateOnRemove();
+}
+
+void CProp_Portal::Precache( void )
+{
+ PrecacheScriptSound( "Portal.ambient_loop" );
+
+ PrecacheScriptSound( "Portal.open_blue" );
+ PrecacheScriptSound( "Portal.open_red" );
+ PrecacheScriptSound( "Portal.close_blue" );
+ PrecacheScriptSound( "Portal.close_red" );
+ PrecacheScriptSound( "Portal.fizzle_moved" );
+ PrecacheScriptSound( "Portal.fizzle_invalid_surface" );
+
+ PrecacheModel( "models/portals/portal1.mdl" );
+ PrecacheModel( "models/portals/portal2.mdl" );
+
+ PrecacheParticleSystem( "portal_1_particles" );
+ PrecacheParticleSystem( "portal_2_particles" );
+ PrecacheParticleSystem( "portal_1_edge" );
+ PrecacheParticleSystem( "portal_2_edge" );
+ PrecacheParticleSystem( "portal_1_nofit" );
+ PrecacheParticleSystem( "portal_2_nofit" );
+ PrecacheParticleSystem( "portal_1_overlap" );
+ PrecacheParticleSystem( "portal_2_overlap" );
+ PrecacheParticleSystem( "portal_1_badvolume" );
+ PrecacheParticleSystem( "portal_2_badvolume" );
+ PrecacheParticleSystem( "portal_1_badsurface" );
+ PrecacheParticleSystem( "portal_2_badsurface" );
+ PrecacheParticleSystem( "portal_1_close" );
+ PrecacheParticleSystem( "portal_2_close" );
+ PrecacheParticleSystem( "portal_1_cleanser" );
+ PrecacheParticleSystem( "portal_2_cleanser" );
+ PrecacheParticleSystem( "portal_1_near" );
+ PrecacheParticleSystem( "portal_2_near" );
+ PrecacheParticleSystem( "portal_1_success" );
+ PrecacheParticleSystem( "portal_2_success" );
+
+ BaseClass::Precache();
+}
+
+void CProp_Portal::CreateSounds()
+{
+ if (!m_pAmbientSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pAmbientSound = controller.SoundCreate( filter, entindex(), "Portal.ambient_loop" );
+ controller.Play( m_pAmbientSound, 0, 100 );
+ }
+}
+
+void CProp_Portal::StopLoopingSounds()
+{
+ if ( m_pAmbientSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pAmbientSound );
+ m_pAmbientSound = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+void CProp_Portal::Spawn( void )
+{
+ Precache();
+
+ Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 );
+ s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
+
+ m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
+
+ AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
+
+ SetSolid( SOLID_OBB );
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
+ SetMoveType( MOVETYPE_NONE );
+ SetCollisionGroup( COLLISION_GROUP_PLAYER );
+
+ //VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_TRIGGER, false );
+ //CreateVPhysics();
+ ResetModel();
+ SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
+
+ UpdateCorners();
+
+ BaseClass::Spawn();
+
+ m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
+}
+
+void CProp_Portal::OnRestore()
+{
+ UpdateCorners();
+
+ Assert( m_pAttachedCloningArea == NULL );
+ m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
+
+ BaseClass::OnRestore();
+
+ if ( m_bActivated )
+ {
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
+ }
+}
+
+void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
+void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
+
+bool CProp_Portal::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
+{
+ physcollision->TraceBox( ray, MASK_ALL, NULL, m_pCollisionShape, GetAbsOrigin(), GetAbsAngles(), &tr );
+ return tr.DidHit();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Runs when a fired portal shot reaches it's destination wall. Detects current placement valididty state.
+//-----------------------------------------------------------------------------
+void CProp_Portal::DelayedPlacementThink( void )
+{
+ Vector vOldOrigin = GetLocalOrigin();
+ QAngle qOldAngles = GetLocalAngles();
+
+ Vector vForward;
+ AngleVectors( m_qDelayedAngles, &vForward );
+
+ // Check if something made the spot invalid mid flight
+ // Bad surface and near fizzle effects take priority
+ if ( m_iDelayedFailure != PORTAL_FIZZLE_BAD_SURFACE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_BLUE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_RED )
+ {
+ if ( IsPortalOverlappingOtherPortals( this, m_vDelayedPosition, m_qDelayedAngles ) )
+ {
+ m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
+ }
+ else if ( IsPortalIntersectingNoPortalVolume( m_vDelayedPosition, m_qDelayedAngles, vForward ) )
+ {
+ m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
+ }
+ }
+
+ if ( sv_portal_placement_never_fail.GetBool() )
+ {
+ m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
+ }
+
+ DoFizzleEffect( m_iDelayedFailure );
+
+ if ( m_iDelayedFailure != PORTAL_FIZZLE_SUCCESS )
+ {
+ // It didn't successfully place
+ return;
+ }
+
+ // Do effects at old location if it was active
+ if ( m_bActivated )
+ {
+ DoFizzleEffect( PORTAL_FIZZLE_CLOSE, false );
+ }
+
+ CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
+
+ if( pPortalGun )
+ {
+ CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
+ if( pFiringPlayer )
+ {
+ pFiringPlayer->IncrementPortalsPlaced();
+
+ // Placement successful, fire the output
+ m_OnPlacedSuccessfully.FireOutput( pPortalGun, this );
+
+ }
+ }
+
+ // Move to new location
+ NewLocation( m_vDelayedPosition, m_qDelayedAngles );
+
+ SetContextThink( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: When placed on a surface that could potentially go away (anything but world geo), we test for that condition and fizzle
+//-----------------------------------------------------------------------------
+void CProp_Portal::TestRestingSurfaceThink( void )
+{
+ // Make sure there's still a surface behind the portal
+ Vector vOrigin = GetAbsOrigin();
+
+ Vector vForward, vRight, vUp;
+ GetVectors( &vForward, &vRight, &vUp );
+
+ trace_t tr;
+ CTraceFilterSimpleClassnameList baseFilter( NULL, COLLISION_GROUP_NONE );
+ UTIL_Portal_Trace_Filter( &baseFilter );
+ baseFilter.AddClassnameToIgnore( "prop_portal" );
+ CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
+
+ int iCornersOnVolatileSurface = 0;
+
+ // Check corners
+ for ( int iCorner = 0; iCorner < 4; ++iCorner )
+ {
+ Vector vCorner = vOrigin;
+
+ if ( iCorner % 2 == 0 )
+ vCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
+ else
+ vCorner += -vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
+
+ if ( iCorner < 2 )
+ vCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
+ else
+ vCorner += -vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
+
+ Ray_t ray;
+ ray.Init( vCorner, vCorner - vForward );
+ enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &tr );
+
+ // This corner isn't on a valid brush (skipping phys converts or physboxes because they frequently go through portals and can't be placed upon).
+ if ( tr.fraction == 1.0f && !tr.startsolid && ( !tr.m_pEnt || ( tr.m_pEnt && !FClassnameIs( tr.m_pEnt, "func_physbox" ) && !FClassnameIs( tr.m_pEnt, "simple_physics_brush" ) ) ) )
+ {
+ DevMsg( "Surface removed from behind portal.\n" );
+ Fizzle();
+ SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
+ break;
+ }
+
+ if ( !tr.DidHitWorld() )
+ {
+ iCornersOnVolatileSurface++;
+ }
+ }
+
+ // Still on a movable or deletable surface
+ if ( iCornersOnVolatileSurface > 0 )
+ {
+ SetContextThink ( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
+ }
+ else
+ {
+ // All corners on world, we don't need to test
+ SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
+ }
+}
+
+bool CProp_Portal::IsActivedAndLinked( void ) const
+{
+ return ( m_bActivated && m_hLinkedPortal.Get() != NULL );
+}
+
+void CProp_Portal::ResetModel( void )
+{
+ if( !m_bIsPortal2 )
+ SetModel( "models/portals/portal1.mdl" );
+ else
+ SetModel( "models/portals/portal2.mdl" );
+
+ SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
+
+ SetSolid( SOLID_OBB );
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
+}
+
+void CProp_Portal::DoFizzleEffect( int iEffect, bool bDelayedPos /*= true*/ )
+{
+ m_vAudioOrigin = ( ( bDelayedPos ) ? ( m_vDelayedPosition ) : ( GetAbsOrigin() ) );
+
+ CEffectData fxData;
+
+ fxData.m_vAngles = ( ( bDelayedPos ) ? ( m_qDelayedAngles ) : ( GetAbsAngles() ) );
+
+ Vector vForward, vUp;
+ AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
+ fxData.m_vOrigin = m_vAudioOrigin + vForward * 1.0f;
+
+ fxData.m_nColor = ( ( m_bIsPortal2 ) ? ( 1 ) : ( 0 ) );
+
+ EmitSound_t ep;
+ CPASAttenuationFilter filter( m_vDelayedPosition );
+
+ ep.m_nChannel = CHAN_STATIC;
+ ep.m_flVolume = 1.0f;
+ ep.m_pOrigin = &m_vAudioOrigin;
+
+ // Rumble effects on the firing player (if one exists)
+ CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
+
+ if ( pPortalGun && (iEffect != PORTAL_FIZZLE_CLOSE )
+ && (iEffect != PORTAL_FIZZLE_SUCCESS )
+ && (iEffect != PORTAL_FIZZLE_NONE ) )
+ {
+ CBasePlayer* pPlayer = (CBasePlayer*)pPortalGun->GetOwner();
+ if ( pPlayer )
+ {
+ pPlayer->RumbleEffect( RUMBLE_PORTAL_PLACEMENT_FAILURE, 0, RUMBLE_FLAGS_NONE );
+ }
+ }
+
+ // Pick a fizzle effect
+ switch ( iEffect )
+ {
+ case PORTAL_FIZZLE_CANT_FIT:
+ //DispatchEffect( "PortalFizzleCantFit", fxData );
+ //ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_nofit" ) : ( "portal_1_nofit" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
+ break;
+
+ case PORTAL_FIZZLE_OVERLAPPED_LINKED:
+ {
+ /*CProp_Portal *pLinkedPortal = m_hLinkedPortal;
+ if ( pLinkedPortal )
+ {
+ Vector vLinkedForward;
+ pLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
+ fxData.m_vStart = pLink3edPortal->GetAbsOrigin() + vLinkedForward * 5.0f;
+ }*/
+
+ //DispatchEffect( "PortalFizzleOverlappedLinked", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_overlap" ) : ( "portal_1_overlap" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+ }
+
+ case PORTAL_FIZZLE_BAD_VOLUME:
+ //DispatchEffect( "PortalFizzleBadVolume", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badvolume" ) : ( "portal_1_badvolume" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+
+ case PORTAL_FIZZLE_BAD_SURFACE:
+ //DispatchEffect( "PortalFizzleBadSurface", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badsurface" ) : ( "portal_1_badsurface" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+
+ case PORTAL_FIZZLE_KILLED:
+ //DispatchEffect( "PortalFizzleKilled", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = "Portal.fizzle_moved";
+ break;
+
+ case PORTAL_FIZZLE_CLEANSER:
+ //DispatchEffect( "PortalFizzleCleanser", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_cleanser" ) : ( "portal_1_cleanser" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+
+ case PORTAL_FIZZLE_CLOSE:
+ //DispatchEffect( "PortalFizzleKilled", fxData );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = ( ( m_bIsPortal2 ) ? ( "Portal.close_red" ) : ( "Portal.close_blue" ) );
+ break;
+
+ case PORTAL_FIZZLE_NEAR_BLUE:
+ {
+ if ( !m_bIsPortal2 )
+ {
+ Vector vLinkedForward;
+ m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
+ fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
+ fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
+ }
+ else
+ {
+ GetVectors( &vForward, NULL, NULL );
+ fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
+ fxData.m_vAngles = GetAbsAngles();
+ }
+
+ //DispatchEffect( "PortalFizzleNear", fxData );
+ AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+ }
+
+ case PORTAL_FIZZLE_NEAR_RED:
+ {
+ if ( m_bIsPortal2 )
+ {
+ Vector vLinkedForward;
+ m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
+ fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
+ fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
+ }
+ else
+ {
+ GetVectors( &vForward, NULL, NULL );
+ fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
+ fxData.m_vAngles = GetAbsAngles();
+ }
+
+ //DispatchEffect( "PortalFizzleNear", fxData );
+ AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ ep.m_pSoundName = "Portal.fizzle_invalid_surface";
+ break;
+ }
+
+ case PORTAL_FIZZLE_SUCCESS:
+ VectorAngles( vUp, vForward, fxData.m_vAngles );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_success" ) : ( "portal_1_success" ) ), fxData.m_vOrigin, fxData.m_vAngles );
+ // Don't make a sound!
+ return;
+
+ case PORTAL_FIZZLE_NONE:
+ // Don't do anything!
+ return;
+ }
+
+ EmitSound( filter, SOUND_FROM_WORLD, ep );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fizzle the portal
+//-----------------------------------------------------------------------------
+void CProp_Portal::FizzleThink( void )
+{
+ CProp_Portal *pRemotePortal = m_hLinkedPortal;
+
+ RemovePortalMicAndSpeaker();
+
+ if ( m_pAmbientSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
+ }
+
+ StopParticleEffects( this );
+
+ m_bActivated = false;
+ m_hLinkedPortal = NULL;
+ m_PortalSimulator.DetachFromLinked();
+ m_PortalSimulator.ReleaseAllEntityOwnership();
+
+ if( pRemotePortal )
+ {
+ //pRemotePortal->m_hLinkedPortal = NULL;
+ pRemotePortal->UpdatePortalLinkage();
+ }
+
+ SetContextThink( NULL, TICK_NEVER_THINK, s_pFizzleThink );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Portal will fizzle next time we get to think
+//-----------------------------------------------------------------------------
+void CProp_Portal::Fizzle( void )
+{
+ SetContextThink( &CProp_Portal::FizzleThink, gpGlobals->curtime, s_pFizzleThink );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes the portal microphone and speakers. This is done in two places
+// (fizzle and UpdateOnRemove) so the code is consolidated here.
+// Input : -
+//-----------------------------------------------------------------------------
+void CProp_Portal::RemovePortalMicAndSpeaker()
+{
+
+ // Shut down microphone/speaker if they exist
+ if ( m_hMicrophone )
+ {
+ CEnvMicrophone *pMicrophone = (CEnvMicrophone*)(m_hMicrophone.Get());
+ if ( pMicrophone )
+ {
+ inputdata_t in;
+ pMicrophone->InputDisable( in );
+ UTIL_Remove( pMicrophone );
+ }
+ m_hMicrophone = 0;
+ }
+
+ if ( m_hSpeaker )
+ {
+ CSpeaker *pSpeaker = (CSpeaker *)(m_hSpeaker.Get());
+ if ( pSpeaker )
+ {
+ // Remove the remote portal's microphone, as it references the speaker we're about to remove.
+ if ( m_hLinkedPortal.Get() )
+ {
+ CProp_Portal* pRemotePortal = m_hLinkedPortal.Get();
+ if ( pRemotePortal->m_hMicrophone )
+ {
+ inputdata_t inputdata;
+ inputdata.pActivator = this;
+ inputdata.pCaller = this;
+ CEnvMicrophone* pRemotePortalMic = dynamic_cast<CEnvMicrophone*>(pRemotePortal->m_hMicrophone.Get());
+ if ( pRemotePortalMic )
+ {
+ pRemotePortalMic->Remove();
+ }
+ }
+ }
+ inputdata_t in;
+ pSpeaker->InputTurnOff( in );
+ UTIL_Remove( pSpeaker );
+ }
+ m_hSpeaker = 0;
+ }
+}
+
+void CProp_Portal::PunchPenetratingPlayer( CBaseEntity *pPlayer )
+{
+ if( m_PortalSimulator.IsReadyToSimulate() )
+ {
+ ICollideable *pCollideable = pPlayer->GetCollideable();
+ if ( pCollideable )
+ {
+ Vector vMin, vMax;
+
+ pCollideable->WorldSpaceSurroundingBounds( &vMin, &vMax );
+
+ if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f, this ) )
+ {
+ Vector vForward;
+ GetVectors( &vForward, 0, 0 );
+ vForward *= 100.0f;
+ pPlayer->VelocityPunch( vForward );
+ }
+ }
+ }
+}
+
+void CProp_Portal::PunchAllPenetratingPlayers( void )
+{
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer )
+ PunchPenetratingPlayer( pPlayer );
+ }
+}
+
+void CProp_Portal::Activate( void )
+{
+ if( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 )
+ s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
+
+ if( m_pAttachedCloningArea == NULL )
+ m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
+
+ UpdatePortalTeleportMatrix();
+
+ UpdatePortalLinkage();
+
+ BaseClass::Activate();
+
+ CreateSounds();
+
+ AddEffects( EF_NOSHADOW | EF_NORECEIVESHADOW );
+
+ if( m_bActivated && (m_hLinkedPortal.Get() != NULL) )
+ {
+ Vector ptCenter = GetAbsOrigin();
+ QAngle qAngles = GetAbsAngles();
+ m_PortalSimulator.MoveTo( ptCenter, qAngles );
+
+ //resimulate everything we're touching
+ touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
+ if( root )
+ {
+ for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
+ {
+ CBaseEntity *pOther = link->entityTouched;
+ if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
+ {
+ CCollisionProperty *pOtherCollision = pOther->CollisionProp();
+ Vector vWorldMins, vWorldMaxs;
+ pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
+ Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
+
+ if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
+ {
+ //we should be interacting with this object, add it to our environment
+ if( SharedEnvironmentCheck( pOther ) )
+ {
+ Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
+ (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
+
+ CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
+ if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
+ pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
+
+ m_PortalSimulator.TakeOwnershipOfEntity( pOther );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool CProp_Portal::ShouldTeleportTouchingEntity( CBaseEntity *pOther )
+{
+ if( !m_PortalSimulator.OwnsEntity( pOther ) ) //can't teleport an entity we don't own
+ {
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it's not simulated by this portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
+ }
+#endif
+ if ( sv_portal_debug_touch.GetBool() )
+ {
+ Msg( "Portal %i not teleporting %s because it's not simulated by this portal. : %f \n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName(), gpGlobals->curtime );
+ }
+ return false;
+ }
+
+ if( !CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
+ return false;
+
+ if( m_hLinkedPortal.Get() == NULL )
+ {
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
+ }
+#endif
+ if ( sv_portal_debug_touch.GetBool() )
+ {
+ Msg( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
+ }
+ return false;
+ }
+
+ //Vector ptOtherOrigin = pOther->GetAbsOrigin();
+ Vector ptOtherCenter = pOther->WorldSpaceCenter();
+
+ IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
+
+ Vector vOtherVelocity;
+ //grab current velocity
+ {
+ if( sv_portal_new_velocity_check.GetBool() )
+ {
+ //we're assuming that physics velocity is the most reliable of all if the convar is true
+ if( pOtherPhysObject )
+ {
+ //pOtherPhysObject->GetImplicitVelocity( &vOtherVelocity, NULL );
+ pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
+
+ if( vOtherVelocity == vec3_origin )
+ {
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+ }
+ else
+ {
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+ }
+ else
+ {
+ //old style of velocity grabbing, which uses implicit velocity as a last resort
+ if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
+ pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
+ else
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+ else
+ {
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+
+ if( vOtherVelocity == vec3_origin )
+ {
+ // Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
+ // In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
+ if ( pOtherPhysObject )
+ {
+ Vector vOtherImplicitVelocity;
+ pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
+ vOtherVelocity += vOtherImplicitVelocity;
+ }
+ }
+ }
+ }
+
+ // Test for entity's center being past portal plane
+ if(m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist)
+ {
+ //entity wants to go further into the plane
+ if( m_PortalSimulator.EntityIsInPortalHole( pOther ) )
+ {
+#ifdef _DEBUG
+ static int iAntiRecurse = 0;
+ if( pOther->IsPlayer() && (iAntiRecurse == 0) )
+ {
+ ++iAntiRecurse;
+ ShouldTeleportTouchingEntity( pOther ); //do it again for debugging
+ --iAntiRecurse;
+ }
+#endif
+ return true;
+ }
+ else
+ {
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
+ }
+#endif
+ if ( sv_portal_debug_touch.GetBool() )
+ {
+ Msg( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
+ }
+ }
+ }
+
+ return false;
+}
+
+void CProp_Portal::TeleportTouchingEntity( CBaseEntity *pOther )
+{
+ if ( GetPortalCallQueue() )
+ {
+ GetPortalCallQueue()->QueueCall( this, &CProp_Portal::TeleportTouchingEntity, pOther );
+ return;
+ }
+
+ Assert( m_hLinkedPortal.Get() != NULL );
+
+ Vector ptOtherOrigin = pOther->GetAbsOrigin();
+ Vector ptOtherCenter;
+
+ bool bPlayer = pOther->IsPlayer();
+ QAngle qPlayerEyeAngles;
+ CPortal_Player *pOtherAsPlayer;
+
+
+ if( bPlayer )
+ {
+ //NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 128, 60.0f );
+ pOtherAsPlayer = (CPortal_Player *)pOther;
+ qPlayerEyeAngles = pOtherAsPlayer->pl.v_angle;
+ }
+ else
+ {
+ pOtherAsPlayer = NULL;
+ }
+
+ ptOtherCenter = pOther->WorldSpaceCenter();
+
+ bool bNonPhysical = false; //special case handling for non-physical objects such as the energy ball and player
+
+
+
+ QAngle qOtherAngles;
+ Vector vOtherVelocity;
+
+ //grab current velocity
+ {
+ IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
+ if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
+ pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
+ else
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+ else if ( bPlayer && pOther->VPhysicsGetObject() )
+ {
+ pOther->VPhysicsGetObject()->GetVelocity( &vOtherVelocity, NULL );
+
+ if ( vOtherVelocity == vec3_origin )
+ {
+ vOtherVelocity = pOther->GetAbsVelocity();
+ }
+ }
+ else
+ {
+ pOther->GetVelocity( &vOtherVelocity );
+ }
+
+ if( vOtherVelocity == vec3_origin )
+ {
+ // Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
+ // In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
+ if ( pOtherPhysObject )
+ {
+ Vector vOtherImplicitVelocity;
+ pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
+ vOtherVelocity += vOtherImplicitVelocity;
+ }
+ }
+ }
+
+ const PS_InternalData_t &RemotePortalDataAccess = m_hLinkedPortal->m_PortalSimulator.m_DataAccess;
+ const PS_InternalData_t &LocalPortalDataAccess = m_PortalSimulator.m_DataAccess;
+
+
+ if( bPlayer )
+ {
+ qOtherAngles = pOtherAsPlayer->EyeAngles();
+ pOtherAsPlayer->m_qPrePortalledViewAngles = qOtherAngles;
+ pOtherAsPlayer->m_bFixEyeAnglesFromPortalling = true;
+ pOtherAsPlayer->m_matLastPortalled = m_matrixThisToLinked;
+ bNonPhysical = true;
+ //if( (fabs( RemotePortalDataAccess.Placement.vForward.z ) + fabs( LocalPortalDataAccess.Placement.vForward.z )) > 0.7071f ) //some combination of floor/ceiling
+ if( fabs( LocalPortalDataAccess.Placement.vForward.z ) > 0.0f )
+ {
+ //we may have to compensate for the fact that AABB's don't rotate ever
+
+ float fAbsLocalZ = fabs( LocalPortalDataAccess.Placement.vForward.z );
+ float fAbsRemoteZ = fabs( RemotePortalDataAccess.Placement.vForward.z );
+
+ if( (fabs(fAbsLocalZ - 1.0f) < 0.01f) &&
+ (fabs(fAbsRemoteZ - 1.0f) < 0.01f) )
+ //(fabs( LocalPortalDataAccess.Placement.vForward.z + RemotePortalDataAccess.Placement.vForward.z ) < 0.01f) )
+ {
+ //portals are both aligned on the z axis, no need to shrink the player
+
+ }
+ else
+ {
+ //curl the player up into a little ball
+ pOtherAsPlayer->SetGroundEntity( NULL );
+
+ if( !pOtherAsPlayer->IsDucked() )
+ {
+ pOtherAsPlayer->ForceDuckThisFrame();
+ pOtherAsPlayer->m_Local.m_bInDuckJump = true;
+
+ if( LocalPortalDataAccess.Placement.vForward.z > 0.0f )
+ ptOtherCenter.z -= 16.0f; //portal facing up, shrink downwards
+ else
+ ptOtherCenter.z += 16.0f; //portal facing down, shrink upwards
+ }
+ }
+ }
+ }
+ else
+ {
+ qOtherAngles = pOther->GetAbsAngles();
+ bNonPhysical = FClassnameIs( pOther, "prop_energy_ball" );
+ }
+
+
+ Vector ptNewOrigin;
+ QAngle qNewAngles;
+ Vector vNewVelocity;
+ //apply transforms to relevant variables (applied to the entity later)
+ {
+ if( bPlayer )
+ {
+ ptNewOrigin = m_matrixThisToLinked * ptOtherCenter;
+ ptNewOrigin += ptOtherOrigin - ptOtherCenter;
+ }
+ else
+ {
+ ptNewOrigin = m_matrixThisToLinked * ptOtherOrigin;
+ }
+
+ // Reorient object angles, originally we did a transformation on the angles, but that doesn't quite work right for gimbal lock cases
+ qNewAngles = TransformAnglesToWorldSpace( qOtherAngles, m_matrixThisToLinked.As3x4() );
+
+ qNewAngles.x = AngleNormalizePositive( qNewAngles.x );
+ qNewAngles.y = AngleNormalizePositive( qNewAngles.y );
+ qNewAngles.z = AngleNormalizePositive( qNewAngles.z );
+
+ // Reorient the velocity
+ vNewVelocity = m_matrixThisToLinked.ApplyRotation( vOtherVelocity );
+ }
+
+ //help camera reorientation for the player
+ if( bPlayer )
+ {
+ Vector vPlayerForward;
+ AngleVectors( qOtherAngles, &vPlayerForward, NULL, NULL );
+
+ float fPlayerForwardZ = vPlayerForward.z;
+ vPlayerForward.z = 0.0f;
+
+ float fForwardLength = vPlayerForward.Length();
+
+ if ( fForwardLength > 0.0f )
+ {
+ VectorNormalize( vPlayerForward );
+ }
+
+ float fPlayerFaceDotPortalFace = LocalPortalDataAccess.Placement.vForward.Dot( vPlayerForward );
+ float fPlayerFaceDotPortalUp = LocalPortalDataAccess.Placement.vUp.Dot( vPlayerForward );
+
+ CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
+
+ // Sometimes reorienting by pitch is more desirable than by roll depending on the portals' orientations and the relative player facing direction
+ if ( pHeldEntity ) // never pitch reorient while holding an object
+ {
+ pOtherAsPlayer->m_bPitchReorientation = false;
+ }
+ else if ( LocalPortalDataAccess.Placement.vUp.z > 0.99f && // entering wall portal
+ ( fForwardLength == 0.0f || // facing strait up or down
+ fPlayerFaceDotPortalFace > 0.5f || // facing mostly away from portal
+ fPlayerFaceDotPortalFace < -0.5f ) // facing mostly toward portal
+ )
+ {
+ pOtherAsPlayer->m_bPitchReorientation = true;
+ }
+ else if ( ( LocalPortalDataAccess.Placement.vForward.z > 0.99f || LocalPortalDataAccess.Placement.vForward.z < -0.99f ) && // entering floor or ceiling portal
+ ( RemotePortalDataAccess.Placement.vForward.z > 0.99f || RemotePortalDataAccess.Placement.vForward.z < -0.99f ) && // exiting floor or ceiling portal
+ ( fPlayerForwardZ < -0.5f || fPlayerForwardZ > 0.5f ) // facing mustly up or down
+ )
+ {
+ pOtherAsPlayer->m_bPitchReorientation = true;
+ }
+ else if ( ( RemotePortalDataAccess.Placement.vForward.z > 0.75f && RemotePortalDataAccess.Placement.vForward.z <= 0.99f ) && // exiting wedge portal
+ ( fPlayerFaceDotPortalUp > 0.0f ) // facing toward the top of the portal
+ )
+ {
+ pOtherAsPlayer->m_bPitchReorientation = true;
+ }
+ else
+ {
+ pOtherAsPlayer->m_bPitchReorientation = false;
+ }
+ }
+
+ //velocity hacks
+ {
+ //minimum floor exit velocity if both portals are on the floor or the player is coming out of the floor
+ if( RemotePortalDataAccess.Placement.vForward.z > 0.7071f )
+ {
+ if ( bPlayer )
+ {
+ if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER )
+ vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER;
+ }
+ else
+ {
+ if( LocalPortalDataAccess.Placement.vForward.z > 0.7071f )
+ {
+ if( vNewVelocity.z < MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY )
+ vNewVelocity.z = MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY;
+ }
+ else
+ {
+ if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY )
+ vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY;
+ }
+ }
+ }
+
+
+ if ( vNewVelocity.LengthSqr() > (MAXIMUM_PORTAL_EXIT_VELOCITY * MAXIMUM_PORTAL_EXIT_VELOCITY) )
+ vNewVelocity *= (MAXIMUM_PORTAL_EXIT_VELOCITY / vNewVelocity.Length());
+ }
+
+ //untouch the portal(s), will force a touch on destination after the teleport
+ {
+ m_PortalSimulator.ReleaseOwnershipOfEntity( pOther, true );
+ this->PhysicsNotifyOtherOfUntouch( this, pOther );
+ pOther->PhysicsNotifyOtherOfUntouch( pOther, this );
+
+ m_hLinkedPortal->m_PortalSimulator.TakeOwnershipOfEntity( pOther );
+
+ //m_hLinkedPortal->PhysicsNotifyOtherOfUntouch( m_hLinkedPortal, pOther );
+ //pOther->PhysicsNotifyOtherOfUntouch( pOther, m_hLinkedPortal );
+ }
+
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
+ }
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
+ }
+#endif
+
+ //do the actual teleportation
+ {
+ pOther->SetGroundEntity( NULL );
+
+ if( bPlayer )
+ {
+ QAngle qTransformedEyeAngles = TransformAnglesToWorldSpace( qPlayerEyeAngles, m_matrixThisToLinked.As3x4() );
+ qTransformedEyeAngles.x = AngleNormalizePositive( qTransformedEyeAngles.x );
+ qTransformedEyeAngles.y = AngleNormalizePositive( qTransformedEyeAngles.y );
+ qTransformedEyeAngles.z = AngleNormalizePositive( qTransformedEyeAngles.z );
+
+ pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
+ pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
+ pOtherAsPlayer->UpdateVPhysicsPosition( ptNewOrigin, vNewVelocity, 0.0f );
+ pOtherAsPlayer->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
+ //pOtherAsPlayer->UnDuck();
+
+ //pOtherAsPlayer->m_angEyeAngles = qTransformedEyeAngles;
+ //pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
+ //pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
+ }
+ else
+ {
+ if( bNonPhysical )
+ {
+ pOther->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
+ }
+ else
+ {
+ //doing velocity in two stages as a bug workaround, setting the velocity to anything other than 0 will screw up how objects rest on this entity in the future
+ pOther->Teleport( &ptNewOrigin, &qNewAngles, &vec3_origin );
+ pOther->ApplyAbsVelocityImpulse( vNewVelocity );
+ }
+ }
+ }
+
+ IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
+ if( (pPhys != NULL) && (pPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
+ {
+ CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pOther );
+ pHoldingPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
+ if ( pHoldingPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ pHoldingPlayer->SetHeldObjectPortal( this );
+ else
+ pHoldingPlayer->SetHeldObjectPortal( NULL );
+ }
+ else if( bPlayer )
+ {
+ CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
+ if( pHeldEntity )
+ {
+ pOtherAsPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
+ if( pOtherAsPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ pOtherAsPlayer->SetHeldObjectPortal( m_hLinkedPortal );
+ }
+ else
+ {
+ pOtherAsPlayer->SetHeldObjectPortal( NULL );
+
+ //we need to make sure the held object and player don't interpenetrate when the player's shape changes
+ Vector vTargetPosition;
+ QAngle qTargetOrientation;
+ UpdateGrabControllerTargetPosition( pOtherAsPlayer, &vTargetPosition, &qTargetOrientation );
+
+ pHeldEntity->Teleport( &vTargetPosition, &qTargetOrientation, 0 );
+
+ FindClosestPassableSpace( pHeldEntity, RemotePortalDataAccess.Placement.vForward );
+ }
+ }
+
+ //we haven't found a good way of fixing the problem of "how do you reorient an AABB". So we just move the player so that they fit
+ //m_hLinkedPortal->ForceEntityToFitInPortalWall( pOtherAsPlayer );
+ }
+
+ //force the entity to be touching the other portal right this millisecond
+ {
+ trace_t Trace;
+ memset( &Trace, 0, sizeof(trace_t) );
+ //UTIL_TraceEntity( pOther, ptNewOrigin, ptNewOrigin, MASK_SOLID, pOther, COLLISION_GROUP_NONE, &Trace ); //fires off some asserts, and we just need a dummy anyways
+
+ pOther->PhysicsMarkEntitiesAsTouching( m_hLinkedPortal.Get(), Trace );
+ m_hLinkedPortal.Get()->PhysicsMarkEntitiesAsTouching( pOther, Trace );
+ }
+
+ // Notify the entity that it's being teleported
+ // Tell the teleported entity of the portal it has just arrived at
+ notify_teleport_params_t paramsTeleport;
+ paramsTeleport.prevOrigin = ptOtherOrigin;
+ paramsTeleport.prevAngles = qOtherAngles;
+ paramsTeleport.physicsRotate = true;
+ notify_system_event_params_t eventParams ( &paramsTeleport );
+ pOther->NotifySystemEvent( this, NOTIFY_EVENT_TELEPORT, eventParams );
+
+ //notify clients of the teleportation
+ {
+ CBroadcastRecipientFilter filter;
+ filter.MakeReliable();
+ UserMessageBegin( filter, "EntityPortalled" );
+ WRITE_EHANDLE( this );
+ WRITE_EHANDLE( pOther );
+ WRITE_FLOAT( ptNewOrigin.x );
+ WRITE_FLOAT( ptNewOrigin.y );
+ WRITE_FLOAT( ptNewOrigin.z );
+ WRITE_FLOAT( qNewAngles.x );
+ WRITE_FLOAT( qNewAngles.y );
+ WRITE_FLOAT( qNewAngles.z );
+ MessageEnd();
+ }
+
+#ifdef _DEBUG
+ {
+ Vector ptTestCenter = pOther->WorldSpaceCenter();
+
+ float fNewDist, fOldDist;
+ fNewDist = RemotePortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptTestCenter ) - RemotePortalDataAccess.Placement.PortalPlane.m_Dist;
+ fOldDist = LocalPortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) - LocalPortalDataAccess.Placement.PortalPlane.m_Dist;
+ AssertMsg( fNewDist >= 0.0f, "Entity portalled behind the destination portal." );
+ }
+#endif
+
+
+ pOther->NetworkProp()->NetworkStateForceUpdate();
+ if( bPlayer )
+ pOtherAsPlayer->pl.NetworkStateChanged();
+
+ //if( bPlayer )
+ // NDebugOverlay::EntityBounds( pOther, 0, 255, 0, 128, 60.0f );
+
+ Assert( (bPlayer == false) || (pOtherAsPlayer->m_hPortalEnvironment.Get() == m_hLinkedPortal.Get()) );
+}
+
+
+void CProp_Portal::Touch( CBaseEntity *pOther )
+{
+ BaseClass::Touch( pOther );
+ pOther->Touch( this );
+
+ // Don't do anything on touch if it's not active
+ if( !m_bActivated || (m_hLinkedPortal.Get() == NULL) )
+ {
+ Assert( !m_PortalSimulator.OwnsEntity( pOther ) );
+ Assert( !pOther->IsPlayer() || (((CPortal_Player *)pOther)->m_hPortalEnvironment.Get() != this) );
+
+ //I'd really like to fix the root cause, but this will keep the game going
+ m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
+ return;
+ }
+
+ Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
+ (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
+
+ // Fizzle portal with any moving brush
+ Vector vVelocityCheck;
+ AngularImpulse vAngularImpulseCheck;
+ pOther->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
+
+ if( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
+ {
+ if ( modelinfo->GetModelType( pOther->GetModel() ) == mod_brush )
+ {
+ if ( !FClassnameIs( pOther, "func_physbox" ) && !FClassnameIs( pOther, "simple_physics_brush" ) ) // except CPhysBox
+ {
+ Vector vForward;
+ GetVectors( &vForward, NULL, NULL );
+
+ Vector vMin, vMax;
+ pOther->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
+
+ if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f - Vector( 2.0f, 2.0f, 2.0f ), this, 0.0f ) )
+ {
+ DevMsg( "Moving brush intersected portal plane.\n" );
+
+ DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ Fizzle();
+ }
+ else
+ {
+ Vector vOrigin = GetAbsOrigin();
+
+ trace_t tr;
+
+ UTIL_TraceLine( vOrigin, vOrigin - vForward * PORTAL_HALF_DEPTH, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
+
+ // Something went wrong
+ if ( tr.fraction == 1.0f && !tr.startsolid )
+ {
+ DevMsg( "Surface removed from behind portal.\n" );
+
+ DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ Fizzle();
+ }
+ else if ( tr.m_pEnt && tr.m_pEnt->IsMoving() )
+ {
+ DevMsg( "Surface behind portal is moving.\n" );
+
+ DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ Fizzle();
+ }
+ }
+ }
+ }
+ }
+
+ if( m_hLinkedPortal == NULL )
+ return;
+
+ //see if we should even be interacting with this object, this is a bugfix where some objects get added to physics environments through walls
+ if( !m_PortalSimulator.OwnsEntity( pOther ) )
+ {
+ //hmm, not in our environment, plane tests, sharing tests
+ if( SharedEnvironmentCheck( pOther ) )
+ {
+ bool bObjectCenterInFrontOfPortal = (m_plane_Origin.normal.Dot( pOther->WorldSpaceCenter() ) > m_plane_Origin.dist);
+ bool bIsStuckPlayer = ( pOther->IsPlayer() )? ( !UTIL_IsSpaceEmpty( pOther, pOther->WorldAlignMins(), pOther->WorldAlignMaxs() ) ) : ( false );
+
+ if ( bIsStuckPlayer )
+ {
+ Assert ( !"Player stuck" );
+ DevMsg( "Player in solid behind behind portal %i's plane, Adding to it's environment to run find closest passable space.\n", ((m_bIsPortal2)?(2):(1)) );
+ }
+
+ if ( bObjectCenterInFrontOfPortal || bIsStuckPlayer )
+ {
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
+ }
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
+ }
+#endif
+
+ //we should be interacting with this object, add it to our environment
+ CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
+ if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
+ pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
+
+ m_PortalSimulator.TakeOwnershipOfEntity( pOther );
+ }
+ }
+ else
+ {
+ return; //we shouldn't interact with this object
+ }
+ }
+
+ if( ShouldTeleportTouchingEntity( pOther ) )
+ TeleportTouchingEntity( pOther );
+}
+
+void CProp_Portal::StartTouch( CBaseEntity *pOther )
+{
+ BaseClass::StartTouch( pOther );
+
+ // Since prop_portal is a trigger it doesn't send back start touch, so I'm forcing it
+ pOther->StartTouch( this );
+
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
+ }
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
+ }
+#endif
+
+ if( (m_hLinkedPortal == NULL) || (m_bActivated == false) )
+ return;
+
+ if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
+ {
+ CCollisionProperty *pOtherCollision = pOther->CollisionProp();
+ Vector vWorldMins, vWorldMaxs;
+ pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
+ Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
+
+ if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
+ {
+ //we should be interacting with this object, add it to our environment
+ if( SharedEnvironmentCheck( pOther ) )
+ {
+ Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
+ (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
+
+ CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
+ if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
+ pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
+
+ m_PortalSimulator.TakeOwnershipOfEntity( pOther );
+ }
+ }
+ }
+}
+
+void CProp_Portal::EndTouch( CBaseEntity *pOther )
+{
+ BaseClass::EndTouch( pOther );
+
+ // Since prop_portal is a trigger it doesn't send back end touch, so I'm forcing it
+ pOther->EndTouch( this );
+
+ // Don't do anything on end touch if it's not active
+ if( !m_bActivated )
+ {
+ return;
+ }
+
+ if( ShouldTeleportTouchingEntity( pOther ) ) //an object passed through the plane and all the way out of the touch box
+ TeleportTouchingEntity( pOther );
+ else if( pOther->IsPlayer() && //player
+ (m_PortalSimulator.m_DataAccess.Placement.vForward.z < -0.7071f) && //most likely falling out of the portal
+ (m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( pOther->WorldSpaceCenter() ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist) && //but behind the portal plane
+ (((CPortal_Player *)pOther)->m_Local.m_bInDuckJump) ) //while ducking
+ {
+ //player has pulled their feet up (moving their center instantaneously) while falling downward out of the portal, send them back (probably only for a frame)
+
+ DevMsg( "Player pulled feet above the portal they fell out of, postponing Releasing ownership\n" );
+ //TeleportTouchingEntity( pOther );
+ }
+ else
+ m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
+
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
+ }
+
+#if !defined( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
+ }
+#endif
+}
+
+void CProp_Portal::PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity )
+{
+ if( pEntity->IsPlayer() )
+ ((CPortal_Player *)pEntity)->m_hPortalEnvironment = this;
+}
+
+void CProp_Portal::PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity )
+{
+ if( pEntity->IsPlayer() && (((CPortal_Player *)pEntity)->m_hPortalEnvironment.Get() == this) )
+ ((CPortal_Player *)pEntity)->m_hPortalEnvironment = NULL;
+}
+
+bool CProp_Portal::SharedEnvironmentCheck( CBaseEntity *pEntity )
+{
+ Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
+ (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
+
+ CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
+ if( (pOwningSimulator == NULL) || (pOwningSimulator == &m_PortalSimulator) )
+ {
+ //nobody else is claiming ownership
+ return true;
+ }
+
+ Vector ptCenter = pEntity->WorldSpaceCenter();
+ if( (ptCenter - m_PortalSimulator.m_DataAccess.Placement.ptCenter).LengthSqr() < (ptCenter - pOwningSimulator->m_DataAccess.Placement.ptCenter).LengthSqr() )
+ return true;
+
+ /*if( !m_hLinkedPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) )
+ {
+ Vector vOtherVelocity;
+ pEntity->GetVelocity( &vOtherVelocity );
+
+ if( vOtherVelocity.Dot( m_PortalSimulator.m_DataAccess.Placement.vForward ) < vOtherVelocity.Dot( m_hLinkedPortal->m_PortalSimulator.m_DataAccess.Placement.vForward ) )
+ return true; //entity is going towards this portal more than the other
+ }*/
+ return false;
+
+ //we're in the shared configuration, and the other portal already owns the object, see if we'd be a better caretaker (distance check
+ /*CCollisionProperty *pEntityCollision = pEntity->CollisionProp();
+ Vector vWorldMins, vWorldMaxs;
+ pEntityCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
+ Vector ptEntityCenter = (vWorldMins + vWorldMaxs) / 2.0f;
+
+ Vector vEntToThis = GetAbsOrigin() - ptEntityCenter;
+ Vector vEntToRemote = m_hLinkedPortal->GetAbsOrigin() - ptEntityCenter;
+
+ return ( vEntToThis.LengthSqr() < vEntToRemote.LengthSqr() );*/
+}
+
+void CProp_Portal::WakeNearbyEntities( void )
+{
+ CBaseEntity* pList[ 1024 ];
+
+ Vector vForward, vUp, vRight;
+ GetVectors( &vForward, &vRight, &vUp );
+
+ Vector ptOrigin = GetAbsOrigin();
+ QAngle qAngles = GetAbsAngles();
+
+ Vector ptOBBStart = ptOrigin;
+ ptOBBStart += vForward * CProp_Portal_Shared::vLocalMins.x;
+ ptOBBStart += vRight * CProp_Portal_Shared::vLocalMins.y;
+ ptOBBStart += vUp * CProp_Portal_Shared::vLocalMins.z;
+
+
+ vForward *= CProp_Portal_Shared::vLocalMaxs.x - CProp_Portal_Shared::vLocalMins.x;
+ vRight *= CProp_Portal_Shared::vLocalMaxs.y - CProp_Portal_Shared::vLocalMins.y;
+ vUp *= CProp_Portal_Shared::vLocalMaxs.z - CProp_Portal_Shared::vLocalMins.z;
+
+
+ Vector vAABBMins, vAABBMaxs;
+ vAABBMins = vAABBMaxs = ptOBBStart;
+
+ for( int i = 1; i != 8; ++i )
+ {
+ Vector ptTest = ptOBBStart;
+ if( i & (1 << 0) ) ptTest += vForward;
+ if( i & (1 << 1) ) ptTest += vRight;
+ if( i & (1 << 2) ) ptTest += vUp;
+
+ if( ptTest.x < vAABBMins.x ) vAABBMins.x = ptTest.x;
+ if( ptTest.y < vAABBMins.y ) vAABBMins.y = ptTest.y;
+ if( ptTest.z < vAABBMins.z ) vAABBMins.z = ptTest.z;
+ if( ptTest.x > vAABBMaxs.x ) vAABBMaxs.x = ptTest.x;
+ if( ptTest.y > vAABBMaxs.y ) vAABBMaxs.y = ptTest.y;
+ if( ptTest.z > vAABBMaxs.z ) vAABBMaxs.z = ptTest.z;
+ }
+
+ int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
+
+ //Iterate over all the possible targets
+ for ( int i = 0; i < count; i++ )
+ {
+ CBaseEntity *pEntity = pList[i];
+
+ if ( pEntity && (pEntity != this) )
+ {
+ CCollisionProperty *pEntCollision = pEntity->CollisionProp();
+ Vector ptEntityCenter = pEntCollision->GetCollisionOrigin();
+
+ //double check intersection at the OBB vs OBB level, we don't want to affect large piles of physics objects if we don't have to. It gets slow
+ if( IsOBBIntersectingOBB( ptOrigin, qAngles, CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
+ ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
+ {
+ if( FClassnameIs( pEntity, "func_portal_detector" ) )
+ {
+ // It's a portal detector
+ CFuncPortalDetector *pPortalDetector = static_cast<CFuncPortalDetector*>( pEntity );
+
+ if ( pPortalDetector->IsActive() && pPortalDetector->GetLinkageGroupID() == m_iLinkageGroupID )
+ {
+ // It's detecting this portal's group
+ Vector vMin, vMax;
+ pPortalDetector->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
+
+ Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
+ Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
+
+ if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, this ) )
+ {
+ // It's intersecting this portal
+ if ( m_bIsPortal2 )
+ pPortalDetector->m_OnStartTouchPortal2.FireOutput( this, pPortalDetector );
+ else
+ pPortalDetector->m_OnStartTouchPortal1.FireOutput( this, pPortalDetector );
+
+ if ( IsActivedAndLinked() )
+ {
+ pPortalDetector->m_OnStartTouchLinkedPortal.FireOutput( this, pPortalDetector );
+
+ if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, m_hLinkedPortal ) )
+ {
+ pPortalDetector->m_OnStartTouchBothLinkedPortals.FireOutput( this, pPortalDetector );
+ }
+ }
+ }
+ }
+ }
+
+ pEntity->WakeRestingObjects();
+ //pEntity->SetGroundEntity( NULL );
+
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
+
+ if ( pPhysicsObject && pPhysicsObject->IsMoveable() )
+ {
+ pPhysicsObject->Wake();
+
+ // If the target is debris, convert it to non-debris
+ if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ // Interactive debris converts back to debris when it comes to rest
+ pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void CProp_Portal::ForceEntityToFitInPortalWall( CBaseEntity *pEntity )
+{
+ CCollisionProperty *pCollision = pEntity->CollisionProp();
+ Vector vWorldMins, vWorldMaxs;
+ pCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
+ Vector ptCenter = pEntity->WorldSpaceCenter(); //(vWorldMins + vWorldMaxs) / 2.0f;
+ Vector ptOrigin = pEntity->GetAbsOrigin();
+ Vector vEntityCenterToOrigin = ptOrigin - ptCenter;
+
+
+ Vector ptPortalCenter = GetAbsOrigin();
+ Vector vPortalCenterToEntityCenter = ptCenter - ptPortalCenter;
+ Vector vPortalForward;
+ GetVectors( &vPortalForward, NULL, NULL );
+ Vector ptProjectedEntityCenter = ptPortalCenter + ( vPortalForward * vPortalCenterToEntityCenter.Dot( vPortalForward ) );
+
+ Vector ptDest;
+
+ if ( m_PortalSimulator.IsReadyToSimulate() )
+ {
+ Ray_t ray;
+ ray.Init( ptProjectedEntityCenter, ptCenter, vWorldMins - ptCenter, vWorldMaxs - ptCenter );
+
+ trace_t ShortestTrace;
+ ShortestTrace.fraction = 2.0f;
+
+ if( m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable )
+ {
+ physcollision->TraceBox( ray, m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &ShortestTrace );
+ }
+
+ /*if( pEnvironment->LocalCollide.pWorldCollide )
+ {
+ trace_t TempTrace;
+ physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWorldCollide, vec3_origin, vec3_angle, &TempTrace );
+ if( TempTrace.fraction < ShortestTrace.fraction )
+ ShortestTrace = TempTrace;
+ }
+
+ if( pEnvironment->LocalCollide.pWallShellCollide )
+ {
+ trace_t TempTrace;
+ physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWallShellCollide, vec3_origin, vec3_angle, &TempTrace );
+ if( TempTrace.fraction < ShortestTrace.fraction )
+ ShortestTrace = TempTrace;
+ }
+
+ if( pEnvironment->LocalCollide.pRemoteWorldWallCollide )
+ {
+ trace_t TempTrace;
+ physcollision->TraceBox( ray, pEnvironment->LocalCollide.pRemoteWorldWallCollide, vec3_origin, vec3_angle, &TempTrace );
+ if( TempTrace.fraction < ShortestTrace.fraction )
+ ShortestTrace = TempTrace;
+ }
+
+ //Add displacement checks here too?
+
+ */
+
+ if( ShortestTrace.fraction < 2.0f )
+ {
+ Vector ptNewPos = ShortestTrace.endpos + vEntityCenterToOrigin;
+ pEntity->Teleport( &ptNewPos, NULL, NULL );
+ pEntity->IncrementInterpolationFrame();
+#if !defined ( DISABLE_DEBUG_HISTORY )
+ if ( !IsMarkedForDeletion() )
+ {
+ ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() ) );
+ }
+#endif
+ if( sv_portal_debug_touch.GetBool() )
+ {
+ DevMsg( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() );
+ }
+ //pEntity->SetAbsOrigin( ShortestTrace.endpos + vEntityCenterToOrigin );
+ }
+ }
+}
+
+void CProp_Portal::UpdatePortalTeleportMatrix( void )
+{
+ ResetModel();
+
+ //setup our origin plane
+ GetVectors( &m_plane_Origin.normal, NULL, NULL );
+ m_plane_Origin.dist = m_plane_Origin.normal.Dot( GetAbsOrigin() );
+ m_plane_Origin.signbits = SignbitsForPlane( &m_plane_Origin );
+
+ Vector vAbsNormal;
+ vAbsNormal.x = fabs(m_plane_Origin.normal.x);
+ vAbsNormal.y = fabs(m_plane_Origin.normal.y);
+ vAbsNormal.z = fabs(m_plane_Origin.normal.z);
+
+ if( vAbsNormal.x > vAbsNormal.y )
+ {
+ if( vAbsNormal.x > vAbsNormal.z )
+ {
+ if( vAbsNormal.x > 0.999f )
+ m_plane_Origin.type = PLANE_X;
+ else
+ m_plane_Origin.type = PLANE_ANYX;
+ }
+ else
+ {
+ if( vAbsNormal.z > 0.999f )
+ m_plane_Origin.type = PLANE_Z;
+ else
+ m_plane_Origin.type = PLANE_ANYZ;
+ }
+ }
+ else
+ {
+ if( vAbsNormal.y > vAbsNormal.z )
+ {
+ if( vAbsNormal.y > 0.999f )
+ m_plane_Origin.type = PLANE_Y;
+ else
+ m_plane_Origin.type = PLANE_ANYY;
+ }
+ else
+ {
+ if( vAbsNormal.z > 0.999f )
+ m_plane_Origin.type = PLANE_Z;
+ else
+ m_plane_Origin.type = PLANE_ANYZ;
+ }
+ }
+
+
+
+ if ( m_hLinkedPortal != NULL )
+ {
+ CProp_Portal_Shared::UpdatePortalTransformationMatrix( EntityToWorldTransform(), m_hLinkedPortal->EntityToWorldTransform(), &m_matrixThisToLinked );
+
+ m_hLinkedPortal->ResetModel();
+ //update the remote portal
+ MatrixInverseTR( m_matrixThisToLinked, m_hLinkedPortal->m_matrixThisToLinked );
+ }
+ else
+ {
+ m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
+ }
+}
+
+void CProp_Portal::UpdatePortalLinkage( void )
+{
+ if( m_bActivated )
+ {
+ CProp_Portal *pLink = m_hLinkedPortal.Get();
+
+ if( !(pLink && pLink->m_bActivated) )
+ {
+ //no old link, or inactive old link
+
+ if( pLink )
+ {
+ //we had an old link, must be inactive
+ if( pLink->m_hLinkedPortal.Get() != NULL )
+ pLink->UpdatePortalLinkage();
+
+ pLink = NULL;
+ }
+
+ int iPortalCount = s_PortalLinkageGroups[m_iLinkageGroupID].Count();
+
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = s_PortalLinkageGroups[m_iLinkageGroupID].Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pCurrentPortal = pPortals[i];
+ if( pCurrentPortal == this )
+ continue;
+ if( pCurrentPortal->m_bActivated && pCurrentPortal->m_hLinkedPortal.Get() == NULL )
+ {
+ pLink = pCurrentPortal;
+ pCurrentPortal->m_hLinkedPortal = this;
+ pCurrentPortal->UpdatePortalLinkage();
+ break;
+ }
+ }
+ }
+ }
+
+ m_hLinkedPortal = pLink;
+
+
+ if( pLink != NULL )
+ {
+ CHandle<CProp_Portal> hThis = this;
+ CHandle<CProp_Portal> hRemote = pLink;
+
+ this->m_hLinkedPortal = hRemote;
+ pLink->m_hLinkedPortal = hThis;
+ m_bIsPortal2 = !m_hLinkedPortal->m_bIsPortal2;
+
+ // Initialize mics/speakers
+ if( m_hMicrophone == 0 )
+ {
+ inputdata_t inputdata;
+
+ m_hMicrophone = CreateEntityByName( "env_microphone" );
+ CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
+ pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
+ pMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
+ DispatchSpawn( pMicrophone );
+
+ m_hSpeaker = CreateEntityByName( "env_speaker" );
+ CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
+
+ if( !m_bIsPortal2 )
+ {
+ pSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
+ pMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
+ pMicrophone->Activate();
+ pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
+ pMicrophone->SetSensitivity( 10.0f );
+ }
+ else
+ {
+ pSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
+ pMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
+ pMicrophone->Activate();
+ pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
+ pMicrophone->SetSensitivity( 10.0f );
+ }
+ }
+
+ if ( m_hLinkedPortal->m_hMicrophone == 0 )
+ {
+ inputdata_t inputdata;
+
+ m_hLinkedPortal->m_hMicrophone = CreateEntityByName( "env_microphone" );
+ CEnvMicrophone *pLinkedMicrophone = static_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
+ pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
+ pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
+ DispatchSpawn( pLinkedMicrophone );
+
+ m_hLinkedPortal->m_hSpeaker = CreateEntityByName( "env_speaker" );
+ CSpeaker *pLinkedSpeaker = static_cast<CSpeaker*>( m_hLinkedPortal->m_hSpeaker.Get() );
+
+ if ( !m_bIsPortal2 )
+ {
+ pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
+ pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
+ pLinkedMicrophone->Activate();
+ pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
+ pLinkedMicrophone->SetSensitivity( 10.0f );
+ }
+ else
+ {
+ pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
+ pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
+ pLinkedMicrophone->Activate();
+ pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
+ pLinkedMicrophone->SetSensitivity( 10.0f );
+ }
+ }
+
+ // Set microphone/speaker positions
+ Vector vZero( 0.0f, 0.0f, 0.0f );
+
+ CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
+ pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
+ pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
+ inputdata_t in;
+ pMicrophone->InputEnable( in );
+
+ CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
+ pSpeaker->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
+ pSpeaker->InputTurnOn( in );
+
+ UpdatePortalTeleportMatrix();
+ }
+ else
+ {
+ m_PortalSimulator.DetachFromLinked();
+ m_PortalSimulator.ReleaseAllEntityOwnership();
+ }
+
+ Vector ptCenter = GetAbsOrigin();
+ QAngle qAngles = GetAbsAngles();
+ m_PortalSimulator.MoveTo( ptCenter, qAngles );
+
+ if( pLink )
+ m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
+
+ if( m_pAttachedCloningArea )
+ m_pAttachedCloningArea->UpdatePosition();
+ }
+ else
+ {
+ CProp_Portal *pRemote = m_hLinkedPortal;
+ //apparently we've been deactivated
+ m_PortalSimulator.DetachFromLinked();
+ m_PortalSimulator.ReleaseAllEntityOwnership();
+
+ m_hLinkedPortal = NULL;
+ if( pRemote )
+ pRemote->UpdatePortalLinkage();
+ }
+}
+
+void CProp_Portal::PlacePortal( const Vector &vOrigin, const QAngle &qAngles, float fPlacementSuccess, bool bDelay /*= false*/ )
+{
+ Vector vOldOrigin = GetLocalOrigin();
+ QAngle qOldAngles = GetLocalAngles();
+
+ Vector vNewOrigin = vOrigin;
+ QAngle qNewAngles = qAngles;
+
+ UTIL_TestForOrientationVolumes( qNewAngles, vNewOrigin, this );
+
+ if ( sv_portal_placement_never_fail.GetBool() )
+ {
+ fPlacementSuccess = PORTAL_FIZZLE_SUCCESS;
+ }
+
+ if ( fPlacementSuccess < 0.5f )
+ {
+ // Prepare fizzle
+ m_vDelayedPosition = vOrigin;
+ m_qDelayedAngles = qAngles;
+
+ if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_CANT_FIT )
+ m_iDelayedFailure = PORTAL_FIZZLE_CANT_FIT;
+ else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED )
+ m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
+ else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_VOLUME )
+ m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
+ else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_SURFACE )
+ m_iDelayedFailure = PORTAL_FIZZLE_BAD_SURFACE;
+ else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE )
+ m_iDelayedFailure = PORTAL_FIZZLE_NONE;
+
+ CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
+
+ if( pPortalGun )
+ {
+ CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
+ if( pFiringPlayer )
+ {
+ g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
+ }
+ }
+
+ return;
+ }
+
+ if ( !bDelay )
+ {
+ m_vDelayedPosition = vNewOrigin;
+ m_qDelayedAngles = qNewAngles;
+ m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
+
+ NewLocation( vNewOrigin, qNewAngles );
+ }
+ else
+ {
+ m_vDelayedPosition = vNewOrigin;
+ m_qDelayedAngles = qNewAngles;
+ m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
+ }
+
+ CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
+
+ if( pPortalGun )
+ {
+ CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
+ if( pFiringPlayer )
+ {
+ g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
+ }
+ }
+}
+
+void CProp_Portal::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
+{
+ // Tell our physics environment to stop simulating it's entities.
+ // Fast moving objects can pass through the hole this frame while it's in the old location.
+ m_PortalSimulator.ReleaseAllEntityOwnership();
+ Vector vOldForward;
+ GetVectors( &vOldForward, 0, 0 );
+
+ m_vPrevForward = vOldForward;
+
+ WakeNearbyEntities();
+
+ Teleport( &vOrigin, &qAngles, 0 );
+
+ if ( m_hMicrophone )
+ {
+ CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
+ pMicrophone->Teleport( &vOrigin, &qAngles, 0 );
+ inputdata_t in;
+ pMicrophone->InputEnable( in );
+ }
+
+ if ( m_hSpeaker )
+ {
+ CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
+ pSpeaker->Teleport( &vOrigin, &qAngles, 0 );
+ inputdata_t in;
+ pSpeaker->InputTurnOn( in );
+ }
+
+ CreateSounds();
+
+ if ( m_pAmbientSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
+ }
+
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
+
+ //if the other portal should be static, let's not punch stuff resting on it
+ bool bOtherShouldBeStatic = false;
+ if( !m_hLinkedPortal )
+ bOtherShouldBeStatic = true;
+
+ m_bActivated = true;
+
+ UpdatePortalLinkage();
+ UpdatePortalTeleportMatrix();
+
+ // Update the four corners of this portal for faster reference
+ UpdateCorners();
+
+ WakeNearbyEntities();
+
+ if ( m_hLinkedPortal )
+ {
+ m_hLinkedPortal->WakeNearbyEntities();
+ if( !bOtherShouldBeStatic )
+ {
+ m_hLinkedPortal->PunchAllPenetratingPlayers();
+ }
+ }
+
+ if ( m_bIsPortal2 )
+ {
+ EmitSound( "Portal.open_red" );
+ }
+ else
+ {
+ EmitSound( "Portal.open_blue" );
+ }
+}
+
+void CProp_Portal::InputSetActivatedState( inputdata_t &inputdata )
+{
+ m_bActivated = inputdata.value.Bool();
+ m_hPlacedBy = NULL;
+
+ if ( m_bActivated )
+ {
+ Vector vOrigin;
+ vOrigin = GetAbsOrigin();
+
+ Vector vForward, vUp;
+ GetVectors( &vForward, 0, &vUp );
+
+ CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
+ UTIL_Portal_Trace_Filter( &baseFilter );
+ CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
+
+ trace_t tr;
+ UTIL_TraceLine( vOrigin + vForward, vOrigin + vForward * -8.0f, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ QAngle qAngles;
+ VectorAngles( tr.plane.normal, vUp, qAngles );
+
+ float fPlacementSuccess = VerifyPortalPlacement( this, tr.endpos, qAngles, PORTAL_PLACED_BY_FIXED );
+ PlacePortal( tr.endpos, qAngles, fPlacementSuccess );
+
+ // If the fixed portal is overlapping a portal that was placed before it... kill it!
+ if ( fPlacementSuccess )
+ {
+ IsPortalOverlappingOtherPortals( this, vOrigin, GetAbsAngles(), true );
+
+ CreateSounds();
+
+ if ( m_pAmbientSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
+ }
+
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
+ DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
+
+ if ( m_bIsPortal2 )
+ {
+ EmitSound( "Portal.open_red" );
+ }
+ else
+ {
+ EmitSound( "Portal.open_blue" );
+ }
+ }
+ }
+ else
+ {
+ if ( m_pAmbientSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
+ }
+
+ StopParticleEffects( this );
+ }
+
+ UpdatePortalTeleportMatrix();
+
+ UpdatePortalLinkage();
+}
+
+void CProp_Portal::InputFizzle( inputdata_t &inputdata )
+{
+ DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ Fizzle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Map can call new location, so far it's only for debugging purposes so it's not made to be very robust.
+// Input : &inputdata - String with 6 float entries with space delimiters, location and orientation
+//-----------------------------------------------------------------------------
+void CProp_Portal::InputNewLocation( inputdata_t &inputdata )
+{
+ char sLocationStats[MAX_PATH];
+ Q_strncpy( sLocationStats, inputdata.value.String(), sizeof(sLocationStats) );
+
+ // first 3 are location of new origin
+ Vector vNewOrigin;
+ char* pTok = strtok( sLocationStats, " " );
+ vNewOrigin.x = atof(pTok);
+ pTok = strtok( NULL, " " );
+ vNewOrigin.y = atof(pTok);
+ pTok = strtok( NULL, " " );
+ vNewOrigin.z = atof(pTok);
+
+ // Next 3 entries are new angles
+ QAngle vNewAngles;
+ pTok = strtok( NULL, " " );
+ vNewAngles.x = atof(pTok);
+ pTok = strtok( NULL, " " );
+ vNewAngles.y = atof(pTok);
+ pTok = strtok( NULL, " " );
+ vNewAngles.z = atof(pTok);
+
+ // Call main placement function (skipping placement rules)
+ NewLocation( vNewOrigin, vNewAngles );
+}
+
+void CProp_Portal::UpdateCorners()
+{
+ Vector vOrigin = GetAbsOrigin();
+ Vector vUp, vRight;
+ GetVectors( NULL, &vRight, &vUp );
+
+ for ( int i = 0; i < 4; ++i )
+ {
+ Vector vAddPoint = vOrigin;
+
+ vAddPoint += vRight * ((i & (1<<0))?(PORTAL_HALF_WIDTH):(-PORTAL_HALF_WIDTH));
+ vAddPoint += vUp * ((i & (1<<1))?(PORTAL_HALF_HEIGHT):(-PORTAL_HALF_HEIGHT));
+
+ m_vPortalCorners[i] = vAddPoint;
+ }
+}
+
+
+
+
+void CProp_Portal::ChangeLinkageGroup( unsigned char iLinkageGroupID )
+{
+ Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) != -1 );
+ s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
+ s_PortalLinkageGroups[iLinkageGroupID].AddToTail( this );
+ m_iLinkageGroupID = iLinkageGroupID;
+}
+
+
+
+CProp_Portal *CProp_Portal::FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound /*= false*/ )
+{
+ int iPortalCount = s_PortalLinkageGroups[iLinkageGroupID].Count();
+
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal *pFoundInactive = NULL;
+ CProp_Portal **pPortals = s_PortalLinkageGroups[iLinkageGroupID].Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ if( pPortals[i]->m_bIsPortal2 == bPortal2 )
+ {
+ if( pPortals[i]->m_bActivated )
+ return pPortals[i];
+ else
+ pFoundInactive = pPortals[i];
+ }
+ }
+
+ if( pFoundInactive )
+ return pFoundInactive;
+ }
+
+ if( bCreateIfNothingFound )
+ {
+ CProp_Portal *pPortal = (CProp_Portal *)CreateEntityByName( "prop_portal" );
+ pPortal->m_iLinkageGroupID = iLinkageGroupID;
+ pPortal->m_bIsPortal2 = bPortal2;
+ DispatchSpawn( pPortal );
+ return pPortal;
+ }
+
+ return NULL;
+}
+
+const CUtlVector<CProp_Portal *> *CProp_Portal::GetPortalLinkageGroup( unsigned char iLinkageGroupID )
+{
+ return &s_PortalLinkageGroups[iLinkageGroupID];
+}
+
+
+
diff --git a/game/server/portal/prop_portal.h b/game/server/portal/prop_portal.h
new file mode 100644
index 0000000..a866e9b
--- /dev/null
+++ b/game/server/portal/prop_portal.h
@@ -0,0 +1,166 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#ifndef PROP_PORTAL_H
+#define PROP_PORTAL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseanimating.h"
+#include "PortalSimulation.h"
+
+// FIX ME
+#include "portal_shareddefs.h"
+
+static const char *s_pDelayedPlacementContext = "DelayedPlacementContext";
+static const char *s_pTestRestingSurfaceContext = "TestRestingSurfaceContext";
+static const char *s_pFizzleThink = "FizzleThink";
+
+class CPhysicsCloneArea;
+
+class CProp_Portal : public CBaseAnimating, public CPortalSimulatorEventCallbacks
+{
+public:
+ DECLARE_CLASS( CProp_Portal, CBaseAnimating );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CProp_Portal( void );
+ virtual ~CProp_Portal( void );
+
+ CNetworkHandle( CProp_Portal, m_hLinkedPortal ); //the portal this portal is linked to
+
+
+ VMatrix m_matrixThisToLinked; //the matrix that will transform a point relative to this portal, to a point relative to the linked portal
+ CNetworkVar( bool, m_bActivated ); //a portal can exist and not be active
+ CNetworkVar( bool, m_bIsPortal2 ); //For teleportation, this doesn't matter, but for drawing and moving, it matters
+ Vector m_vPrevForward; //used for the indecisive push in find closest passable spaces when portal is moved
+
+ bool m_bSharedEnvironmentConfiguration; //this will be set by an instance of CPortal_Environment when two environments are in close proximity
+
+ EHANDLE m_hMicrophone; //the microphone for teleporting sound
+ EHANDLE m_hSpeaker; //the speaker for teleported sound
+
+ CSoundPatch *m_pAmbientSound;
+
+ Vector m_vAudioOrigin;
+ Vector m_vDelayedPosition;
+ QAngle m_qDelayedAngles;
+ int m_iDelayedFailure;
+ EHANDLE m_hPlacedBy;
+
+ COutputEvent m_OnPlacedSuccessfully; // Output in hammer for when this portal was successfully placed (not attempted and fizzed).
+
+ cplane_t m_plane_Origin; //a portal plane on the entity origin
+
+ CPhysicsCloneArea *m_pAttachedCloningArea;
+
+ bool IsPortal2() const;
+ void SetIsPortal2( bool bIsPortal2 );
+ const VMatrix& MatrixThisToLinked() const;
+
+ virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+
+ virtual void Precache( void );
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds( void );
+ virtual void Spawn( void );
+ virtual void Activate( void );
+ virtual void OnRestore( void );
+
+ virtual void UpdateOnRemove( void );
+
+ void DelayedPlacementThink( void );
+ void TestRestingSurfaceThink ( void );
+ void FizzleThink( void );
+
+ bool IsActivedAndLinked( void ) const;
+
+ void WakeNearbyEntities( void ); //wakes all nearby entities in-case there's been a significant change in how they can rest near a portal
+
+ void ForceEntityToFitInPortalWall( CBaseEntity *pEntity ); //projects an object's center into the middle of the portal wall hall, and traces back to where it wants to be
+
+ void PlacePortal( const Vector &vOrigin, const QAngle &qAngles, float fPlacementSuccess, bool bDelay = false );
+ void NewLocation( const Vector &vOrigin, const QAngle &qAngles );
+
+ void ResetModel( void ); //sets the model and bounding box
+ void DoFizzleEffect( int iEffect, bool bDelayedPos = true ); //display cool visual effect
+ void Fizzle( void ); //go inactive
+ void PunchPenetratingPlayer( CBaseEntity *pPlayer ); // adds outward force to player intersecting the portal plane
+ void PunchAllPenetratingPlayers( void ); // adds outward force to player intersecting the portal plane
+
+ virtual void StartTouch( CBaseEntity *pOther );
+ virtual void Touch( CBaseEntity *pOther );
+ virtual void EndTouch( CBaseEntity *pOther );
+ bool ShouldTeleportTouchingEntity( CBaseEntity *pOther ); //assuming the entity is or was just touching the portal, check for teleportation conditions
+ void TeleportTouchingEntity( CBaseEntity *pOther );
+ void InputSetActivatedState( inputdata_t &inputdata );
+ void InputFizzle( inputdata_t &inputdata );
+ void InputNewLocation( inputdata_t &inputdata );
+
+ void UpdatePortalLinkage( void );
+ void UpdatePortalTeleportMatrix( void ); //computes the transformation from this portal to the linked portal, and will update the remote matrix as well
+
+ //void SendInteractionMessage( CBaseEntity *pEntity, bool bEntering ); //informs clients that the entity is interacting with a portal (mostly used for clip planes)
+
+ bool SharedEnvironmentCheck( CBaseEntity *pEntity ); //does all testing to verify that the object is better handled with this portal instead of the other
+
+ // The four corners of the portal in worldspace, updated on placement. The four points will be coplanar on the portal plane.
+ Vector m_vPortalCorners[4];
+
+ CPortalSimulator m_PortalSimulator;
+
+ //virtual bool CreateVPhysics( void );
+ //virtual void VPhysicsDestroyObject( void );
+
+ virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
+
+ virtual void PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity );
+ virtual void PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity );
+
+private:
+ unsigned char m_iLinkageGroupID; //a group ID specifying which portals this one can possibly link to
+
+ CPhysCollide *m_pCollisionShape;
+ void RemovePortalMicAndSpeaker(); // Cleans up the portal's internal audio members
+ void UpdateCorners( void ); // Updates the four corners of this portal on spawn and placement
+
+public:
+ inline unsigned char GetLinkageGroup( void ) const { return m_iLinkageGroupID; };
+ void ChangeLinkageGroup( unsigned char iLinkageGroupID );
+
+ //find a portal with the designated attributes, or creates one with them, favors active portals over inactive
+ static CProp_Portal *FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound = false );
+ static const CUtlVector<CProp_Portal *> *GetPortalLinkageGroup( unsigned char iLinkageGroupID );
+};
+
+
+//-----------------------------------------------------------------------------
+// inline state querying methods
+//-----------------------------------------------------------------------------
+inline bool CProp_Portal::IsPortal2() const
+{
+ return m_bIsPortal2;
+}
+
+inline void CProp_Portal::SetIsPortal2( bool bIsPortal2 )
+{
+ m_bIsPortal2 = bIsPortal2;
+}
+
+inline const VMatrix& CProp_Portal::MatrixThisToLinked() const
+{
+ return m_matrixThisToLinked;
+}
+
+
+#endif //#ifndef PROP_PORTAL_H
diff --git a/game/server/portal/prop_portal_stats_display.cpp b/game/server/portal/prop_portal_stats_display.cpp
new file mode 100644
index 0000000..3e8ff86
--- /dev/null
+++ b/game/server/portal/prop_portal_stats_display.cpp
@@ -0,0 +1,478 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the big scary boom-boom machine Antlions fear.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "baseanimating.h"
+#include "portal_player.h"
+#include "EnvMessage.h"
+#include "fmtstr.h"
+#include "vguiscreen.h"
+#include "point_bonusmaps_accessor.h"
+#include "portal_shareddefs.h"
+
+
+#define PORTAL_STATS_DISPLAY_MODEL_NAME "models/props/Round_elevator_body.mdl"
+
+
+class CPropPortalStatsDisplay : public CBaseAnimating
+{
+public:
+
+ DECLARE_CLASS( CPropPortalStatsDisplay, CBaseAnimating );
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+
+ virtual ~CPropPortalStatsDisplay();
+
+ virtual int UpdateTransmitState();
+ virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
+
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void OnRestore( void );
+
+ void ScreenVisible( bool bVisible );
+
+ void Disable( void );
+ void Enable( void );
+
+ void InputDisable( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+
+ void InputUpdateStats( inputdata_t &inputdata );
+ void InputResetPlayerStats( inputdata_t &inputdata );
+
+private:
+
+ CNetworkVar( bool, m_bEnabled );
+
+ CNetworkVar( int, m_iNumPortalsPlaced );
+ CNetworkVar( int, m_iNumStepsTaken );
+ CNetworkVar( float, m_fNumSecondsTaken );
+
+ CNetworkVar( int, m_iBronzeObjective );
+ CNetworkVar( int, m_iSilverObjective );
+ CNetworkVar( int, m_iGoldObjective );
+
+ CNetworkString( szChallengeFileName, 128 );
+ CNetworkString( szChallengeMapName, 32 );
+ CNetworkString( szChallengeName, 32 );
+
+ CNetworkVar( int, m_iDisplayObjective );
+
+ COutputEvent m_OnMetBronzeObjective;
+ COutputEvent m_OnMetSilverObjective;
+ COutputEvent m_OnMetGoldObjective;
+ COutputEvent m_OnFailedAllObjectives;
+
+private:
+
+ // Control panel
+ void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName );
+ void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName );
+ void SpawnControlPanels( void );
+ void RestoreControlPanels( void );
+
+ typedef CHandle<CVGuiScreen> ScreenHandle_t;
+ CUtlVector<ScreenHandle_t> m_hScreens;
+
+};
+
+
+LINK_ENTITY_TO_CLASS( prop_portal_stats_display, CPropPortalStatsDisplay );
+
+//-----------------------------------------------------------------------------
+// Save/load
+//-----------------------------------------------------------------------------
+BEGIN_DATADESC( CPropPortalStatsDisplay )
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_iNumPortalsPlaced, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iNumStepsTaken, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fNumSecondsTaken, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_iBronzeObjective, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iSilverObjective, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iGoldObjective, FIELD_INTEGER ),
+
+ DEFINE_AUTO_ARRAY( szChallengeFileName, FIELD_CHARACTER ),
+ DEFINE_AUTO_ARRAY( szChallengeMapName, FIELD_CHARACTER ),
+ DEFINE_AUTO_ARRAY( szChallengeName, FIELD_CHARACTER ),
+
+ DEFINE_FIELD( m_iDisplayObjective, FIELD_INTEGER ),
+
+ //DEFINE_UTLVECTOR( m_hScreens, FIELD_EHANDLE ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "UpdateStats", InputUpdateStats ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ResetPlayerStats", InputResetPlayerStats ),
+
+ DEFINE_OUTPUT ( m_OnMetBronzeObjective, "OnMetBronzeObjective" ),
+ DEFINE_OUTPUT ( m_OnMetSilverObjective, "OnMetSilverObjective" ),
+ DEFINE_OUTPUT ( m_OnMetGoldObjective, "OnMetGoldObjective" ),
+ DEFINE_OUTPUT ( m_OnFailedAllObjectives, "OnFailedAllObjectives" ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CPropPortalStatsDisplay, DT_PropPortalStatsDisplay )
+ SendPropBool( SENDINFO(m_bEnabled) ),
+
+ SendPropInt( SENDINFO(m_iNumPortalsPlaced) ),
+ SendPropInt( SENDINFO(m_iNumStepsTaken) ),
+ SendPropFloat( SENDINFO(m_fNumSecondsTaken) ),
+
+ SendPropInt( SENDINFO(m_iBronzeObjective) ),
+ SendPropInt( SENDINFO(m_iSilverObjective) ),
+ SendPropInt( SENDINFO(m_iGoldObjective) ),
+
+ SendPropString( SENDINFO( szChallengeFileName ) ),
+ SendPropString( SENDINFO( szChallengeMapName ) ),
+ SendPropString( SENDINFO( szChallengeName ) ),
+
+ SendPropInt( SENDINFO(m_iDisplayObjective) ),
+END_SEND_TABLE()
+
+
+CPropPortalStatsDisplay::~CPropPortalStatsDisplay()
+{
+ int i;
+ // Kill the control panels
+ for ( i = m_hScreens.Count(); --i >= 0; )
+ {
+ DestroyVGuiScreen( m_hScreens[i].Get() );
+ }
+ m_hScreens.RemoveAll();
+}
+
+int CPropPortalStatsDisplay::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_FULLCHECK );
+}
+
+void CPropPortalStatsDisplay::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
+ return;
+
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Force our screens to be sent too.
+ for ( int i=0; i < m_hScreens.Count(); i++ )
+ {
+ CVGuiScreen *pScreen = m_hScreens[i].Get();
+ pScreen->SetTransmit( pInfo, bAlways );
+ }
+}
+
+void CPropPortalStatsDisplay::Spawn( void )
+{
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ szModel = PORTAL_STATS_DISPLAY_MODEL_NAME;
+ SetModelName( AllocPooledString(szModel) );
+ }
+
+ Precache();
+ SetModel( szModel );
+
+ SetSolid( SOLID_VPHYSICS );
+ VPhysicsInitStatic();
+
+ BaseClass::Spawn();
+
+ int iBronze, iSilver, iGold;
+ BonusMapChallengeObjectives( iBronze, iSilver, iGold );
+ m_iBronzeObjective = iBronze;
+ m_iSilverObjective = iSilver;
+ m_iGoldObjective = iGold;
+
+ BonusMapChallengeNames( szChallengeFileName.GetForModify(), szChallengeMapName.GetForModify(), szChallengeName.GetForModify() );
+
+ m_bEnabled = false;
+
+ int iSequence = SelectHeaviestSequence ( ACT_IDLE );
+
+ if ( iSequence != ACT_INVALID )
+ {
+ SetSequence( iSequence );
+ ResetSequenceInfo();
+
+ //Do this so we get the nice ramp-up effect.
+ m_flPlaybackRate = random->RandomFloat( 0.0f, 1.0f );
+ }
+
+ SpawnControlPanels();
+
+ ScreenVisible( m_bEnabled );
+}
+
+void CPropPortalStatsDisplay::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( STRING( GetModelName() ) );
+
+ PrecacheVGuiScreen( "portal_stats_display_screen" );
+}
+
+void CPropPortalStatsDisplay::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ RestoreControlPanels();
+
+ ScreenVisible( m_bEnabled );
+}
+
+void CPropPortalStatsDisplay::ScreenVisible( bool bVisible )
+{
+ for ( int iScreen = 0; iScreen < m_hScreens.Count(); ++iScreen )
+ {
+ CVGuiScreen *pScreen = m_hScreens[ iScreen ].Get();
+ if ( bVisible )
+ pScreen->RemoveEffects( EF_NODRAW );
+ else
+ pScreen->AddEffects( EF_NODRAW );
+ }
+}
+
+void CPropPortalStatsDisplay::Disable( void )
+{
+ if ( !m_bEnabled )
+ return;
+
+ m_bEnabled = false;
+
+ ScreenVisible( false );
+}
+
+void CPropPortalStatsDisplay::Enable( void )
+{
+ if ( m_bEnabled )
+ return;
+
+ // Don't show stats display in non challenge mode!
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ if ( pPlayer && pPlayer->GetBonusChallenge() == 0 )
+ return;
+
+ m_bEnabled = true;
+
+ m_iDisplayObjective = pPlayer->GetBonusChallenge() - 1;
+
+ // Check if they beat objectives
+ if ( pPlayer->GetBonusProgress() <= m_iBronzeObjective )
+ m_OnMetBronzeObjective.FireOutput( this, this );
+ else if ( pPlayer->GetBonusProgress() <= m_iSilverObjective )
+ m_OnMetSilverObjective.FireOutput( this, this );
+ else if ( pPlayer->GetBonusProgress() <= m_iGoldObjective )
+ m_OnMetGoldObjective.FireOutput( this, this );
+ else
+ m_OnFailedAllObjectives.FireOutput( this, this );
+
+ ScreenVisible( true );
+}
+
+void CPropPortalStatsDisplay::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+void CPropPortalStatsDisplay::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+void CPropPortalStatsDisplay::InputUpdateStats( inputdata_t &inputdata )
+{
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+ if( pPlayer == NULL )
+ pPlayer = GetPortalPlayer( 1 ); //last ditch effort
+
+ if( pPlayer )
+ {
+ m_iNumPortalsPlaced = pPlayer->NumPortalsPlaced();
+ m_iNumStepsTaken = pPlayer->NumStepsTaken();
+ m_fNumSecondsTaken = pPlayer->NumSecondsTaken();
+
+ // Now that we've recorded it, don't let it change
+ pPlayer->PauseBonusProgress();
+ }
+}
+
+void CPropPortalStatsDisplay::InputResetPlayerStats( inputdata_t &inputdata )
+{
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+ if( pPlayer == NULL )
+ pPlayer = GetPortalPlayer( 1 ); //last ditch effort
+
+ if( pPlayer )
+ {
+ pPlayer->ResetThisLevelStats();
+ }
+}
+
+void CPropPortalStatsDisplay::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "portal_stats_display_screen";
+}
+
+void CPropPortalStatsDisplay::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "vgui_screen";
+}
+
+//-----------------------------------------------------------------------------
+// This is called by the base object when it's time to spawn the control panels
+//-----------------------------------------------------------------------------
+void CPropPortalStatsDisplay::SpawnControlPanels()
+{
+ char buf[64];
+
+ // FIXME: Deal with dynamically resizing control panels?
+
+ // If we're attached to an entity, spawn control panels on it instead of use
+ CBaseAnimating *pEntityToSpawnOn = this;
+ char *pOrgLL = "statPanel%d_bl";
+ char *pOrgUR = "statPanel%d_tr";
+ char *pAttachmentNameLL = pOrgLL;
+ char *pAttachmentNameUR = pOrgUR;
+
+ Assert( pEntityToSpawnOn );
+
+ // Lookup the attachment point...
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
+ int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ pEntityToSpawnOn = this;
+ Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
+ nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ return;
+ }
+
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
+ int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
+ nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ return;
+ }
+
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ // Compute the screen size from the attachment points...
+ matrix3x4_t panelToWorld;
+ pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
+
+ matrix3x4_t worldToPanel;
+ MatrixInvert( panelToWorld, worldToPanel );
+
+ // Now get the lower right position + transform into panel space
+ Vector lr, lrlocal;
+ pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
+ MatrixGetColumn( panelToWorld, 3, lr );
+ VectorTransform( lr, worldToPanel, lrlocal );
+
+ float flWidth = lrlocal.x;
+ float flHeight = lrlocal.y;
+
+ CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
+ pScreen->ChangeTeam( GetTeamNumber() );
+ pScreen->SetActualSize( flWidth, flHeight );
+ pScreen->SetActive( true );
+ pScreen->MakeVisibleOnlyToTeammates( false );
+ pScreen->SetTransparency( true );
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+ }
+}
+
+void CPropPortalStatsDisplay::RestoreControlPanels( void )
+{
+ char buf[64];
+
+ // FIXME: Deal with dynamically resizing control panels?
+
+ // If we're attached to an entity, spawn control panels on it instead of use
+ CBaseAnimating *pEntityToSpawnOn = this;
+ char *pOrgLL = "statPanel%d_bl";
+ char *pOrgUR = "statPanel%d_tr";
+ char *pAttachmentNameLL = pOrgLL;
+ char *pAttachmentNameUR = pOrgUR;
+
+ Assert( pEntityToSpawnOn );
+
+ // Lookup the attachment point...
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
+ int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ pEntityToSpawnOn = this;
+ Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
+ nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ return;
+ }
+
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
+ int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
+ nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ return;
+ }
+
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ CVGuiScreen *pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( NULL, pScreenClassname );
+
+ while ( pScreen && pScreen->GetOwnerEntity() != this && Q_strcmp( pScreen->GetPanelName(), pScreenName ) == 0 )
+ {
+ pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( pScreen, pScreenClassname );
+ }
+
+ if ( pScreen )
+ {
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+ }
+ }
+} \ No newline at end of file
diff --git a/game/server/portal/prop_telescopic_arm.cpp b/game/server/portal/prop_telescopic_arm.cpp
new file mode 100644
index 0000000..1f38dd5
--- /dev/null
+++ b/game/server/portal/prop_telescopic_arm.cpp
@@ -0,0 +1,538 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the big scary boom-boom machine Antlions fear.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "baseentity.h"
+#include "rotorwash.h"
+#include "soundenvelope.h"
+#include "engine/IEngineSound.h"
+#include "te_effect_dispatch.h"
+#include "point_posecontroller.h"
+#include "prop_portal_shared.h"
+
+
+#define TELESCOPE_ENABLE_TIME 1.0f
+#define TELESCOPE_DISABLE_TIME 2.0f
+#define TELESCOPE_ROTATEX_TIME 0.5f
+#define TELESCOPE_ROTATEY_TIME 0.5f
+
+#define TELESCOPING_ARM_MODEL_NAME "models/props/telescopic_arm.mdl"
+
+#define DEBUG_TELESCOPIC_ARM_AIM 1
+
+
+class CPropTelescopicArm : public CBaseAnimating
+{
+public:
+ DECLARE_CLASS( CPropTelescopicArm, CBaseAnimating );
+ DECLARE_DATADESC();
+
+ virtual void UpdateOnRemove( void );
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void Activate ( void );
+
+ void DisabledThink( void );
+ void EnabledThink( void );
+
+ void AimAt( Vector vTarget );
+ bool TestLOS( const Vector& vAimPoint );
+ void SetTarget( const char *pTargetName );
+ void SetTarget( CBaseEntity *pTarget );
+
+ void InputDisable( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+
+ void InputSetTarget( inputdata_t &inputdata );
+ void InputTargetPlayer( inputdata_t &inputdata );
+
+private:
+
+ Vector FindTargetAimPoint( void );
+ Vector FindAimPointThroughPortal ( const CProp_Portal* pPortal );
+
+ bool m_bEnabled;
+ bool m_bCanSeeTarget;
+ int m_iFrontMarkerAttachment;
+
+ EHANDLE m_hRotXPoseController;
+ EHANDLE m_hRotYPoseController;
+ EHANDLE m_hTelescopicPoseController;
+
+ EHANDLE m_hAimTarget;
+
+ COutputEvent m_OnLostTarget;
+ COutputEvent m_OnFoundTarget;
+};
+
+LINK_ENTITY_TO_CLASS( prop_telescopic_arm, CPropTelescopicArm );
+
+//-----------------------------------------------------------------------------
+// Save/load
+//-----------------------------------------------------------------------------
+BEGIN_DATADESC( CPropTelescopicArm )
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bCanSeeTarget, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iFrontMarkerAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hRotXPoseController, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hRotYPoseController, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hTelescopicPoseController, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hAimTarget, FIELD_EHANDLE ),
+ DEFINE_THINKFUNC( DisabledThink ),
+ DEFINE_THINKFUNC( EnabledThink ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TargetPlayer", InputTargetPlayer ),
+ DEFINE_OUTPUT ( m_OnLostTarget, "OnLostTarget" ),
+ DEFINE_OUTPUT ( m_OnFoundTarget, "OnFoundTarget" ),
+END_DATADESC()
+
+
+void CPropTelescopicArm::UpdateOnRemove( void )
+{
+ CPoseController *pPoseController;
+
+ pPoseController = static_cast<CPoseController*>( m_hRotXPoseController.Get() );
+ if ( pPoseController )
+ UTIL_Remove( pPoseController );
+ m_hRotXPoseController = 0;
+
+ pPoseController = static_cast<CPoseController*>( m_hRotYPoseController.Get() );
+ if ( pPoseController )
+ UTIL_Remove( pPoseController );
+ m_hRotYPoseController = 0;
+
+ pPoseController = static_cast<CPoseController*>( m_hTelescopicPoseController.Get() );
+ if ( pPoseController )
+ UTIL_Remove( pPoseController );
+ m_hTelescopicPoseController = 0;
+}
+
+void CPropTelescopicArm::Spawn( void )
+{
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ szModel = TELESCOPING_ARM_MODEL_NAME;
+ SetModelName( AllocPooledString(szModel) );
+ }
+
+ Precache();
+ SetModel( szModel );
+
+ SetSolid( SOLID_VPHYSICS );
+ SetMoveType( MOVETYPE_PUSH );
+ VPhysicsInitStatic();
+
+ BaseClass::Spawn();
+
+ m_bEnabled = false;
+ m_bCanSeeTarget = false;
+
+ SetThink( &CPropTelescopicArm::DisabledThink );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+
+ int iSequence = SelectHeaviestSequence ( ACT_IDLE );
+
+ if ( iSequence != ACT_INVALID )
+ {
+ SetSequence( iSequence );
+ ResetSequenceInfo();
+
+ //Do this so we get the nice ramp-up effect.
+ m_flPlaybackRate = random->RandomFloat( 0.0f, 1.0f );
+ }
+
+ m_iFrontMarkerAttachment = LookupAttachment( "Front_marker" );
+
+ CPoseController *pPoseController;
+
+ pPoseController = static_cast<CPoseController*>( CreateEntityByName( "point_posecontroller" ) );
+ DispatchSpawn( pPoseController );
+ if ( pPoseController )
+ {
+ pPoseController->SetProp( this );
+ pPoseController->SetInterpolationWrap( true );
+ pPoseController->SetPoseParameterName( "rot_x" );
+ m_hRotXPoseController = pPoseController;
+ }
+
+ pPoseController = static_cast<CPoseController*>( CreateEntityByName( "point_posecontroller" ) );
+ DispatchSpawn( pPoseController );
+ if ( pPoseController )
+ {
+ pPoseController->SetProp( this );
+ pPoseController->SetInterpolationWrap( true );
+ pPoseController->SetPoseParameterName( "rot_y" );
+ m_hRotYPoseController = pPoseController;
+ }
+
+ pPoseController = static_cast<CPoseController*>( CreateEntityByName( "point_posecontroller" ) );
+ DispatchSpawn( pPoseController );
+ if ( pPoseController )
+ {
+ pPoseController->SetProp( this );
+ pPoseController->SetPoseParameterName( "telescopic" );
+ m_hTelescopicPoseController = pPoseController;
+ }
+}
+
+void CPropTelescopicArm::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( STRING( GetModelName() ) );
+ PrecacheScriptSound( "coast.thumper_hit" );
+ PrecacheScriptSound( "coast.thumper_ambient" );
+ PrecacheScriptSound( "coast.thumper_dust" );
+ PrecacheScriptSound( "coast.thumper_startup" );
+ PrecacheScriptSound( "coast.thumper_shutdown" );
+ PrecacheScriptSound( "coast.thumper_large_hit" );
+}
+
+void CPropTelescopicArm::Activate( void )
+{
+ BaseClass::Activate();
+}
+
+void CPropTelescopicArm::DisabledThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 1.0 );
+}
+
+void CPropTelescopicArm::EnabledThink( void )
+{
+ CBaseEntity *pTarget = m_hAimTarget.Get();
+
+ if ( !pTarget )
+ {
+ //SetTarget ( UTIL_PlayerByIndex( 1 ) );
+
+ // Default to targeting a player
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer && FVisible( pPlayer ) && pPlayer->IsAlive() )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+ if( pTarget == NULL )
+ {
+ //search again, but don't require the player to be visible
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer && pPlayer->IsAlive() )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+
+ if( pTarget == NULL )
+ {
+ //search again, but don't require the player to be visible or alive
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+ }
+ }
+
+ if( pTarget )
+ SetTarget( pTarget );
+ }
+
+ if ( pTarget )
+ {
+ // Aim at the center of the abs box
+ Vector vAimPoint = FindTargetAimPoint();
+ Assert ( vAimPoint != vec3_invalid );
+ AimAt( vAimPoint );
+
+ // We have direct line of sight to our target
+ if ( TestLOS ( vAimPoint ) )
+ {
+ // Just aquired LOS
+ if ( !m_bCanSeeTarget )
+ {
+ m_OnFoundTarget.FireOutput( m_hAimTarget, this );
+ }
+ m_bCanSeeTarget = true;
+ }
+ // No LOS to target
+ else
+ {
+ // Just lost LOS
+ if ( m_bCanSeeTarget )
+ {
+ m_OnLostTarget.FireOutput( m_hAimTarget, this );
+ }
+ m_bCanSeeTarget = false;
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the point to aim at in order to see the target entity.
+// Note: Also considers aim paths through portals.
+// Output : Point of the target
+//-----------------------------------------------------------------------------
+Vector CPropTelescopicArm::FindTargetAimPoint( void )
+{
+ CBaseEntity *pTarget = m_hAimTarget.Get();
+
+ if ( !pTarget )
+ {
+ // No target to aim at, can't return meaningful info
+ Warning( "CPropTelescopicArm::FindTargetAimPoint called with no valid target entity." );
+ return vec3_invalid;
+ }
+ else
+ {
+ Vector vFrontPoint;
+ GetAttachment( m_iFrontMarkerAttachment, vFrontPoint, NULL, NULL, NULL );
+
+ // Aim at the target through the world
+ Vector vAimPoint = pTarget->GetAbsOrigin() + ( pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs() ) * 0.5f;
+ //float fDistToPoint = vFrontPoint.DistToSqr( vAimPoint );
+
+ CProp_Portal *pShortestDistPortal = NULL;
+ UTIL_Portal_ShortestDistance( vFrontPoint, vAimPoint, &pShortestDistPortal, true );
+
+ Vector ptShortestAimPoint;
+ if( pShortestDistPortal )
+ {
+ ptShortestAimPoint = FindAimPointThroughPortal( pShortestDistPortal );
+ if( ptShortestAimPoint == vec3_invalid )
+ ptShortestAimPoint = vAimPoint;
+ }
+ else
+ {
+ ptShortestAimPoint = vAimPoint;
+ }
+
+ return ptShortestAimPoint;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the center of the target entity as seen through the specified portal
+// Input : pPortal - The portal to look through
+// Output : Vector& output point in world space where the target *appears* to be as seen through the portal
+//-----------------------------------------------------------------------------
+Vector CPropTelescopicArm::FindAimPointThroughPortal( const CProp_Portal* pPortal )
+{
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ CProp_Portal* pLinked = pPortal->m_hLinkedPortal.Get();
+ CBaseEntity* pTarget = m_hAimTarget.Get();
+
+ if ( pLinked && pLinked->m_bActivated && pTarget )
+ {
+ VMatrix matToPortalView = pLinked->m_matrixThisToLinked;
+ Vector vTargetAimPoint = pTarget->GetAbsOrigin() + ( pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs() ) * 0.5f;
+
+ return matToPortalView * vTargetAimPoint;
+ }
+ }
+
+ // Bad portal pointer, not linked, no target or otherwise failed
+ return vec3_invalid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tests if this prop's front point has direct line of sight to it's target entity
+// Input : vAimPoint - The point to aim at
+// Output : Returns true if target is in direct line of sight, false otherwise.
+//-----------------------------------------------------------------------------
+bool CPropTelescopicArm::TestLOS( const Vector& vAimPoint )
+{
+ // Test for LOS and fire outputs if the sight condition changes
+ Vector vFaceOrigin;
+ trace_t tr;
+ GetAttachment( m_iFrontMarkerAttachment, vFaceOrigin, NULL, NULL, NULL );
+ Ray_t ray;
+ ray.Init( vFaceOrigin, vAimPoint );
+ ray.m_IsRay = true;
+
+ // This aim point does hit target, now make sure there are no blocking objects in the way
+ CTraceFilterWorldAndPropsOnly filter;
+ UTIL_Portal_TraceRay( ray, MASK_SHOT, &filter, &tr );
+ return !(tr.fraction < 1.0f);
+}
+
+void CPropTelescopicArm::AimAt( Vector vTarget )
+{
+ Vector vFaceOrigin;
+ GetAttachment( m_iFrontMarkerAttachment, vFaceOrigin, NULL, NULL, NULL );
+
+ Vector vNormalToTarget = vTarget - vFaceOrigin;
+ VectorNormalize( vNormalToTarget );
+
+ VMatrix vWorldToLocalRotation = EntityToWorldTransform();
+ vNormalToTarget = vWorldToLocalRotation.InverseTR().ApplyRotation( vNormalToTarget );
+
+ Vector vUp;
+ GetVectors( NULL, NULL, &vUp );
+
+ QAngle qAnglesToTarget;
+ VectorAngles( vNormalToTarget, vUp, qAnglesToTarget );
+
+ float fNewX = ( qAnglesToTarget.x + 90.0f ) / 360.0f;
+ float fNewY = qAnglesToTarget.y / 360.0f;
+
+ if ( fNewY < 0.0f )
+ fNewY += 1.0f;
+
+ CPoseController *pPoseController = static_cast<CPoseController*>( m_hRotXPoseController.Get() );
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_ROTATEX_TIME );
+ pPoseController->SetPoseValue( fNewX );
+ }
+
+ pPoseController = static_cast<CPoseController*>( m_hRotYPoseController.Get() );
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_ROTATEY_TIME );
+ pPoseController->SetPoseValue( fNewY );
+ }
+}
+
+void CPropTelescopicArm::SetTarget( const char *pchTargetName )
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pchTargetName, NULL, NULL );
+
+ //if ( pTarget == NULL )
+ // pTarget = UTIL_PlayerByIndex( 1 );
+
+ return SetTarget( pTarget );
+}
+
+void CPropTelescopicArm::SetTarget( CBaseEntity *pTarget )
+{
+ m_hAimTarget = pTarget;
+}
+
+void CPropTelescopicArm::InputDisable( inputdata_t &inputdata )
+{
+ if ( m_bEnabled )
+ {
+ m_bEnabled = false;
+
+ EmitSound( "coast.thumper_shutdown" );
+
+ CPoseController *pPoseController;
+
+ pPoseController = static_cast<CPoseController*>( m_hRotXPoseController.Get() );
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_DISABLE_TIME * 0.5f );
+ pPoseController->SetPoseValue( 0.0f );
+ }
+
+ pPoseController = static_cast<CPoseController*>( m_hRotYPoseController.Get() );
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_DISABLE_TIME * 0.5f );
+ pPoseController->SetPoseValue( 0.0f );
+ }
+
+ pPoseController = static_cast<CPoseController*>( m_hTelescopicPoseController.Get() );
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_DISABLE_TIME );
+ pPoseController->SetPoseValue( 0.0f );
+ }
+
+ SetThink( &CPropTelescopicArm::DisabledThink );
+ SetNextThink( gpGlobals->curtime + TELESCOPE_DISABLE_TIME );
+ }
+}
+
+void CPropTelescopicArm::InputEnable( inputdata_t &inputdata )
+{
+ if ( !m_bEnabled )
+ {
+ m_bEnabled = true;
+
+ EmitSound( "coast.thumper_startup" );
+
+ CPoseController *pPoseController;
+ pPoseController = static_cast<CPoseController*>( m_hTelescopicPoseController.Get() );
+
+ if ( pPoseController )
+ {
+ pPoseController->SetInterpolationTime( TELESCOPE_ENABLE_TIME );
+ pPoseController->SetPoseValue( 1.0f );
+ }
+
+ SetThink( &CPropTelescopicArm::EnabledThink );
+ SetNextThink( gpGlobals->curtime + TELESCOPE_ENABLE_TIME );
+ }
+}
+
+void CPropTelescopicArm::InputSetTarget( inputdata_t &inputdata )
+{
+ SetTarget( inputdata.value.String() );
+}
+
+void CPropTelescopicArm::InputTargetPlayer( inputdata_t &inputdata )
+{
+ //SetTarget( UTIL_PlayerByIndex( 1 ) );
+
+ CBaseEntity *pTarget = NULL;
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer && FVisible( pPlayer ) && pPlayer->IsAlive() )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+ if( pTarget == NULL )
+ {
+ //search again, but don't require the player to be visible
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer && pPlayer->IsAlive() )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+
+ if( pTarget == NULL )
+ {
+ //search again, but don't require the player to be visible or alive
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if( pPlayer )
+ {
+ pTarget = pPlayer;
+ break;
+ }
+ }
+ }
+ }
+
+ if( pTarget )
+ SetTarget( pTarget );
+} \ No newline at end of file
diff --git a/game/server/portal/trigger_portal_cleanser.cpp b/game/server/portal/trigger_portal_cleanser.cpp
new file mode 100644
index 0000000..97fffc3
--- /dev/null
+++ b/game/server/portal/trigger_portal_cleanser.cpp
@@ -0,0 +1,269 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A volume which bumps portal placement. Keeps a global list loaded in from the map
+// and provides an interface with which prop_portal can get this list and avoid successfully
+// creating portals partially inside the volume.
+//
+// $NoKeywords: $
+//======================================================================================//
+
+#include "cbase.h"
+#include "triggers.h"
+#include "portal_player.h"
+#include "weapon_portalgun.h"
+#include "prop_portal_shared.h"
+#include "portal_shareddefs.h"
+#include "physobj.h"
+#include "portal/weapon_physcannon.h"
+#include "model_types.h"
+#include "rumble_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static char *g_pszPortalNonCleansable[] =
+{
+ "func_door",
+ "func_door_rotating",
+ "prop_door_rotating",
+ "func_tracktrain",
+ "env_ghostanimating",
+ "physicsshadowclone",
+ "prop_energy_ball",
+ NULL,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes anything that touches it. If the trigger has a targetname,
+// firing it will toggle state.
+//-----------------------------------------------------------------------------
+class CTriggerPortalCleanser : public CBaseTrigger
+{
+public:
+ DECLARE_CLASS( CTriggerPortalCleanser, CBaseTrigger );
+
+ void Spawn( void );
+ void Touch( CBaseEntity *pOther );
+
+ DECLARE_DATADESC();
+
+ // Outputs
+ COutputEvent m_OnDissolve;
+ COutputEvent m_OnFizzle;
+ COutputEvent m_OnDissolveBox;
+};
+
+BEGIN_DATADESC( CTriggerPortalCleanser )
+
+// Outputs
+DEFINE_OUTPUT( m_OnDissolve, "OnDissolve" ),
+DEFINE_OUTPUT( m_OnFizzle, "OnFizzle" ),
+DEFINE_OUTPUT( m_OnDissolveBox, "OnDissolveBox" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( trigger_portal_cleanser, CTriggerPortalCleanser );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTriggerPortalCleanser::Spawn( void )
+{
+ BaseClass::Spawn();
+ InitTrigger();
+}
+
+// Creates a base entity with model/physics matching the parameter ent.
+// Used to avoid higher level functions on a disolving entity, which should be inert
+// and not react the way it used to (touches, etc).
+// Uses simple physics entities declared in physobj.cpp
+CBaseEntity* ConvertToSimpleProp ( CBaseEntity* pEnt )
+{
+ CBaseEntity *pRetVal = NULL;
+ int modelindex = pEnt->GetModelIndex();
+ const model_t *model = modelinfo->GetModel( modelindex );
+ if ( model && modelinfo->GetModelType(model) == mod_brush )
+ {
+ pRetVal = CreateEntityByName( "simple_physics_brush" );
+ }
+ else
+ {
+ pRetVal = CreateEntityByName( "simple_physics_prop" );
+ }
+
+ pRetVal->KeyValue( "model", STRING(pEnt->GetModelName()) );
+ pRetVal->SetAbsOrigin( pEnt->GetAbsOrigin() );
+ pRetVal->SetAbsAngles( pEnt->GetAbsAngles() );
+ pRetVal->Spawn();
+ pRetVal->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false );
+
+ return pRetVal;
+}
+
+
+void CTriggerPortalCleanser::Touch( CBaseEntity *pOther )
+{
+ if ( !PassesTriggerFilters( pOther ) )
+ return;
+
+ if ( pOther->IsPlayer() )
+ {
+ CPortal_Player *pPlayer = ToPortalPlayer( pOther );
+
+ if ( pPlayer )
+ {
+ CWeaponPortalgun *pPortalgun = dynamic_cast<CWeaponPortalgun*>( pPlayer->Weapon_OwnsThisType( "weapon_portalgun" ) );
+
+ if ( pPortalgun )
+ {
+ bool bFizzledPortal = false;
+
+ if ( pPortalgun->CanFirePortal1() )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( pPortalgun->m_iPortalLinkageGroupID, false );
+
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ pPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pPortal->Fizzle();
+ // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ pPortalgun->m_fEffectsMaxSize1 = 50.0f;
+
+ bFizzledPortal = true;
+ }
+
+ // Cancel portals that are still mid flight
+ if ( pPortal && pPortal->GetNextThink( s_pDelayedPlacementContext ) > gpGlobals->curtime )
+ {
+ pPortal->SetContextThink( NULL, gpGlobals->curtime, s_pDelayedPlacementContext );
+ pPortalgun->m_fEffectsMaxSize2 = 50.0f;
+ bFizzledPortal = true;
+ }
+ }
+
+ if ( pPortalgun->CanFirePortal2() )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( pPortalgun->m_iPortalLinkageGroupID, true );
+
+ if ( pPortal && pPortal->m_bActivated )
+ {
+ pPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pPortal->Fizzle();
+ // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ pPortalgun->m_fEffectsMaxSize2 = 50.0f;
+
+ bFizzledPortal = true;
+ }
+
+ // Cancel portals that are still mid flight
+ if ( pPortal && pPortal->GetNextThink( s_pDelayedPlacementContext ) > gpGlobals->curtime )
+ {
+ pPortal->SetContextThink( NULL, gpGlobals->curtime, s_pDelayedPlacementContext );
+ pPortalgun->m_fEffectsMaxSize2 = 50.0f;
+ bFizzledPortal = true;
+ }
+ }
+
+ if ( bFizzledPortal )
+ {
+ pPortalgun->SendWeaponAnim( ACT_VM_FIZZLE );
+ pPortalgun->SetLastFiredPortal( 0 );
+ m_OnFizzle.FireOutput( pOther, this );
+ pPlayer->RumbleEffect( RUMBLE_RPG_MISSILE, 0, RUMBLE_FLAG_RESTART );
+ }
+ }
+ }
+
+ return;
+ }
+
+ CBaseAnimating *pBaseAnimating = dynamic_cast<CBaseAnimating*>( pOther );
+
+ if ( pBaseAnimating && !pBaseAnimating->IsDissolving() )
+ {
+ int i = 0;
+
+ while ( g_pszPortalNonCleansable[ i ] )
+ {
+ if ( FClassnameIs( pBaseAnimating, g_pszPortalNonCleansable[ i ] ) )
+ {
+ // Don't dissolve non cleansable objects
+ return;
+ }
+
+ ++i;
+ }
+
+ // The portal weight box, used for puzzles in the portal mod is differentiated by its name
+ // always being 'box'. We use special logic when the cleanser dissolves a box so this is a special output for it.
+ if ( pBaseAnimating->NameMatches( "box" ) )
+ {
+ m_OnDissolveBox.FireOutput( pOther, this );
+ }
+
+ if ( FClassnameIs( pBaseAnimating, "updateitem2" ) )
+ {
+ pBaseAnimating->EmitSound( "UpdateItem.Fizzle" );
+ }
+
+ Vector vOldVel;
+ AngularImpulse vOldAng;
+ pBaseAnimating->GetVelocity( &vOldVel, &vOldAng );
+
+ IPhysicsObject* pOldPhys = pBaseAnimating->VPhysicsGetObject();
+
+ if ( pOldPhys && ( pOldPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
+ {
+ CPortal_Player *pPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pBaseAnimating );
+ if( pPlayer )
+ {
+ // Modify the velocity for held objects so it gets away from the player
+ pPlayer->ForceDropOfCarriedPhysObjects( pBaseAnimating );
+
+ pPlayer->GetAbsVelocity();
+ vOldVel = pPlayer->GetAbsVelocity() + Vector( pPlayer->EyeDirection2D().x * 4.0f, pPlayer->EyeDirection2D().y * 4.0f, -32.0f );
+ }
+ }
+
+ // Swap object with an disolving physics model to avoid touch logic
+ CBaseEntity *pDisolvingObj = ConvertToSimpleProp( pBaseAnimating );
+ if ( pDisolvingObj )
+ {
+ // Remove old prop, transfer name and children to the new simple prop
+ pDisolvingObj->SetName( pBaseAnimating->GetEntityName() );
+ UTIL_TransferPoseParameters( pBaseAnimating, pDisolvingObj );
+ TransferChildren( pBaseAnimating, pDisolvingObj );
+ pDisolvingObj->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ pBaseAnimating->AddSolidFlags( FSOLID_NOT_SOLID );
+ pBaseAnimating->AddEffects( EF_NODRAW );
+
+ IPhysicsObject* pPhys = pDisolvingObj->VPhysicsGetObject();
+ if ( pPhys )
+ {
+ pPhys->EnableGravity( false );
+
+ Vector vVel = vOldVel;
+ AngularImpulse vAng = vOldAng;
+
+ // Disolving hurts, damp and blur the motion a little
+ vVel *= 0.5f;
+ vAng.z += 20.0f;
+
+ pPhys->SetVelocity( &vVel, &vAng );
+ }
+
+ pBaseAnimating->AddFlag( FL_DISSOLVING );
+ UTIL_Remove( pBaseAnimating );
+ }
+
+ CBaseAnimating *pDisolvingAnimating = dynamic_cast<CBaseAnimating*>( pDisolvingObj );
+ if ( pDisolvingAnimating )
+ {
+ pDisolvingAnimating->Dissolve( "", gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL );
+ }
+
+ m_OnDissolve.FireOutput( pOther, this );
+ }
+} \ No newline at end of file
diff --git a/game/server/portal/weapon_physcannon.cpp b/game/server/portal/weapon_physcannon.cpp
new file mode 100644
index 0000000..215bb91
--- /dev/null
+++ b/game/server/portal/weapon_physcannon.cpp
@@ -0,0 +1,4934 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Physics cannon
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "player.h"
+#include "weapon_portalgun.h"
+#include "prop_portal_shared.h"
+#include "portal_util_shared.h"
+#include "gamerules.h"
+#include "soundenvelope.h"
+#include "engine/IEngineSound.h"
+#include "physics.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "IEffects.h"
+#include "ndebugoverlay.h"
+#include "shake.h"
+#include "portal_player.h"
+#include "hl2_player.h"
+#include "beam_shared.h"
+#include "Sprite.h"
+#include "util.h"
+#include "portal/weapon_physcannon.h"
+#include "physics_saverestore.h"
+#include "ai_basenpc.h"
+#include "player_pickup.h"
+#include "physics_prop_ragdoll.h"
+#include "globalstate.h"
+#include "props.h"
+#include "movevars_shared.h"
+#include "weapon_portalbasecombatweapon.h"
+#include "te_effect_dispatch.h"
+#include "vphysics/friction.h"
+#include "saverestore_utlvector.h"
+#include "prop_combine_ball.h"
+#include "physobj.h"
+#include "hl2_gamerules.h"
+#include "citadel_effects_shared.h"
+#include "eventqueue.h"
+#include "model_types.h"
+#include "ai_interactions.h"
+#include "rumble_shared.h"
+#include "gamestats.h"
+// NVNT haptic utils
+#include "haptics/haptic_utils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static const char *s_pWaitForUpgradeContext = "WaitForUpgrade";
+
+ConVar g_debug_physcannon( "g_debug_physcannon", "0" );
+
+ConVar physcannon_minforce( "physcannon_minforce", "700" );
+ConVar physcannon_maxforce( "physcannon_maxforce", "1500" );
+ConVar physcannon_maxmass( "physcannon_maxmass", "250" );
+ConVar physcannon_tracelength( "physcannon_tracelength", "250" );
+ConVar physcannon_mega_tracelength( "physcannon_mega_tracelength", "850" );
+ConVar physcannon_chargetime("physcannon_chargetime", "2" );
+ConVar physcannon_pullforce( "physcannon_pullforce", "4000" );
+ConVar physcannon_mega_pullforce( "physcannon_mega_pullforce", "8000" );
+ConVar physcannon_cone( "physcannon_cone", "0.97" );
+ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997" );
+ConVar physcannon_punt_cone( "physcannon_punt_cone", "0.997" );
+ConVar player_throwforce( "player_throwforce", "1000" );
+ConVar physcannon_dmg_glass( "physcannon_dmg_glass", "15" );
+
+extern ConVar hl2_normspeed;
+extern ConVar hl2_walkspeed;
+
+#define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt"
+#define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz.vmt"
+#define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1.vmt"
+#define PHYSCANNON_CENTER_GLOW "sprites/orangecore1.vmt"
+#define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2.vmt"
+
+#define MEGACANNON_BEAM_SPRITE "sprites/lgtning_noz.vmt"
+#define MEGACANNON_GLOW_SPRITE "sprites/blueflare1_noz.vmt"
+#define MEGACANNON_ENDCAP_SPRITE "sprites/blueflare1_noz.vmt"
+#define MEGACANNON_CENTER_GLOW "effects/fluttercore.vmt"
+#define MEGACANNON_BLAST_SPRITE "effects/fluttercore.vmt"
+
+#define MEGACANNON_RAGDOLL_BOOGIE_SPRITE "sprites/lgtning_noz.vmt"
+
+#define MEGACANNON_MODEL "models/weapons/v_superphyscannon.mdl"
+#define MEGACANNON_SKIN 1
+
+// -------------------------------------------------------------------------
+// Physcannon trace filter to handle special cases
+// -------------------------------------------------------------------------
+
+class CTraceFilterPhyscannon : public CTraceFilterSimple
+{
+public:
+ DECLARE_CLASS( CTraceFilterPhyscannon, CTraceFilterSimple );
+
+ CTraceFilterPhyscannon( const IHandleEntity *passentity, int collisionGroup )
+ : CTraceFilterSimple( NULL, collisionGroup ), m_pTraceOwner( passentity ) { }
+
+ // For this test, we only test against entities (then world brushes afterwards)
+ virtual TraceType_t GetTraceType() const { return TRACE_ENTITIES_ONLY; }
+
+ bool HasContentsGrate( CBaseEntity *pEntity )
+ {
+ // FIXME: Move this into the GetModelContents() function in base entity
+
+ // Find the contents based on the model type
+ int nModelType = modelinfo->GetModelType( pEntity->GetModel() );
+ if ( nModelType == mod_studio )
+ {
+ CBaseAnimating *pAnim = dynamic_cast<CBaseAnimating *>(pEntity);
+ if ( pAnim != NULL )
+ {
+ CStudioHdr *pStudioHdr = pAnim->GetModelPtr();
+ if ( pStudioHdr != NULL && (pStudioHdr->contents() & CONTENTS_GRATE) )
+ return true;
+ }
+ }
+ else if ( nModelType == mod_brush )
+ {
+ // Brushes poll their contents differently
+ int contents = modelinfo->GetModelContents( pEntity->GetModelIndex() );
+ if ( contents & CONTENTS_GRATE )
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ // Only skip ourselves (not things we own)
+ if ( pHandleEntity == m_pTraceOwner )
+ return false;
+
+ // Get the entity referenced by this handle
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+ if ( pEntity == NULL )
+ return false;
+
+ // Handle grate entities differently
+ if ( HasContentsGrate( pEntity ) )
+ {
+ // See if it's a grabbable physics prop
+ CPhysicsProp *pPhysProp = dynamic_cast<CPhysicsProp *>(pEntity);
+ if ( pPhysProp != NULL )
+ return pPhysProp->CanBePickedUpByPhyscannon();
+
+ // See if it's a grabbable physics prop
+ if ( FClassnameIs( pEntity, "prop_physics" ) )
+ {
+ CPhysicsProp *pPhysProp = dynamic_cast<CPhysicsProp *>(pEntity);
+ if ( pPhysProp != NULL )
+ return pPhysProp->CanBePickedUpByPhyscannon();
+
+ // Somehow had a classname that didn't match the class!
+ Assert(0);
+ }
+ else if ( FClassnameIs( pEntity, "func_physbox" ) )
+ {
+ // Must be a moveable physbox
+ CPhysBox *pPhysBox = dynamic_cast<CPhysBox *>(pEntity);
+ if ( pPhysBox )
+ return pPhysBox->CanBePickedUpByPhyscannon();
+
+ // Somehow had a classname that didn't match the class!
+ Assert(0);
+ }
+
+ // Don't bother with any other sort of grated entity
+ return false;
+ }
+
+ // Use the default rules
+ return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask );
+ }
+
+protected:
+ const IHandleEntity *m_pTraceOwner;
+};
+
+// We want to test against brushes alone
+class CTraceFilterOnlyBrushes : public CTraceFilterSimple
+{
+public:
+ DECLARE_CLASS( CTraceFilterOnlyBrushes, CTraceFilterSimple );
+ CTraceFilterOnlyBrushes( int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ) {}
+ virtual TraceType_t GetTraceType() const { return TRACE_WORLD_ONLY; }
+};
+
+//-----------------------------------------------------------------------------
+// this will hit skip the pass entity, but not anything it owns
+// (lets player grab own grenades)
+class CTraceFilterNoOwnerTest : public CTraceFilterSimple
+{
+public:
+ DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple );
+
+ CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup )
+ : CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity)
+ {
+ }
+
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ if ( pHandleEntity != m_pPassNotOwner )
+ return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask );
+
+ return false;
+ }
+
+protected:
+ const IHandleEntity *m_pPassNotOwner;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Trace a line the special physcannon way!
+//-----------------------------------------------------------------------------
+void UTIL_PhyscannonTraceLine( const Vector &vecAbsStart, const Vector &vecAbsEnd, CBaseEntity *pTraceOwner, trace_t *pTrace )
+{
+ // Default to HL2 vanilla
+ if ( hl2_episodic.GetBool() == false )
+ {
+ CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace );
+ return;
+ }
+
+ // First, trace against entities
+ CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace );
+
+ // If we've hit something, test again to make sure no brushes block us
+ if ( pTrace->m_pEnt != NULL )
+ {
+ trace_t testTrace;
+ CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE );
+ UTIL_TraceLine( pTrace->startpos, pTrace->endpos, MASK_SHOT, &brushFilter, &testTrace );
+
+ // If we hit a brush, replace the trace with that result
+ if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid )
+ {
+ *pTrace = testTrace;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Trace a hull for the physcannon
+//-----------------------------------------------------------------------------
+void UTIL_PhyscannonTraceHull( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &vecAbsMins, const Vector &vecAbsMaxs, CBaseEntity *pTraceOwner, trace_t *pTrace )
+{
+ // Default to HL2 vanilla
+ if ( hl2_episodic.GetBool() == false )
+ {
+ CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE );
+ UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace );
+ return;
+ }
+
+ // First, trace against entities
+ CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE );
+ UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace );
+
+ // If we've hit something, test again to make sure no brushes block us
+ if ( pTrace->m_pEnt != NULL )
+ {
+ trace_t testTrace;
+ CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE );
+ UTIL_TraceHull( pTrace->startpos, pTrace->endpos, vecAbsMins, vecAbsMaxs, MASK_SHOT, &brushFilter, &testTrace );
+
+ // If we hit a brush, replace the trace with that result
+ if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid )
+ {
+ *pTrace = testTrace;
+ }
+ }
+}
+
+static void MatrixOrthogonalize( matrix3x4_t &matrix, int column )
+{
+ Vector columns[3];
+ int i;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ MatrixGetColumn( matrix, i, columns[i] );
+ }
+
+ int index0 = column;
+ int index1 = (column+1)%3;
+ int index2 = (column+2)%3;
+
+ columns[index2] = CrossProduct( columns[index0], columns[index1] );
+ columns[index1] = CrossProduct( columns[index2], columns[index0] );
+ VectorNormalize( columns[index2] );
+ VectorNormalize( columns[index1] );
+ MatrixSetColumn( columns[index1], index1, matrix );
+ MatrixSetColumn( columns[index2], index2, matrix );
+}
+
+#define SIGN(x) ( (x) < 0 ? -1 : 1 )
+
+static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle )
+{
+ matrix3x4_t alignMatrix;
+ AngleMatrix( angles, alignMatrix );
+
+ // NOTE: Must align z first
+ for ( int j = 3; --j >= 0; )
+ {
+ Vector vec;
+ MatrixGetColumn( alignMatrix, j, vec );
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( fabs(vec[i]) > cosineAlignAngle )
+ {
+ vec[i] = SIGN(vec[i]);
+ vec[(i+1)%3] = 0;
+ vec[(i+2)%3] = 0;
+ MatrixSetColumn( vec, j, alignMatrix );
+ MatrixOrthogonalize( alignMatrix, j );
+ break;
+ }
+ }
+ }
+
+ QAngle out;
+ MatrixAngles( alignMatrix, out );
+ return out;
+}
+
+
+static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr )
+{
+ physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr );
+
+ if ( ptr->DidHit() )
+ {
+ ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction;
+ ptr->startpos = start;
+ ptr->plane.dist = -ptr->plane.dist;
+ ptr->plane.normal *= -1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the nearest ragdoll sub-piece to a location and returns it
+// Input : *pTarget - entity that is the potential ragdoll
+// &position - position we're testing against
+// Output : IPhysicsObject - sub-object (if any)
+//-----------------------------------------------------------------------------
+IPhysicsObject *GetRagdollChildAtPosition( CBaseEntity *pTarget, const Vector &position )
+{
+ // Check for a ragdoll
+ if ( dynamic_cast<CRagdollProp*>( pTarget ) == NULL )
+ return NULL;
+
+ // Get the root
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE( pList ) );
+
+ IPhysicsObject *pBestChild = NULL;
+ float flBestDist = 99999999.0f;
+ float flDist;
+ Vector vPos;
+
+ // Find the nearest child to where we're looking
+ for ( int i = 0; i < count; i++ )
+ {
+ pList[i]->GetPosition( &vPos, NULL );
+
+ flDist = ( position - vPos ).LengthSqr();
+
+ if ( flDist < flBestDist )
+ {
+ pBestChild = pList[i];
+ flBestDist = flDist;
+ }
+ }
+
+ // Make this our base now
+ pTarget->VPhysicsSwapObject( pBestChild );
+
+ return pTarget->VPhysicsGetObject();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Computes a local matrix for the player clamped to valid carry ranges
+//-----------------------------------------------------------------------------
+// when looking level, hold bottom of object 8 inches below eye level
+#define PLAYER_HOLD_LEVEL_EYES -8
+
+// when looking down, hold bottom of object 0 inches from feet
+#define PLAYER_HOLD_DOWN_FEET 2
+
+// when looking up, hold bottom of object 24 inches above eye level
+#define PLAYER_HOLD_UP_EYES 24
+
+// use a +/-30 degree range for the entire range of motion of pitch
+#define PLAYER_LOOK_PITCH_RANGE 30
+
+// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom)
+#define PLAYER_REACH_DOWN_DISTANCE 24
+
+static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out )
+{
+ if ( !pPlayer )
+ return;
+
+ QAngle angles = pPlayer->EyeAngles();
+ Vector origin = pPlayer->EyePosition();
+
+ // 0-360 / -180-180
+ //angles.x = init ? 0 : AngleDistance( angles.x, 0 );
+ //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE );
+ angles.x = 0;
+
+ float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z;
+ float eyes = origin.z;
+ float zoffset = 0;
+ // moving up (negative pitch is up)
+ if ( angles.x < 0 )
+ {
+ zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES );
+ }
+ else
+ {
+ zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) );
+ }
+ origin.z += zoffset;
+ angles.x = 0;
+ AngleMatrix( angles, origin, out );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+// derive from this so we can add save/load data to it
+struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t
+{
+ DECLARE_SIMPLE_DATADESC();
+};
+
+BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t )
+
+ DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( targetRotation, FIELD_VECTOR ),
+ DEFINE_FIELD( maxAngular, FIELD_FLOAT ),
+ DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ),
+ DEFINE_FIELD( maxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( dampFactor, FIELD_FLOAT ),
+ DEFINE_FIELD( teleportDistance, FIELD_FLOAT ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+class CGrabController : public IMotionEvent
+{
+ DECLARE_SIMPLE_DATADESC();
+
+public:
+
+ CGrabController( void );
+ ~CGrabController( void );
+ void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition );
+ void DetachEntity( bool bClearVelocity );
+ void OnRestore();
+
+ bool UpdateObject( CBasePlayer *pPlayer, float flError );
+
+ void SetTargetPosition( const Vector &target, const QAngle &targetOrientation );
+ void GetTargetPosition( Vector *target, QAngle *targetOrientation );
+ float ComputeError();
+ float GetLoadWeight( void ) const { return m_flLoadWeight; }
+ void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; }
+ void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; }
+ QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
+ QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
+
+ CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; }
+
+ IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
+ float GetSavedMass( IPhysicsObject *pObject );
+
+ bool IsObjectAllowedOverhead( CBaseEntity *pEntity );
+
+ //set when a held entity is penetrating another through a portal. Needed for special fixes
+ void SetPortalPenetratingEntity( CBaseEntity *pPenetrated );
+
+private:
+ // Compute the max speed for an attached object
+ void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics );
+
+ game_shadowcontrol_params_t m_shadow;
+ float m_timeToArrive;
+ float m_errorTime;
+ float m_error;
+ float m_contactAmount;
+ float m_angleAlignment;
+ bool m_bCarriedEntityBlocksLOS;
+ bool m_bIgnoreRelativePitch;
+
+ float m_flLoadWeight;
+ float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ EHANDLE m_attachedEntity;
+ QAngle m_vecPreferredCarryAngles;
+ bool m_bHasPreferredCarryAngles;
+ float m_flDistanceOffset;
+
+ QAngle m_attachedAnglesPlayerSpace;
+ Vector m_attachedPositionObjectSpace;
+
+ IPhysicsMotionController *m_controller;
+
+ // NVNT player controlling this grab controller
+ CBasePlayer* m_pControllingPlayer;
+
+ bool m_bAllowObjectOverhead; // Can the player hold this object directly overhead? (Default is NO)
+
+ //set when a held entity is penetrating another through a portal. Needed for special fixes
+ EHANDLE m_PenetratedEntity;
+
+ friend class CWeaponPhysCannon;
+ friend void GetSavedParamsForCarriedPhysObject( CGrabController *pGrabController, IPhysicsObject *pObject, float *pSavedMassOut, float *pSavedRotationalDampingOut );
+};
+
+BEGIN_SIMPLE_DATADESC( CGrabController )
+
+ DEFINE_EMBEDDED( m_shadow ),
+
+ DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ),
+ DEFINE_FIELD( m_errorTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_error, FIELD_FLOAT ),
+ DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ),
+ DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ),
+ DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flDistanceOffset, FIELD_FLOAT ),
+ DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ),
+ DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bAllowObjectOverhead, FIELD_BOOLEAN ),
+
+ // Physptrs can't be inside embedded classes
+ // DEFINE_PHYSPTR( m_controller ),
+
+END_DATADESC()
+
+const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f;
+const float REDUCED_CARRY_MASS = 1.0f;
+
+CGrabController::CGrabController( void )
+{
+ m_shadow.dampFactor = 1.0;
+ m_shadow.teleportDistance = 0;
+ m_errorTime = 0;
+ m_error = 0;
+ // make this controller really stiff!
+ m_shadow.maxSpeed = 1000;
+ m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
+ m_shadow.maxDampSpeed = m_shadow.maxSpeed*2;
+ m_shadow.maxDampAngular = m_shadow.maxAngular;
+ m_attachedEntity = NULL;
+ m_vecPreferredCarryAngles = vec3_angle;
+ m_bHasPreferredCarryAngles = false;
+ m_flDistanceOffset = 0;
+ // NVNT constructing m_pControllingPlayer to NULL
+ m_pControllingPlayer = NULL;
+}
+
+CGrabController::~CGrabController( void )
+{
+ DetachEntity( false );
+}
+
+void CGrabController::OnRestore()
+{
+ if ( m_controller )
+ {
+ m_controller->SetEventHandler( this );
+ }
+}
+
+void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation )
+{
+ m_shadow.targetPosition = target;
+ m_shadow.targetRotation = targetOrientation;
+
+ m_timeToArrive = gpGlobals->frametime;
+
+ CBaseEntity *pAttached = GetAttached();
+ if ( pAttached )
+ {
+ IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
+
+ if ( pObj != NULL )
+ {
+ pObj->Wake();
+ }
+ else
+ {
+ DetachEntity( false );
+ }
+ }
+}
+
+void CGrabController::GetTargetPosition( Vector *target, QAngle *targetOrientation )
+{
+ if ( target )
+ *target = m_shadow.targetPosition;
+
+ if ( targetOrientation )
+ *targetOrientation = m_shadow.targetRotation;
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CGrabController::ComputeError()
+{
+ if ( m_errorTime <= 0 )
+ return 0;
+
+ CBaseEntity *pAttached = GetAttached();
+ if ( pAttached )
+ {
+ Vector pos;
+ IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
+
+ if ( pObj )
+ {
+ pObj->GetShadowPosition( &pos, NULL );
+
+ float error = (m_shadow.targetPosition - pos).Length();
+ if ( m_errorTime > 0 )
+ {
+ if ( m_errorTime > 1 )
+ {
+ m_errorTime = 1;
+ }
+ float speed = error / m_errorTime;
+ if ( speed > m_shadow.maxSpeed )
+ {
+ error *= 0.5;
+ }
+ m_error = (1-m_errorTime) * m_error + error * m_errorTime;
+ }
+ }
+ else
+ {
+ DevMsg( "Object attached to Physcannon has no physics object\n" );
+ DetachEntity( false );
+ return 9999; // force detach
+ }
+ }
+
+ if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
+ {
+ m_error *= 3.0f;
+ }
+
+ // If held across a portal but not looking at the portal multiply error
+ CPortal_Player *pPortalPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pAttached );
+ Assert( pPortalPlayer );
+ if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ Vector forward, right, up;
+ QAngle playerAngles = pPortalPlayer->EyeAngles();
+
+ float pitch = AngleDistance(playerAngles.x,0);
+ playerAngles.x = clamp( pitch, -75, 75 );
+ AngleVectors( playerAngles, &forward, &right, &up );
+
+ Vector start = pPortalPlayer->Weapon_ShootPosition();
+
+ // If the player is upside down then we need to hold the box closer to their feet.
+ if ( up.z < 0.0f )
+ start += pPortalPlayer->GetViewOffset() * up.z;
+ if ( right.z < 0.0f )
+ start += pPortalPlayer->GetViewOffset() * right.z;
+
+ Ray_t rayPortalTest;
+ rayPortalTest.Init( start, start + forward * 256.0f );
+
+ if ( UTIL_IntersectRayWithPortal( rayPortalTest, pPortalPlayer->GetHeldObjectPortal() ) < 0.0f )
+ {
+ m_error *= 2.5f;
+ }
+ }
+
+ m_errorTime = 0;
+
+ return m_error;
+}
+
+
+#define MASS_SPEED_SCALE 60
+#define MAX_MASS 40
+
+void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics )
+{
+ m_shadow.maxSpeed = 1000;
+ m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
+
+ // Compute total mass...
+ float flMass = PhysGetEntityMass( pEntity );
+ float flMaxMass = physcannon_maxmass.GetFloat();
+ if ( flMass <= flMaxMass )
+ return;
+
+ float flLerpFactor = clamp( flMass, flMaxMass, 500.0f );
+ flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f );
+
+ float invMass = pPhysics->GetInvMass();
+ float invInertia = pPhysics->GetInvInertia().Length();
+
+ float invMaxMass = 1.0f / MAX_MASS;
+ float ratio = invMaxMass / invMass;
+ invMass = invMaxMass;
+ invInertia *= ratio;
+
+ float maxSpeed = invMass * MASS_SPEED_SCALE * 200;
+ float maxAngular = invInertia * MASS_SPEED_SCALE * 360;
+
+ m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed );
+ m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular );
+}
+
+
+QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
+{
+ if ( m_bIgnoreRelativePitch )
+ {
+ matrix3x4_t test;
+ QAngle angleTest = pPlayer->EyeAngles();
+ angleTest.x = 0;
+ AngleMatrix( angleTest, test );
+ return TransformAnglesToLocalSpace( anglesIn, test );
+ }
+ return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() );
+}
+
+QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
+{
+ if ( m_bIgnoreRelativePitch )
+ {
+ matrix3x4_t test;
+ QAngle angleTest = pPlayer->EyeAngles();
+ angleTest.x = 0;
+ AngleMatrix( angleTest, test );
+ return TransformAnglesToWorldSpace( anglesIn, test );
+ }
+ return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() );
+}
+
+
+void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition )
+{
+ CPortal_Player *pPortalPlayer = ToPortalPlayer( pPlayer );
+ // play the impact sound of the object hitting the player
+ // used as feedback to let the player know he picked up the object
+ if ( !pPortalPlayer->m_bSilentDropAndPickup )
+ {
+ int hitMaterial = pPhys->GetMaterialIndex();
+ int playerMaterial = pPlayer->VPhysicsGetObject() ? pPlayer->VPhysicsGetObject()->GetMaterialIndex() : hitMaterial;
+ PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, hitMaterial, playerMaterial, 1.0, 64 );
+ }
+ Vector position;
+ QAngle angles;
+ pPhys->GetPosition( &position, &angles );
+ // If it has a preferred orientation, use that instead.
+ Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
+
+ //Fix attachment orientation weirdness
+ if( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ Vector vPlayerForward;
+ pPlayer->EyeVectors( &vPlayerForward );
+
+ Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -vPlayerForward );
+ Vector player2d = pPlayer->CollisionProp()->OBBMaxs();
+ float playerRadius = player2d.Length2D();
+ float flDot = DotProduct( vPlayerForward, radial );
+
+ float radius = playerRadius + fabs( flDot );
+
+ float distance = 24 + ( radius * 2.0f );
+
+ //find out which portal the object is on the other side of....
+ Vector start = pPlayer->Weapon_ShootPosition();
+ Vector end = start + ( vPlayerForward * distance );
+
+ CProp_Portal *pObjectPortal = NULL;
+ pObjectPortal = pPortalPlayer->GetHeldObjectPortal();
+
+ // If our end point hasn't gone into the portal yet we at least need to know what portal is in front of us
+ if ( !pObjectPortal )
+ {
+ Ray_t rayPortalTest;
+ rayPortalTest.Init( start, start + vPlayerForward * 1024.0f );
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ float fMinDist = 2.0f;
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal->m_bActivated &&
+ (pTempPortal->m_hLinkedPortal.Get() != NULL) )
+ {
+ float fDist = UTIL_IntersectRayWithPortal( rayPortalTest, pTempPortal );
+ if( (fDist >= 0.0f) && (fDist < fMinDist) )
+ {
+ fMinDist = fDist;
+ pObjectPortal = pTempPortal;
+ }
+ }
+ }
+ }
+ }
+
+ if( pObjectPortal )
+ {
+ UTIL_Portal_AngleTransform( pObjectPortal->m_hLinkedPortal->MatrixThisToLinked(), angles, angles );
+ }
+ }
+
+ VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
+// ComputeMaxSpeed( pEntity, pPhys );
+
+ // If we haven't been killed by a grab, we allow the gun to grab the nearest part of a ragdoll
+ if ( bUseGrabPosition )
+ {
+ IPhysicsObject *pChild = GetRagdollChildAtPosition( pEntity, vGrabPosition );
+
+ if ( pChild )
+ {
+ pPhys = pChild;
+ }
+ }
+
+ // Carried entities can never block LOS
+ m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS();
+ pEntity->SetBlocksLOS( false );
+ m_controller = physenv->CreateMotionController( this );
+ m_controller->AttachObject( pPhys, true );
+ // Don't do this, it's causing trouble with constraint solvers.
+ //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
+
+ pPhys->Wake();
+ PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
+ SetTargetPosition( position, angles );
+ m_attachedEntity = pEntity;
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ m_flLoadWeight = 0;
+ float damping = 10;
+ float flFactor = count / 7.5f;
+ if ( flFactor < 1.0f )
+ {
+ flFactor = 1.0f;
+ }
+ for ( int i = 0; i < count; i++ )
+ {
+ float mass = pList[i]->GetMass();
+ pList[i]->GetDamping( NULL, &m_savedRotDamping[i] );
+ m_flLoadWeight += mass;
+ m_savedMass[i] = mass;
+
+ // reduce the mass to prevent the player from adding crazy amounts of energy to the system
+ pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor );
+ pList[i]->SetDamping( NULL, &damping );
+ }
+
+ // NVNT setting m_pControllingPlayer to the player attached
+ m_pControllingPlayer = pPlayer;
+
+ // Give extra mass to the phys object we're actually picking up
+ pPhys->SetMass( REDUCED_CARRY_MASS );
+ pPhys->EnableDrag( false );
+
+ m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating
+ m_error = 0;
+ m_contactAmount = 0;
+
+ m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer );
+ if ( m_angleAlignment != 0 )
+ {
+ m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment );
+ }
+
+ // Ragdolls don't offset this way
+ if ( dynamic_cast<CRagdollProp*>(pEntity) )
+ {
+ m_attachedPositionObjectSpace.Init();
+ }
+ else
+ {
+ VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
+ }
+
+ // If it's a prop, see if it has desired carry angles
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity);
+ if ( pProp )
+ {
+ m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles );
+ m_flDistanceOffset = pProp->GetCarryDistanceOffset();
+ }
+ else
+ {
+ m_bHasPreferredCarryAngles = false;
+ m_flDistanceOffset = 0;
+ }
+
+ m_bAllowObjectOverhead = IsObjectAllowedOverhead( pEntity );
+}
+
+static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit )
+{
+ Vector vel;
+ AngularImpulse angVel;
+ pPhys->GetVelocity( &vel, &angVel );
+ float speed = VectorNormalize(vel) - linearLimit;
+ float angSpeed = VectorNormalize(angVel) - angularLimit;
+ speed = speed < 0 ? 0 : -speed;
+ angSpeed = angSpeed < 0 ? 0 : -angSpeed;
+ vel *= speed;
+ angVel *= angSpeed;
+ pPhys->AddVelocity( &vel, &angVel );
+}
+
+void CGrabController::DetachEntity( bool bClearVelocity )
+{
+ Assert(!PhysIsInCallback());
+ CBaseEntity *pEntity = GetAttached();
+ if ( pEntity )
+ {
+ // Restore the LS blocking state
+ pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS );
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ IPhysicsObject *pPhys = pList[i];
+ if ( !pPhys )
+ continue;
+
+ // on the odd chance that it's gone to sleep while under anti-gravity
+ pPhys->EnableDrag( true );
+ pPhys->Wake();
+ pPhys->SetMass( m_savedMass[i] );
+ pPhys->SetDamping( NULL, &m_savedRotDamping[i] );
+ PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
+ if ( bClearVelocity )
+ {
+ PhysForceClearVelocity( pPhys );
+ }
+ else
+ {
+ ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f );
+ }
+
+ }
+ }
+
+ m_attachedEntity = NULL;
+ physenv->DestroyMotionController( m_controller );
+ m_controller = NULL;
+}
+
+static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass )
+{
+ bool contact = false;
+ IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot();
+ while ( pSnapshot->IsValid() )
+ {
+ IPhysicsObject *pOther = pSnapshot->GetObject( 1 );
+ if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass )
+ {
+ contact = true;
+ break;
+ }
+ pSnapshot->NextFrictionData();
+ }
+ pObject->DestroyFrictionSnapshot( pSnapshot );
+ return contact;
+}
+
+IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
+{
+ game_shadowcontrol_params_t shadowParams = m_shadow;
+ if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) )
+ {
+ m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f );
+ }
+ else
+ {
+ m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f );
+ }
+ shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount;
+ m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime );
+
+ // Slide along the current contact points to fix bouncing problems
+ Vector velocity;
+ AngularImpulse angVel;
+ pObject->GetVelocity( &velocity, &angVel );
+ PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() );
+ pObject->SetVelocityInstantaneous( &velocity, NULL );
+
+ linear.Init();
+ angular.Init();
+ m_errorTime += deltaTime;
+
+ return SIM_LOCAL_ACCELERATION;
+}
+
+float CGrabController::GetSavedMass( IPhysicsObject *pObject )
+{
+ CBaseEntity *pHeld = m_attachedEntity;
+ if ( pHeld )
+ {
+ if ( pObject->GetGameData() == (void*)pHeld )
+ {
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( pList[i] == pObject )
+ return m_savedMass[i];
+ }
+ }
+ }
+ return 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Is this an object that the player is allowed to lift to a position
+// directly overhead? The default behavior prevents lifting objects directly
+// overhead, but there are exceptions for gameplay purposes.
+//-----------------------------------------------------------------------------
+bool CGrabController::IsObjectAllowedOverhead( CBaseEntity *pEntity )
+{
+ // Allow combine balls overhead
+ if( UTIL_IsCombineBallDefinite(pEntity) )
+ return true;
+
+ // Allow props that are specifically flagged as such
+ CPhysicsProp *pPhysProp = dynamic_cast<CPhysicsProp *>(pEntity);
+ if ( pPhysProp != NULL && pPhysProp->HasInteraction( PROPINTER_PHYSGUN_ALLOW_OVERHEAD ) )
+ return true;
+
+ // String checks are fine here, we only run this code one time- when the object is picked up.
+ if( pEntity->ClassMatches("grenade_helicopter") )
+ return true;
+
+ if( pEntity->ClassMatches("weapon_striderbuster") )
+ return true;
+
+ return false;
+}
+
+
+
+
+void CGrabController::SetPortalPenetratingEntity( CBaseEntity *pPenetrated )
+{
+ m_PenetratedEntity = pPenetrated;
+}
+
+//-----------------------------------------------------------------------------
+// Player pickup controller
+//-----------------------------------------------------------------------------
+class CPlayerPickupController : public CBaseEntity
+{
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CPlayerPickupController, CBaseEntity );
+public:
+ void Init( CBasePlayer *pPlayer, CBaseEntity *pObject );
+ void Shutdown( bool bThrown = false );
+ bool OnControls( CBaseEntity *pControls ) { return true; }
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void OnRestore()
+ {
+ m_grabController.OnRestore();
+ }
+ void VPhysicsUpdate( IPhysicsObject *pPhysics ){}
+ void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {}
+
+ bool IsHoldingEntity( CBaseEntity *pEnt );
+ CGrabController &GetGrabController() { return m_grabController; }
+
+private:
+ CGrabController m_grabController;
+ CBasePlayer *m_pPlayer;
+};
+
+LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController );
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CPlayerPickupController )
+
+ DEFINE_EMBEDDED( m_grabController ),
+
+ // Physptrs can't be inside embedded classes
+ DEFINE_PHYSPTR( m_grabController.m_controller ),
+
+ DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+// *pObject -
+//-----------------------------------------------------------------------------
+void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject )
+{
+ // Holster player's weapon
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ // Don't holster the portalgun
+ if ( FClassnameIs( pPlayer->GetActiveWeapon(), "weapon_portalgun" ) )
+ {
+ CWeaponPortalgun *pPortalGun = (CWeaponPortalgun*)(pPlayer->GetActiveWeapon());
+ pPortalGun->OpenProngs( true );
+ }
+ else
+ {
+ if ( !pPlayer->GetActiveWeapon()->Holster() )
+ {
+ Shutdown();
+ return;
+ }
+ }
+ }
+
+ CPortal_Player *pOwner = ToPortalPlayer( pPlayer );
+ if ( pOwner )
+ {
+ pOwner->EnableSprint( false );
+ }
+
+ // If the target is debris, convert it to non-debris
+ if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ // Interactive debris converts back to debris when it comes to rest
+ pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ }
+
+ // done so I'll go across level transitions with the player
+ SetParent( pPlayer );
+ m_grabController.SetIgnorePitch( true );
+ m_grabController.SetAngleAlignment( DOT_30DEGREE );
+ m_pPlayer = pPlayer;
+ IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
+
+ if ( !pOwner->m_bSilentDropAndPickup )
+ {
+ Pickup_OnPhysGunPickup( pObject, m_pPlayer, PICKED_UP_BY_PLAYER );
+ }
+
+ m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false );
+#ifdef WIN32
+ // NVNT apply a downward force to simulate the mass of the held object.
+ HapticSetConstantForce(m_pPlayer,clamp(m_grabController.GetLoadWeight()*0.1,1,6)*Vector(0,-1,0));
+#endif
+ m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
+ m_pPlayer->SetUseEntity( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : bool -
+//-----------------------------------------------------------------------------
+void CPlayerPickupController::Shutdown( bool bThrown )
+{
+ CBaseEntity *pObject = m_grabController.GetAttached();
+
+ bool bClearVelocity = false;
+ if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) )
+ {
+ bClearVelocity = true;
+ }
+
+ m_grabController.DetachEntity( bClearVelocity );
+#ifdef WIN32
+ // NVNT if we have a player, issue a zero constant force message
+ if(m_pPlayer)
+ HapticSetConstantForce(m_pPlayer,Vector(0,0,0));
+#endif
+ if ( pObject != NULL )
+ {
+ if ( !ToPortalPlayer( m_pPlayer )->m_bSilentDropAndPickup )
+ {
+ Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER );
+ }
+ }
+
+ if ( m_pPlayer )
+ {
+ CPortal_Player *pOwner = ToPortalPlayer( m_pPlayer );
+ if ( pOwner )
+ {
+ pOwner->EnableSprint( true );
+ }
+
+ m_pPlayer->SetUseEntity( NULL );
+ if ( !pOwner->m_bSilentDropAndPickup )
+ {
+ if ( m_pPlayer->GetActiveWeapon() )
+ {
+ if ( FClassnameIs( m_pPlayer->GetActiveWeapon(), "weapon_portalgun" ) )
+ {
+ m_pPlayer->SetNextAttack( gpGlobals->curtime + 0.5f );
+ CWeaponPortalgun *pPortalGun = (CWeaponPortalgun*)(m_pPlayer->GetActiveWeapon());
+ pPortalGun->DelayAttack( 0.5f );
+ pPortalGun->OpenProngs( false );
+ }
+ else
+ {
+ if ( !m_pPlayer->GetActiveWeapon()->Deploy() )
+ {
+ // We tried to restore the player's weapon, but we couldn't.
+ // This usually happens when they're holding an empty weapon that doesn't
+ // autoswitch away when out of ammo. Switch to next best weapon.
+ m_pPlayer->SwitchToNextBestWeapon( NULL );
+ }
+ }
+ }
+
+ m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
+ }
+ }
+ Remove();
+}
+
+
+void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( ToBasePlayer(pActivator) == m_pPlayer )
+ {
+ CBaseEntity *pAttached = m_grabController.GetAttached();
+
+ // UNDONE: Use vphysics stress to decide to drop objects
+ // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work
+ if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 )
+ {
+ Shutdown();
+ return;
+ }
+
+ //Adrian: Oops, our object became motion disabled, let go!
+ IPhysicsObject *pPhys = pAttached->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() == false )
+ {
+ Shutdown();
+ return;
+ }
+
+#if STRESS_TEST
+ vphysics_objectstress_t stress;
+ CalculateObjectStress( pPhys, pAttached, &stress );
+ if ( stress.exertedStress > 250 )
+ {
+ Shutdown();
+ return;
+ }
+#endif
+ // +ATTACK will throw phys objects
+ if ( m_pPlayer->m_nButtons & IN_ATTACK )
+ {
+ Shutdown( true );
+ Vector vecLaunch;
+ m_pPlayer->EyeVectors( &vecLaunch );
+ // If throwing from the opposite side of a portal, reorient the direction
+ if( ((CPortal_Player *)m_pPlayer)->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ CProp_Portal *pHeldPortal = ((CPortal_Player *)m_pPlayer)->GetHeldObjectPortal();
+ UTIL_Portal_VectorTransform( pHeldPortal->MatrixThisToLinked(), vecLaunch, vecLaunch );
+ }
+
+ ((CPortal_Player *)m_pPlayer)->SetHeldObjectOnOppositeSideOfPortal( false );
+ // JAY: Scale this with mass because some small objects really go flying
+ float massFactor = clamp( pPhys->GetMass(), 0.5, 15 );
+ massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 );
+ vecLaunch *= player_throwforce.GetFloat() * massFactor;
+
+ pPhys->ApplyForceCenter( vecLaunch );
+ AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor;
+ pPhys->ApplyTorqueCenter( aVel );
+ return;
+ }
+
+ if ( useType == USE_SET )
+ {
+ // update position
+ m_grabController.UpdateObject( m_pPlayer, 12 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnt -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt )
+{
+ if ( pEnt )
+ {
+ return ( m_grabController.GetAttached() == pEnt );
+ }
+
+ return ( m_grabController.GetAttached() != 0 );
+}
+
+void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject )
+{
+ //Don't pick up if we don't have a phys object.
+ if ( pObject->VPhysicsGetObject() == NULL )
+ return;
+
+ if ( pObject->GetBaseAnimating() && pObject->GetBaseAnimating()->IsDissolving() )
+ return;
+
+ CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer );
+
+ if ( !pController )
+ return;
+
+ pController->Init( pPlayer, pObject );
+}
+
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Physcannon
+//-----------------------------------------------------------------------------
+
+#define NUM_BEAMS 4
+#define NUM_SPRITES 6
+
+struct thrown_objects_t
+{
+ float fTimeThrown;
+ EHANDLE hEntity;
+
+ DECLARE_SIMPLE_DATADESC();
+};
+
+BEGIN_SIMPLE_DATADESC( thrown_objects_t )
+ DEFINE_FIELD( fTimeThrown, FIELD_TIME ),
+ DEFINE_FIELD( hEntity, FIELD_EHANDLE ),
+END_DATADESC()
+
+class CWeaponPhysCannon : public CBasePortalCombatWeapon
+{
+public:
+ DECLARE_CLASS( CWeaponPhysCannon, CBasePortalCombatWeapon );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CWeaponPhysCannon( void );
+
+ void Drop( const Vector &vecVelocity );
+ void Precache();
+ virtual void Spawn();
+ virtual void OnRestore();
+ virtual void StopLoopingSounds();
+ virtual void UpdateOnRemove(void);
+ void PrimaryAttack();
+ void SecondaryAttack();
+ void WeaponIdle();
+ void ItemPreFrame();
+ void ItemPostFrame();
+ void ItemBusyFrame();
+
+ virtual float GetMaxAutoAimDeflection() { return 0.90f; }
+
+ void ForceDrop( void );
+ bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in
+ CGrabController &GetGrabController() { return m_grabController; }
+
+ bool CanHolster( void );
+ bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+ bool Deploy( void );
+
+ bool HasAnyAmmo( void ) { return true; }
+
+ void InputBecomeMegaCannon( inputdata_t &inputdata );
+
+ void BeginUpgrade();
+
+ virtual void SetViewModel( void );
+ virtual const char *GetShootSound( int iIndex ) const;
+
+ void RecordThrownObject( CBaseEntity *pObject );
+ void PurgeThrownObjects();
+ bool IsAccountableForObject( CBaseEntity *pObject );
+
+ bool ShouldDisplayHUDHint() { return true; }
+
+
+
+protected:
+ enum FindObjectResult_t
+ {
+ OBJECT_FOUND = 0,
+ OBJECT_NOT_FOUND,
+ OBJECT_BEING_DETACHED,
+ };
+
+ void DoMegaEffect( int effectType, Vector *pos = NULL );
+ void DoEffect( int effectType, Vector *pos = NULL );
+
+ void OpenElements( void );
+ void CloseElements( void );
+
+ // Pickup and throw objects.
+ bool CanPickupObject( CBaseEntity *pTarget );
+ void CheckForTarget( void );
+ FindObjectResult_t FindObject( void );
+ void FindObjectTrace( CBasePlayer *pPlayer, trace_t *pTraceResult );
+ CBaseEntity *MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls );
+ CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone );
+ bool AttachObject( CBaseEntity *pObject, const Vector &vPosition );
+ void UpdateObject( void );
+ void DetachObject( bool playSound = true, bool wasLaunched = false );
+ void LaunchObject( const Vector &vecDir, float flForce );
+ void StartEffects( void ); // Initialize all sprites and beams
+ void StopEffects( bool stopSound = true ); // Hide all effects temporarily
+ void DestroyEffects( void ); // Destroy all sprites and beams
+
+ // Punt objects - this is pointing at an object in the world and applying a force to it.
+ void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr );
+ void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr );
+ void PuntRagdoll( CBaseEntity *pEntity, const Vector &forward, trace_t &tr );
+
+ // Velocity-based throw common to punt and launch code.
+ void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward, const Vector &vecHitPos, PhysGunForce_t reason );
+
+ // Physgun effects
+ void DoEffectClosed( void );
+ void DoMegaEffectClosed( void );
+
+ void DoEffectReady( void );
+ void DoMegaEffectReady( void );
+
+ void DoMegaEffectHolding( void );
+ void DoEffectHolding( void );
+
+ void DoMegaEffectLaunch( Vector *pos );
+ void DoEffectLaunch( Vector *pos );
+
+ void DoEffectNone( void );
+ void DoEffectIdle( void );
+
+ // Trace length
+ float TraceLength();
+
+ // Do we have the super-phys gun?
+ inline bool IsMegaPhysCannon()
+ {
+ return PlayerHasMegaPhysCannon();
+ }
+
+ // Sprite scale factor
+ float SpriteScaleFactor();
+
+ float GetLoadPercentage();
+ CSoundPatch *GetMotorSound( void );
+
+ void DryFire( void );
+ void PrimaryFireEffect( void );
+
+ // What happens when the physgun picks up something
+ void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason );
+
+ // Wait until we're done upgrading
+ void WaitForUpgradeThink();
+
+ bool EntityAllowsPunts( CBaseEntity *pEntity );
+
+ bool m_bOpen;
+ bool m_bActive;
+ int m_nChangeState; //For delayed state change of elements
+ float m_flCheckSuppressTime; //Amount of time to suppress the checking for targets
+ bool m_flLastDenySoundPlayed; //Debounce for deny sound
+ int m_nAttack2Debounce;
+
+ CNetworkVar( bool, m_bIsCurrentlyUpgrading );
+
+ float m_flElementDebounce;
+ float m_flElementPosition;
+ float m_flElementDestination;
+
+ CHandle<CBeam> m_hBeams[NUM_BEAMS];
+ CHandle<CSprite> m_hGlowSprites[NUM_SPRITES];
+ CHandle<CSprite> m_hEndSprites[2];
+ float m_flEndSpritesOverride[2];
+ CHandle<CSprite> m_hCenterSprite;
+ CHandle<CSprite> m_hBlastSprite;
+
+ CSoundPatch *m_sndMotor; // Whirring sound for the gun
+
+ CGrabController m_grabController;
+
+ int m_EffectState; // Current state of the effects on the gun
+
+ bool m_bPhyscannonState;
+
+ // A list of the objects thrown or punted recently, and the time done so.
+ CUtlVector< thrown_objects_t > m_ThrownEntities;
+
+ float m_flTimeNextObjectPurge;
+};
+
+IMPLEMENT_SERVERCLASS_ST(CWeaponPhysCannon, DT_WeaponPhysCannon)
+ SendPropBool( SENDINFO( m_bIsCurrentlyUpgrading ) ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon );
+PRECACHE_WEAPON_REGISTER( weapon_physcannon );
+
+BEGIN_DATADESC( CWeaponPhysCannon )
+
+ DEFINE_FIELD( m_bOpen, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_nChangeState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flCheckSuppressTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flElementDebounce, FIELD_TIME ),
+ DEFINE_FIELD( m_flElementPosition, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flElementDestination, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nAttack2Debounce, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bIsCurrentlyUpgrading, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_EffectState, FIELD_INTEGER ),
+
+ DEFINE_AUTO_ARRAY( m_hBeams, FIELD_EHANDLE ),
+ DEFINE_AUTO_ARRAY( m_hGlowSprites, FIELD_EHANDLE ),
+ DEFINE_AUTO_ARRAY( m_hEndSprites, FIELD_EHANDLE ),
+ DEFINE_AUTO_ARRAY( m_flEndSpritesOverride, FIELD_TIME ),
+ DEFINE_FIELD( m_hCenterSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hBlastSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLastDenySoundPlayed, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bPhyscannonState, FIELD_BOOLEAN ),
+ DEFINE_SOUNDPATCH( m_sndMotor ),
+
+ DEFINE_EMBEDDED( m_grabController ),
+
+ // Physptrs can't be inside embedded classes
+ DEFINE_PHYSPTR( m_grabController.m_controller ),
+
+ DEFINE_THINKFUNC( WaitForUpgradeThink ),
+
+ DEFINE_UTLVECTOR( m_ThrownEntities, FIELD_EMBEDDED ),
+
+ DEFINE_FIELD( m_flTimeNextObjectPurge, FIELD_TIME ),
+
+END_DATADESC()
+
+
+enum
+{
+ ELEMENT_STATE_NONE = -1,
+ ELEMENT_STATE_OPEN,
+ ELEMENT_STATE_CLOSED,
+};
+
+enum
+{
+ EFFECT_NONE,
+ EFFECT_CLOSED,
+ EFFECT_READY,
+ EFFECT_HOLDING,
+ EFFECT_LAUNCH,
+};
+
+
+//-----------------------------------------------------------------------------
+// Do we have the super-phys gun?
+//-----------------------------------------------------------------------------
+bool PlayerHasMegaPhysCannon()
+{
+ return ( HL2GameRules()->MegaPhyscannonActive() == true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CWeaponPhysCannon::CWeaponPhysCannon( void )
+{
+ m_flElementPosition = 0.0f;
+ m_flElementDestination = 0.0f;
+ m_bOpen = false;
+ m_nChangeState = ELEMENT_STATE_NONE;
+ m_flCheckSuppressTime = 0.0f;
+ m_EffectState = EFFECT_NONE;
+ m_flLastDenySoundPlayed = false;
+
+ m_flEndSpritesOverride[0] = 0.0f;
+ m_flEndSpritesOverride[1] = 0.0f;
+
+ m_bPhyscannonState = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::Precache( void )
+{
+ PrecacheModel( PHYSCANNON_BEAM_SPRITE );
+ PrecacheModel( PHYSCANNON_GLOW_SPRITE );
+ PrecacheModel( PHYSCANNON_ENDCAP_SPRITE );
+ PrecacheModel( PHYSCANNON_CENTER_GLOW );
+ PrecacheModel( PHYSCANNON_BLAST_SPRITE );
+
+ PrecacheModel( MEGACANNON_BEAM_SPRITE );
+ PrecacheModel( MEGACANNON_GLOW_SPRITE );
+ PrecacheModel( MEGACANNON_ENDCAP_SPRITE );
+ PrecacheModel( MEGACANNON_CENTER_GLOW );
+ PrecacheModel( MEGACANNON_BLAST_SPRITE );
+
+ PrecacheModel( MEGACANNON_RAGDOLL_BOOGIE_SPRITE );
+
+ // Precache our alternate model
+ PrecacheModel( MEGACANNON_MODEL );
+
+ PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" );
+ PrecacheScriptSound( "Weapon_Physgun.Off" );
+
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.DryFire" );
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.Launch" );
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.Pickup");
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.Drop");
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.HoldSound");
+ PrecacheScriptSound( "Weapon_MegaPhysCannon.ChargeZap");
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ // Need to get close to pick it up
+ CollisionProp()->UseTriggerBounds( false );
+
+ m_bPhyscannonState = IsMegaPhysCannon();
+
+ // The megacannon uses a different skin
+ if ( IsMegaPhysCannon() )
+ {
+ m_nSkin = MEGACANNON_SKIN;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Restore
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::OnRestore()
+{
+ BaseClass::OnRestore();
+ m_grabController.OnRestore();
+
+ m_bPhyscannonState = IsMegaPhysCannon();
+
+ // Tracker 8106: Physcannon effects disappear through level transition, so
+ // just recreate any effects here
+ if ( m_EffectState != EFFECT_NONE )
+ {
+ DoEffect( m_EffectState, NULL );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// On Remove
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::UpdateOnRemove(void)
+{
+ DestroyEffects( );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Sprite scale factor
+//-----------------------------------------------------------------------------
+inline float CWeaponPhysCannon::SpriteScaleFactor()
+{
+ return IsMegaPhysCannon() ? 1.5f : 1.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::Deploy( void )
+{
+ CloseElements();
+ DoEffect( EFFECT_READY );
+
+ // Unbloat our bounds
+ if ( IsMegaPhysCannon() )
+ {
+ CollisionProp()->UseTriggerBounds( false );
+ }
+
+ m_flTimeNextObjectPurge = gpGlobals->curtime;
+
+ return BaseClass::Deploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::SetViewModel( void )
+{
+ if ( !IsMegaPhysCannon() )
+ {
+ BaseClass::SetViewModel();
+ return;
+ }
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner == NULL )
+ return;
+
+ CBaseViewModel *vm = pOwner->GetViewModel( m_nViewModelIndex );
+ if ( vm == NULL )
+ return;
+
+ vm->SetWeaponModel( MEGACANNON_MODEL, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the cannon to drop anything it's carrying
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::ForceDrop( void )
+{
+ CloseElements();
+ DetachObject();
+ StopEffects();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Drops its held entity if it matches the entity passed in
+// Input : *pTarget -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return false;
+
+ CBaseEntity *pHeld = m_grabController.GetAttached();
+
+ if ( pHeld == NULL )
+ return false;
+
+ if ( pHeld == pTarget )
+ {
+ ForceDrop();
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::Drop( const Vector &vecVelocity )
+{
+ ForceDrop();
+ BaseClass::Drop( vecVelocity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::CanHolster( void )
+{
+ //Don't holster this weapon if we're holding onto something
+ if ( m_bActive )
+ return false;
+
+ return BaseClass::CanHolster();
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ //Don't holster this weapon if we're holding onto something
+ if ( m_bActive )
+ {
+ if ( m_grabController.GetAttached() == pSwitchingTo &&
+ GetOwner()->Weapon_OwnsThisType( pSwitchingTo->GetClassname(), pSwitchingTo->GetSubType()) )
+ {
+
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner )
+ {
+ pOwner->RumbleEffect( RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP );
+ }
+
+ ForceDrop();
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DryFire( void )
+{
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ WeaponSound( EMPTY );
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner )
+ {
+ pOwner->RumbleEffect( RUMBLE_PISTOL, 0, RUMBLE_FLAG_RESTART );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::PrimaryFireEffect( void )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ pOwner->ViewPunch( QAngle(-6, random->RandomInt(-2,2) ,0) );
+
+ color32 white = { 245, 245, 255, 32 };
+ UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN );
+
+ WeaponSound( SINGLE );
+}
+
+#define MAX_KNOCKBACK_FORCE 128
+
+//-----------------------------------------------------------------------------
+// Punt non-physics
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr )
+{
+ float flDamage = 1.0f;
+ if ( FClassnameIs( pEntity, "func_breakable" ) )
+ {
+ CBreakable *pBreak = dynamic_cast <CBreakable *>(pEntity);
+ if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
+ {
+ flDamage = physcannon_dmg_glass.GetFloat();
+ }
+ }
+
+ CTakeDamageInfo info;
+
+ info.SetAttacker( GetOwner() );
+ info.SetInflictor( this );
+ info.SetDamage( flDamage );
+ info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN );
+ info.SetDamageForce( forward ); // Scale?
+ info.SetDamagePosition( tr.endpos );
+
+ pEntity->DispatchTraceAttack( info, forward, &tr );
+
+ ApplyMultiDamage();
+
+ //Explosion effect
+ DoEffect( EFFECT_LAUNCH, &tr.endpos );
+
+ PrimaryFireEffect();
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+
+ m_nChangeState = ELEMENT_STATE_CLOSED;
+ m_flElementDebounce = gpGlobals->curtime + 0.5f;
+ m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
+}
+
+
+//-----------------------------------------------------------------------------
+// What happens when the physgun picks up something
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason )
+{
+ // If the target is debris, convert it to non-debris
+ if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ // Interactive debris converts back to debris when it comes to rest
+ pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ }
+
+ float mass = 0.0f;
+ if( pEntity->VPhysicsGetObject() )
+ {
+ mass = pEntity->VPhysicsGetObject()->GetMass();
+ }
+
+ if( reason == PUNTED_BY_CANNON )
+ {
+ pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAGS_NONE );
+ RecordThrownObject( pEntity );
+ }
+
+ // Warn Alyx if the player is punting a car around.
+ if( hl2_episodic.GetBool() && mass > 250.0f )
+ {
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ int nAIs = g_AI_Manager.NumAIs();
+
+ for ( int i = 0; i < nAIs; i++ )
+ {
+ if( ppAIs[ i ]->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ ppAIs[ i ]->DispatchInteraction( g_interactionPlayerPuntedHeavyObject, pEntity, pOwner );
+ }
+ }
+ }
+
+ Pickup_OnPhysGunPickup( pEntity, pOwner, reason );
+}
+
+
+//-----------------------------------------------------------------------------
+// Punt vphysics
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr )
+{
+ CTakeDamageInfo info;
+
+ Vector forward = vecForward;
+
+ info.SetAttacker( GetOwner() );
+ info.SetInflictor( this );
+ info.SetDamage( 0.0f );
+ info.SetDamageType( DMG_PHYSGUN );
+ pEntity->DispatchTraceAttack( info, forward, &tr );
+ ApplyMultiDamage();
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) )
+ {
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ if ( !listCount )
+ {
+ //FIXME: Do we want to do this if there's no physics object?
+ Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON );
+ DryFire();
+ return;
+ }
+
+ if( forward.z < 0 )
+ {
+ //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets
+ forward.z *= -0.65f;
+ }
+
+ // NOTE: Do this first to enable motion (if disabled) - so forces will work
+ // Tell the object it's been punted
+ Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON );
+
+ // don't push vehicles that are attached to the world via fixed constraints
+ // they will just wiggle...
+ if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() )
+ {
+ forward.Init();
+ }
+
+ if ( !IsMegaPhysCannon() && !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) )
+ {
+ int i;
+
+ // limit mass to avoid punting REALLY huge things
+ float totalMass = 0;
+ for ( i = 0; i < listCount; i++ )
+ {
+ totalMass += pList[i]->GetMass();
+ }
+ float maxMass = 250;
+ IServerVehicle *pVehicle = pEntity->GetServerVehicle();
+ if ( pVehicle )
+ {
+ maxMass *= 2.5; // 625 for vehicles
+ }
+ float mass = MIN(totalMass, maxMass); // max 250kg of additional force
+
+ // Put some spin on the object
+ for ( i = 0; i < listCount; i++ )
+ {
+ const float hitObjectFactor = 0.5f;
+ const float otherObjectFactor = 1.0f - hitObjectFactor;
+ // Must be light enough
+ float ratio = pList[i]->GetMass() / totalMass;
+ if ( pList[i] == pEntity->VPhysicsGetObject() )
+ {
+ ratio += hitObjectFactor;
+ ratio = MIN(ratio,1.0f);
+ }
+ else
+ {
+ ratio *= otherObjectFactor;
+ }
+ pList[i]->ApplyForceCenter( forward * 15000.0f * ratio );
+ pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos );
+ }
+ }
+ else
+ {
+ ApplyVelocityBasedForce( pEntity, vecForward, tr.endpos, PHYSGUN_FORCE_PUNTED );
+ }
+ }
+
+ // Add recoil
+ QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 );
+ pOwner->ViewPunch( recoil );
+
+ //Explosion effect
+ DoEffect( EFFECT_LAUNCH, &tr.endpos );
+
+ PrimaryFireEffect();
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+
+ m_nChangeState = ELEMENT_STATE_CLOSED;
+ m_flElementDebounce = gpGlobals->curtime + 0.5f;
+ m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
+
+ // Don't allow the gun to regrab a thrown object!!
+ m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Applies velocity-based forces to throw the entity. This code is
+// called from both punt and launch carried code.
+// ASSUMES: that pEntity is a vphysics entity.
+// Input : -
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward, const Vector &vecHitPos, PhysGunForce_t reason )
+{
+ // Get the launch velocity
+ Vector vVel = Pickup_PhysGunLaunchVelocity( pEntity, forward, reason );
+
+ // Get the launch angular impulse
+ AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, reason );
+
+ // Get the physics object (MUST have one)
+ IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
+ if ( pPhysicsObject == NULL )
+ {
+ Assert( 0 );
+ return;
+ }
+
+ // Affect the object
+ CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp*>( pEntity );
+ if ( pRagdoll == NULL )
+ {
+#ifdef HL2_EPISODIC
+ // The jeep being punted needs special force overrides
+ if ( reason == PHYSGUN_FORCE_PUNTED && pEntity->GetServerVehicle() )
+ {
+ // We want the point to emanate low on the vehicle to move it along the ground, not to twist it
+ Vector vecFinalPos = vecHitPos;
+ vecFinalPos.z = pEntity->GetAbsOrigin().z;
+ pPhysicsObject->ApplyForceOffset( vVel, vecFinalPos );
+ }
+ else
+ {
+ pPhysicsObject->AddVelocity( &vVel, &aVel );
+ }
+#else
+
+ pPhysicsObject->AddVelocity( &vVel, &aVel );
+
+#endif // HL2_EPISODIC
+ }
+ else
+ {
+ Vector vTempVel;
+ AngularImpulse vTempAVel;
+
+ ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( );
+ for ( int j = 0; j < pRagdollPhys->listCount; ++j )
+ {
+ pRagdollPhys->list[j].pObject->AddVelocity( &vVel, &aVel );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Punt non-physics
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::PuntRagdoll( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ Pickup_OnPhysGunDrop( pEntity, pOwner, LAUNCHED_BY_CANNON );
+
+ CTakeDamageInfo info;
+
+ Vector forward = vecForward;
+ info.SetAttacker( GetOwner() );
+ info.SetInflictor( this );
+ info.SetDamage( 0.0f );
+ info.SetDamageType( DMG_PHYSGUN );
+ pEntity->DispatchTraceAttack( info, forward, &tr );
+ ApplyMultiDamage();
+
+ if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) )
+ {
+ Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON );
+
+ if( forward.z < 0 )
+ {
+ //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets
+ forward.z *= -0.65f;
+ }
+
+ Vector vVel = forward * 1500;
+ AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED );
+
+ CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp*>( pEntity );
+ ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( );
+
+ int j;
+ for ( j = 0; j < pRagdollPhys->listCount; ++j )
+ {
+ pRagdollPhys->list[j].pObject->AddVelocity( &vVel, NULL );
+ }
+ }
+
+ // Add recoil
+ QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 );
+ pOwner->ViewPunch( recoil );
+
+ //Explosion effect
+ DoEffect( EFFECT_LAUNCH, &tr.endpos );
+
+ PrimaryFireEffect();
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+
+ m_nChangeState = ELEMENT_STATE_CLOSED;
+ m_flElementDebounce = gpGlobals->curtime + 0.5f;
+ m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
+
+ // Don't allow the gun to regrab a thrown object!!
+ m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Trace length
+//-----------------------------------------------------------------------------
+float CWeaponPhysCannon::TraceLength()
+{
+ if ( !IsMegaPhysCannon() )
+ {
+ return physcannon_tracelength.GetFloat();
+ }
+
+ return physcannon_mega_tracelength.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// If there's any special rejection code you need to do per entity then do it here
+// This is kinda nasty but I'd hate to move more physcannon related stuff into CBaseEntity
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity )
+{
+ if ( pEntity->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) )
+ {
+ CPhysBox *pPhysBox = dynamic_cast<CPhysBox*>(pEntity);
+
+ if ( pPhysBox != NULL )
+ {
+ if ( pPhysBox->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) )
+ {
+ return false;
+ }
+ }
+ }
+
+ if ( pEntity->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) )
+ {
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>(pEntity);
+
+ if ( pWeapon != NULL )
+ {
+ if ( pWeapon->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) )
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+// This mode is a toggle. Primary fire one time to pick up a physics object.
+// With an object held, click primary fire again to drop object.
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::PrimaryAttack( void )
+{
+ if( m_flNextPrimaryAttack > gpGlobals->curtime )
+ return;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ if( m_bActive )
+ {
+ // Punch the object being held!!
+ Vector forward;
+ pOwner->EyeVectors( &forward );
+
+ // Validate the item is within punt range
+ CBaseEntity *pHeld = m_grabController.GetAttached();
+ Assert( pHeld != NULL );
+
+ if ( pHeld != NULL )
+ {
+ float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length();
+
+ if ( heldDist > physcannon_tracelength.GetFloat() )
+ {
+ // We can't punt this yet
+ DryFire();
+ return;
+ }
+ }
+
+ LaunchObject( forward, physcannon_maxforce.GetFloat() );
+
+ PrimaryFireEffect();
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+ return;
+ }
+
+ // If not active, just issue a physics punch in the world.
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
+
+ Vector forward;
+ pOwner->EyeVectors( &forward );
+
+ // NOTE: Notice we're *not* using the mega tracelength here
+ // when you have the mega cannon. Punting has shorter range.
+ Vector start, end;
+ start = pOwner->Weapon_ShootPosition();
+ float flPuntDistance = physcannon_tracelength.GetFloat();
+ VectorMA( start, flPuntDistance, forward, end );
+
+ trace_t tr;
+ UTIL_PhyscannonTraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), pOwner, &tr );
+ bool bValid = true;
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
+ {
+ bValid = false;
+ }
+ else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) )
+ {
+ bValid = false;
+ }
+
+ // If the entity we've hit is invalid, try a traceline instead
+ if ( !bValid )
+ {
+ UTIL_PhyscannonTraceLine( start, end, pOwner, &tr );
+ if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
+ {
+ if( hl2_episodic.GetBool() )
+ {
+ // Try to find something in a very small cone.
+ CBaseEntity *pObject = FindObjectInCone( start, forward, physcannon_punt_cone.GetFloat() );
+
+ if( pObject )
+ {
+ // Trace to the object.
+ UTIL_PhyscannonTraceLine( start, pObject->WorldSpaceCenter(), pOwner, &tr );
+
+ if( tr.m_pEnt && tr.m_pEnt == pObject && !(pObject->IsEFlagSet(EFL_NO_PHYSCANNON_INTERACTION)) )
+ {
+ bValid = true;
+ pEntity = pObject;
+ }
+ }
+ }
+ }
+ else
+ {
+ bValid = true;
+ pEntity = tr.m_pEnt;
+ }
+ }
+
+ if ( ToPortalPlayer( pOwner )->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ CProp_Portal *pPortal = ToPortalPlayer( pOwner )->GetHeldObjectPortal();
+ UTIL_Portal_VectorTransform( pPortal->MatrixThisToLinked(), forward, forward );
+ }
+
+ if( !bValid )
+ {
+ DryFire();
+ return;
+ }
+
+ // See if we hit something
+ if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS )
+ {
+ if ( pEntity->m_takedamage == DAMAGE_NO )
+ {
+ DryFire();
+ return;
+ }
+
+ if( GetOwner()->IsPlayer() && !IsMegaPhysCannon() )
+ {
+ // Don't let the player zap any NPC's except regular antlions and headcrabs.
+ if( pEntity->IsNPC() && pEntity->Classify() != CLASS_HEADCRAB && !FClassnameIs(pEntity, "npc_antlion") )
+ {
+ DryFire();
+ return;
+ }
+ }
+
+ if ( IsMegaPhysCannon() )
+ {
+ if ( pEntity->IsNPC() && !pEntity->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) && pEntity->MyNPCPointer()->CanBecomeRagdoll() )
+ {
+ CTakeDamageInfo info( pOwner, pOwner, 1.0f, DMG_GENERIC );
+ CBaseEntity *pRagdoll = CreateServerRagdoll( pEntity->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
+ PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS );
+ pRagdoll->SetCollisionBounds( pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs() );
+
+ // Necessary to cause it to do the appropriate death cleanup
+ CTakeDamageInfo ragdollInfo( pOwner, pOwner, 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL );
+ pEntity->TakeDamage( ragdollInfo );
+
+ PuntRagdoll( pRagdoll, forward, tr );
+ return;
+ }
+ }
+
+ PuntNonVPhysics( pEntity, forward, tr );
+ }
+ else
+ {
+ if ( EntityAllowsPunts( pEntity) == false )
+ {
+ DryFire();
+ return;
+ }
+
+ if ( !IsMegaPhysCannon() )
+ {
+ if ( pEntity->VPhysicsIsFlesh( ) )
+ {
+ DryFire();
+ return;
+ }
+ PuntVPhysics( pEntity, forward, tr );
+ }
+ else
+ {
+ if ( dynamic_cast<CRagdollProp*>(pEntity) )
+ {
+ PuntRagdoll( pEntity, forward, tr );
+ }
+ else
+ {
+ PuntVPhysics( pEntity, forward, tr );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Click secondary attack whilst holding an object to hurl it.
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::SecondaryAttack( void )
+{
+ if ( m_flNextSecondaryAttack > gpGlobals->curtime )
+ return;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ // See if we should drop a held item
+ if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) )
+ {
+ // Drop the held object
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.5;
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.5;
+
+ DetachObject();
+
+ DoEffect( EFFECT_READY );
+
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ }
+ else
+ {
+ // Otherwise pick it up
+ FindObjectResult_t result = FindObject();
+ switch ( result )
+ {
+ case OBJECT_FOUND:
+ WeaponSound( SPECIAL1 );
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f;
+
+ // We found an object. Debounce the button
+ m_nAttack2Debounce |= pOwner->m_nButtons;
+ break;
+
+ case OBJECT_NOT_FOUND:
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f;
+ CloseElements();
+ break;
+
+ case OBJECT_BEING_DETACHED:
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f;
+ break;
+ }
+
+ DoEffect( EFFECT_HOLDING );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::WeaponIdle( void )
+{
+ if ( HasWeaponIdleTimeElapsed() )
+ {
+ if ( m_bActive )
+ {
+ //Shake when holding an item
+ SendWeaponAnim( ACT_VM_RELOAD );
+ }
+ else
+ {
+ //Otherwise idle simply
+ SendWeaponAnim( ACT_VM_IDLE );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pObject -
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition )
+{
+ if ( m_bActive )
+ return false;
+
+ if ( CanPickupObject( pObject ) == false )
+ return false;
+
+ m_grabController.SetIgnorePitch( false );
+ m_grabController.SetAngleAlignment( 0 );
+
+ bool bKilledByGrab = false;
+
+ bool bIsMegaPhysCannon = IsMegaPhysCannon();
+ if ( bIsMegaPhysCannon )
+ {
+ if ( pObject->IsNPC() && !pObject->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) )
+ {
+ Assert( pObject->MyNPCPointer()->CanBecomeRagdoll() );
+ CTakeDamageInfo info( GetOwner(), GetOwner(), 1.0f, DMG_GENERIC );
+ CBaseEntity *pRagdoll = CreateServerRagdoll( pObject->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
+ PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS );
+
+ pRagdoll->SetCollisionBounds( pObject->CollisionProp()->OBBMins(), pObject->CollisionProp()->OBBMaxs() );
+
+ // Necessary to cause it to do the appropriate death cleanup
+ CTakeDamageInfo ragdollInfo( GetOwner(), GetOwner(), 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL );
+ pObject->TakeDamage( ragdollInfo );
+
+ // Now we act on the ragdoll for the remainder of the time
+ pObject = pRagdoll;
+ bKilledByGrab = true;
+ }
+ }
+
+ IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
+
+ // Must be valid
+ if ( !pPhysics )
+ return false;
+
+ CPortal_Player *pOwner = ToPortalPlayer( GetOwner() );
+
+ m_bActive = true;
+ if( pOwner )
+ {
+#ifdef HL2_EPISODIC
+ CBreakableProp *pProp = dynamic_cast< CBreakableProp * >( pObject );
+
+ if ( pProp && pProp->HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) )
+ {
+ pOwner->FlashlightTurnOff();
+ }
+#endif
+
+ // NOTE: This can change the mass; so it must be done before max speed setting
+ Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON );
+ }
+
+ // NOTE :This must happen after OnPhysGunPickup because that can change the mass
+ m_grabController.AttachEntity( pOwner, pObject, pPhysics, bIsMegaPhysCannon, vPosition, (!bKilledByGrab) );
+
+ if( pOwner )
+ {
+#ifdef WIN32
+ // NVNT set the players constant force to simulate holding mass
+ HapticSetConstantForce(pOwner,clamp(m_grabController.GetLoadWeight()*0.05,1,5)*Vector(0,-1,0));
+#endif
+ pOwner->EnableSprint( false );
+
+ float loadWeight = ( 1.0f - GetLoadPercentage() );
+ float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight );
+
+ //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() );
+ pOwner->SetMaxSpeed( maxSpeed );
+ }
+
+ // Don't drop again for a slight delay, in case they were pulling objects near them
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f;
+
+ DoEffect( EFFECT_HOLDING );
+ OpenElements();
+
+ if ( GetMotorSound() )
+ {
+ (CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 );
+ (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f );
+ (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f );
+ }
+
+ return true;
+}
+
+void CWeaponPhysCannon::FindObjectTrace( CBasePlayer *pPlayer, trace_t *pTraceResult )
+{
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+
+ // Setup our positions
+ Vector start = pPlayer->Weapon_ShootPosition();
+ float testLength = TraceLength() * 4.0f;
+ Vector end = start + forward * testLength;
+
+ if( IsMegaPhysCannon() && hl2_episodic.GetBool() )
+ {
+ Vector vecAutoAimDir = pPlayer->GetAutoaimVector( 1.0f, testLength );
+ end = start + vecAutoAimDir * testLength;
+ }
+
+ // Try to find an object by looking straight ahead
+ UTIL_PhyscannonTraceLine( start, end, pPlayer, pTraceResult );
+
+ // Try again with a hull trace
+ if ( !pTraceResult->DidHitNonWorldEntity() )
+ {
+ UTIL_PhyscannonTraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), pPlayer, pTraceResult );
+ }
+}
+
+
+CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ Assert( pPlayer );
+ if ( pPlayer == NULL )
+ return OBJECT_NOT_FOUND;
+
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+
+ // Setup our positions
+ Vector start = pPlayer->Weapon_ShootPosition();
+ float testLength = TraceLength() * 4.0f;
+ Vector end = start + forward * testLength;
+ trace_t tr;
+ FindObjectTrace( pPlayer, &tr );
+ CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL;
+ bool bAttach = false;
+ bool bPull = false;
+
+ // If we hit something, pick it up or pull it
+ if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) )
+ {
+ // Attempt to attach if within range
+ if ( tr.fraction <= 0.25f )
+ {
+ bAttach = true;
+ }
+ else if ( tr.fraction > 0.25f )
+ {
+ bPull = true;
+ }
+ }
+
+
+ // Find anything within a general cone in front
+ CBaseEntity *pConeEntity = NULL;
+ if ( !IsMegaPhysCannon() )
+ {
+ if (!bAttach && !bPull)
+ {
+ pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() );
+ }
+ }
+ else
+ {
+ pConeEntity = MegaPhysCannonFindObjectInCone( start, forward,
+ physcannon_cone.GetFloat(), physcannon_ball_cone.GetFloat(), bAttach || bPull );
+ }
+
+ if ( pConeEntity )
+ {
+ pEntity = pConeEntity;
+
+ // If the object is near, grab it. Else, pull it a bit.
+ if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) )
+ {
+ bAttach = true;
+ }
+ else
+ {
+ bPull = true;
+ }
+ }
+
+ if ( CanPickupObject( pEntity ) == false )
+ {
+ CBaseEntity *pNewObject = Pickup_OnFailedPhysGunPickup( pEntity, start );
+
+ if ( pNewObject && CanPickupObject( pNewObject ) )
+ {
+ pEntity = pNewObject;
+ }
+ else
+ {
+ // Make a noise to signify we can't pick this up
+ if ( !m_flLastDenySoundPlayed )
+ {
+ m_flLastDenySoundPlayed = true;
+ WeaponSound( SPECIAL3 );
+ }
+
+ return OBJECT_NOT_FOUND;
+ }
+ }
+
+ // Check to see if the object is constrained + needs to be ripped off...
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) )
+ return OBJECT_BEING_DETACHED;
+
+ if ( bAttach )
+ {
+ return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND;
+ }
+
+ if ( !bPull )
+ return OBJECT_NOT_FOUND;
+
+ // FIXME: This needs to be run through the CanPickupObject logic
+ IPhysicsObject *pObj = pEntity->VPhysicsGetObject();
+ if ( !pObj )
+ return OBJECT_NOT_FOUND;
+
+ // If we're too far, simply start to pull the object towards us
+ Vector pullDir = start - pEntity->WorldSpaceCenter();
+ VectorNormalize( pullDir );
+ pullDir *= IsMegaPhysCannon() ? physcannon_mega_pullforce.GetFloat() : physcannon_pullforce.GetFloat();
+
+ float mass = PhysGetEntityMass( pEntity );
+ if ( mass < 50.0f )
+ {
+ pullDir *= (mass + 0.5) * (1/50.0f);
+ }
+
+ // Nudge it towards us
+ pObj->ApplyForceCenter( pullDir );
+ return OBJECT_NOT_FOUND;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CBaseEntity *CWeaponPhysCannon::MegaPhysCannonFindObjectInCone( const Vector &vecOrigin,
+ const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls )
+{
+ // Find the nearest physics-based item in a cone in front of me.
+ CBaseEntity *list[1024];
+ float flMaxDist = TraceLength() + 1.0;
+ float flNearestDist = flMaxDist;
+ bool bNearestIsCombineBall = bOnlyCombineBalls ? true : false;
+ Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist );
+ Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist );
+
+ CBaseEntity *pNearest = NULL;
+
+ int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 );
+ for( int i = 0 ; i < count ; i++ )
+ {
+ if ( !list[ i ]->VPhysicsGetObject() )
+ continue;
+
+ bool bIsCombineBall = FClassnameIs( list[ i ], "prop_combine_ball" );
+ if ( !bIsCombineBall && bNearestIsCombineBall )
+ continue;
+
+ // Closer than other objects
+ Vector los;
+ VectorSubtract( list[ i ]->WorldSpaceCenter(), vecOrigin, los );
+ float flDist = VectorNormalize( los );
+
+ if ( !bIsCombineBall || bNearestIsCombineBall )
+ {
+ // Closer than other objects
+ if( flDist >= flNearestDist )
+ continue;
+
+ // Cull to the cone
+ if ( DotProduct( los, vecDir ) <= flCone )
+ continue;
+ }
+ else
+ {
+ // Close enough?
+ if ( flDist >= flMaxDist )
+ continue;
+
+ // Cull to the cone
+ if ( DotProduct( los, vecDir ) <= flCone )
+ continue;
+
+ // NOW: If it's either closer than nearest dist or within the ball cone, use it!
+ if ( (flDist > flNearestDist) && (DotProduct( los, vecDir ) <= flCombineBallCone) )
+ continue;
+ }
+
+ // Make sure it isn't occluded!
+ trace_t tr;
+ UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr );
+ if( tr.m_pEnt == list[ i ] )
+ {
+ flNearestDist = flDist;
+ pNearest = list[ i ];
+ bNearestIsCombineBall = bIsCombineBall;
+ }
+ }
+
+ return pNearest;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone )
+{
+ // Find the nearest physics-based item in a cone in front of me.
+ CBaseEntity *list[256];
+ float flNearestDist = physcannon_tracelength.GetFloat() + 1.0; //Use regular distance.
+ Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist );
+ Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist );
+
+ CBaseEntity *pNearest = NULL;
+
+ int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 );
+ for( int i = 0 ; i < count ; i++ )
+ {
+ if ( !list[ i ]->VPhysicsGetObject() )
+ continue;
+
+ // Closer than other objects
+ Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin );
+ float flDist = VectorNormalize( los );
+ if( flDist >= flNearestDist )
+ continue;
+
+ // Cull to the cone
+ if ( DotProduct( los, vecDir ) <= flCone )
+ continue;
+
+ // Make sure it isn't occluded!
+ trace_t tr;
+ UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr );
+ if( tr.m_pEnt == list[ i ] )
+ {
+ flNearestDist = flDist;
+ pNearest = list[ i ];
+ }
+ }
+
+ return pNearest;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError )
+{
+ CBaseEntity *pPenetratedEntity = m_PenetratedEntity.Get();
+ if( pPenetratedEntity )
+ {
+ //FindClosestPassableSpace( pPenetratedEntity, Vector( 0.0f, 0.0f, 1.0f ) );
+ IPhysicsObject *pPhysObject = pPenetratedEntity->VPhysicsGetObject();
+ if( pPhysObject )
+ pPhysObject->Wake();
+
+ m_PenetratedEntity = NULL; //assume we won
+ }
+
+ CBaseEntity *pEntity = GetAttached();
+ if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() )
+ {
+ return false;
+ }
+
+ //Adrian: Oops, our object became motion disabled, let go!
+ IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() == false )
+ {
+ return false;
+ }
+
+ Vector forward, right, up;
+ QAngle playerAngles = pPlayer->EyeAngles();
+ float pitch = AngleDistance(playerAngles.x,0);
+ if( !m_bAllowObjectOverhead )
+ {
+ playerAngles.x = clamp( pitch, -75, 75 );
+ }
+ else
+ {
+ playerAngles.x = clamp( pitch, -90, 75 );
+ }
+ AngleVectors( playerAngles, &forward, &right, &up );
+
+ if ( HL2GameRules()->MegaPhyscannonActive() )
+ {
+ Vector los = ( pEntity->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition() );
+ VectorNormalize( los );
+
+ float flDot = DotProduct( los, forward );
+
+ //Let go of the item if we turn around too fast.
+ if ( flDot <= 0.35f )
+ return false;
+ }
+
+
+
+
+
+ Vector start = pPlayer->Weapon_ShootPosition();
+
+ // If the player is upside down then we need to hold the box closer to their feet.
+ if ( up.z < 0.0f )
+ start += pPlayer->GetViewOffset() * up.z;
+ if ( right.z < 0.0f )
+ start += pPlayer->GetViewOffset() * right.z;
+
+ CPortal_Player *pPortalPlayer = ToPortalPlayer( pPlayer );
+
+ // Find out if it's being held across a portal
+ bool bLookingAtHeldPortal = true;
+ CProp_Portal *pPortal = pPortalPlayer->GetHeldObjectPortal();
+
+ if ( !pPortal )
+ {
+ // If the portal is invalid make sure we don't try to hold it across the portal
+ pPortalPlayer->SetHeldObjectOnOppositeSideOfPortal( false );
+ }
+
+ if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ Ray_t rayPortalTest;
+ rayPortalTest.Init( start, start + forward * 1024.0f );
+
+ // Check if we're looking at the portal we're holding through
+ if ( pPortal )
+ {
+ if ( UTIL_IntersectRayWithPortal( rayPortalTest, pPortal ) < 0.0f )
+ {
+ bLookingAtHeldPortal = false;
+ }
+ }
+ // If our end point hasn't gone into the portal yet we at least need to know what portal is in front of us
+ else
+ {
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ float fMinDist = 2.0f;
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal->m_bActivated &&
+ (pTempPortal->m_hLinkedPortal.Get() != NULL) )
+ {
+ float fDist = UTIL_IntersectRayWithPortal( rayPortalTest, pTempPortal );
+ if( (fDist >= 0.0f) && (fDist < fMinDist) )
+ {
+ fMinDist = fDist;
+ pPortal = pTempPortal;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ pPortal = NULL;
+ }
+
+ QAngle qEntityAngles = pEntity->GetAbsAngles();
+
+ if ( pPortal )
+ {
+ // If the portal isn't linked we need to drop the object
+ if ( !pPortal->m_hLinkedPortal.Get() )
+ {
+ pPlayer->ForceDropOfCarriedPhysObjects();
+ return false;
+ }
+
+ UTIL_Portal_AngleTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), qEntityAngles, qEntityAngles );
+ }
+ // Now clamp a sphere of object radius at end to the player's bbox
+ Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, qEntityAngles, -forward );
+ Vector player2d = pPlayer->CollisionProp()->OBBMaxs();
+ float playerRadius = player2d.Length2D();
+
+ float radius = playerRadius + radial.Length();
+
+ float distance = 24 + ( radius * 2.0f );
+
+ // Add the prop's distance offset
+ distance += m_flDistanceOffset;
+
+ Vector end = start + ( forward * distance );
+
+ trace_t tr;
+ CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE );
+ Ray_t ray;
+ ray.Init( start, end );
+ //enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
+ UTIL_Portal_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
+
+ if ( tr.fraction < 0.5 )
+ {
+ end = start + forward * (radius*0.5f);
+ }
+ else if ( tr.fraction <= 1.0f )
+ {
+ end = start + forward * ( distance - radius );
+ }
+ Vector playerMins, playerMaxs, nearest;
+ pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs );
+ Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter();
+ CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL );
+
+ if( !m_bAllowObjectOverhead )
+ {
+ Vector delta = end - nearest;
+ float len = VectorNormalize(delta);
+ if ( len < radius )
+ {
+ end = nearest + radius * delta;
+ }
+ }
+
+ QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer );
+
+ //Show overlays of radius
+ if ( g_debug_physcannon.GetBool() )
+ {
+ NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 );
+
+ NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(),
+ -Vector( radius, radius, radius),
+ Vector( radius, radius, radius ),
+ 255, 0, 0,
+ true,
+ 0.0f );
+ }
+
+
+ // If it has a preferred orientation, update to ensure we're still oriented correctly.
+ Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
+
+ // We may be holding a prop that has preferred carry angles
+ if ( m_bHasPreferredCarryAngles )
+ {
+ matrix3x4_t tmp;
+ ComputePlayerMatrix( pPlayer, tmp );
+ angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp );
+ }
+
+ matrix3x4_t attachedToWorld;
+ Vector offset;
+ AngleMatrix( angles, attachedToWorld );
+ VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset );
+
+ // Translate hold position and angles across portal
+ if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
+ {
+ CProp_Portal *pPortalLinked = pPortal->m_hLinkedPortal;
+ if ( pPortal && pPortal->m_bActivated && pPortalLinked != NULL )
+ {
+ Vector vTeleportedPosition;
+ QAngle qTeleportedAngles;
+
+ if ( !bLookingAtHeldPortal && ( start - pPortal->GetAbsOrigin() ).Length() > distance - radius )
+ {
+ // Pull the object through the portal
+ Vector vPortalLinkedForward;
+ pPortalLinked->GetVectors( &vPortalLinkedForward, NULL, NULL );
+ vTeleportedPosition = pPortalLinked->GetAbsOrigin() - vPortalLinkedForward * ( 1.0f + offset.Length() );
+ qTeleportedAngles = pPortalLinked->GetAbsAngles();
+ }
+ else
+ {
+ // Translate hold position and angles across the portal
+ VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
+ UTIL_Portal_PointTransform( matThisToLinked, end - offset, vTeleportedPosition );
+ UTIL_Portal_AngleTransform( matThisToLinked, angles, qTeleportedAngles );
+ }
+
+ SetTargetPosition( vTeleportedPosition, qTeleportedAngles );
+ pPortalPlayer->SetHeldObjectPortal( pPortal );
+ }
+ else
+ {
+ pPlayer->ForceDropOfCarriedPhysObjects();
+ }
+ }
+ else
+ {
+ SetTargetPosition( end - offset, angles );
+ pPortalPlayer->SetHeldObjectPortal( NULL );
+ }
+
+ return true;
+}
+
+void CWeaponPhysCannon::UpdateObject( void )
+{
+ CPortal_Player *pPlayer = ToPortalPlayer( GetOwner() );
+ Assert( pPlayer );
+
+ float flError = IsMegaPhysCannon() ? 18 : 12;
+ if ( !m_grabController.UpdateObject( pPlayer, flError ) )
+ {
+ pPlayer->SetHeldObjectOnOppositeSideOfPortal( false );
+ DetachObject();
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched )
+{
+ if ( m_bActive == false )
+ return;
+
+ CPortal_Player *pOwner = (CPortal_Player *)ToBasePlayer( GetOwner() );
+ if( pOwner != NULL )
+ {
+ pOwner->EnableSprint( true );
+ pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() );
+ pOwner->SetHeldObjectOnOppositeSideOfPortal( false );
+ if( wasLaunched )
+ {
+ pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART );
+ }
+#ifdef WIN32
+ // NVNT clear constant force
+ HapticSetConstantForce(pOwner,Vector(0,0,0));
+#endif
+ }
+
+ CBaseEntity *pObject = m_grabController.GetAttached();
+
+ m_grabController.DetachEntity( wasLaunched );
+
+ if ( pObject != NULL )
+ {
+ Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON );
+ }
+
+ // Stop our looping sound
+ if ( GetMotorSound() )
+ {
+ (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
+ (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
+ }
+
+ m_bActive = false;
+
+ if ( playSound )
+ {
+ //Play the detach sound
+ WeaponSound( MELEE_MISS );
+ }
+
+ RecordThrownObject( pObject );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::ItemPreFrame()
+{
+ BaseClass::ItemPreFrame();
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ m_flElementPosition = UTIL_Approach( m_flElementDestination, m_flElementPosition, 0.1f );
+
+ CBaseViewModel *vm = pOwner->GetViewModel();
+
+ if ( vm != NULL )
+ {
+ vm->SetPoseParameter( "active", m_flElementPosition );
+ }
+
+ // Update the object if the weapon is switched on.
+ if( m_bActive )
+ {
+ UpdateObject();
+ }
+
+ if( gpGlobals->curtime >= m_flTimeNextObjectPurge )
+ {
+ PurgeThrownObjects();
+ m_flTimeNextObjectPurge = gpGlobals->curtime + 0.5f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::CheckForTarget( void )
+{
+ //See if we're suppressing this
+ if ( m_flCheckSuppressTime > gpGlobals->curtime )
+ return;
+
+ // holstered
+ if ( IsEffectActive( EF_NODRAW ) )
+ return;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ if ( m_bActive )
+ return;
+
+ trace_t tr;
+ FindObjectTrace( pOwner, &tr );
+
+ if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) )
+ {
+ float dist = (tr.endpos - tr.startpos).Length();
+ if ( dist <= TraceLength() )
+ {
+ // FIXME: Try just having the elements always open when pointed at a physics object
+ if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) )
+ // if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) )
+ {
+ m_nChangeState = ELEMENT_STATE_NONE;
+ OpenElements();
+ return;
+ }
+ }
+ }
+
+ // Close the elements after a delay to prevent overact state switching
+ if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) )
+ {
+ m_nChangeState = ELEMENT_STATE_CLOSED;
+ m_flElementDebounce = gpGlobals->curtime + 0.5f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Begin upgrading!
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::BeginUpgrade()
+{
+ if ( IsMegaPhysCannon() )
+ return;
+
+ if ( m_bIsCurrentlyUpgrading )
+ return;
+
+ SetSequence( SelectWeightedSequence( ACT_PHYSCANNON_UPGRADE ) );
+ ResetSequenceInfo();
+
+ m_bIsCurrentlyUpgrading = true;
+
+ SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 6.0f, s_pWaitForUpgradeContext );
+
+ EmitSound( "WeaponDissolve.Charge" );
+
+ // Bloat our bounds
+ CollisionProp()->UseTriggerBounds( true, 32.0f );
+
+ // Turn on the new skin
+ m_nSkin = MEGACANNON_SKIN;
+}
+
+
+//-----------------------------------------------------------------------------
+// Wait until we're done upgrading
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::WaitForUpgradeThink()
+{
+ Assert( m_bIsCurrentlyUpgrading );
+
+ StudioFrameAdvance();
+ if ( !IsActivityFinished() )
+ {
+ SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 0.1f, s_pWaitForUpgradeContext );
+ return;
+ }
+
+ if ( !GlobalEntity_IsInTable( "super_phys_gun" ) )
+ {
+ GlobalEntity_Add( MAKE_STRING("super_phys_gun"), gpGlobals->mapname, GLOBAL_ON );
+ }
+ else
+ {
+ GlobalEntity_SetState( MAKE_STRING("super_phys_gun"), GLOBAL_ON );
+ }
+ m_bIsCurrentlyUpgrading = false;
+
+ // This is necessary to get the effects to look different
+ DestroyEffects();
+
+ // HACK: Hacky notification back to the level that we've finish upgrading
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, "script_physcannon_upgrade" );
+ if ( pEnt )
+ {
+ variant_t emptyVariant;
+ pEnt->AcceptInput( "Trigger", this, this, emptyVariant, 0 );
+ }
+
+ StopSound( "WeaponDissolve.Charge" );
+
+ // Re-enable weapon pickup
+ AddSolidFlags( FSOLID_TRIGGER );
+
+ SetContextThink( NULL, gpGlobals->curtime, s_pWaitForUpgradeContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectIdle( void )
+{
+ if ( IsEffectActive( EF_NODRAW ) )
+ {
+ StopEffects();
+ return;
+ }
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ if ( m_bPhyscannonState != IsMegaPhysCannon() )
+ {
+ DestroyEffects();
+ StartEffects();
+
+ m_bPhyscannonState = IsMegaPhysCannon();
+
+ //This means we just switched to regular physcannon this frame.
+ if ( m_bPhyscannonState == false )
+ {
+ EmitSound( "Weapon_Physgun.Off" );
+
+#ifdef HL2_EPISODIC
+ ForceDrop();
+
+ CHL2_Player *pPlayer = dynamic_cast<CHL2_Player*>( pOwner );
+
+ if ( pPlayer )
+ {
+ pPlayer->StartArmorReduction();
+ }
+#endif
+
+ CCitadelEnergyCore *pCore = static_cast<CCitadelEnergyCore*>( CreateEntityByName( "env_citadel_energy_core" ) );
+
+ if ( pCore == NULL )
+ return;
+
+ CBaseAnimating *pBeamEnt = pOwner->GetViewModel();
+
+ if ( pBeamEnt )
+ {
+ int iAttachment = pBeamEnt->LookupAttachment( "muzzle" );
+
+ Vector vOrigin;
+ QAngle vAngle;
+
+ pBeamEnt->GetAttachment( iAttachment, vOrigin, vAngle );
+
+ pCore->SetAbsOrigin( vOrigin );
+ pCore->SetAbsAngles( vAngle );
+
+ DispatchSpawn( pCore );
+ pCore->Activate();
+
+ pCore->SetParent( pBeamEnt, iAttachment );
+ pCore->SetScale( 2.5f );
+
+ variant_t variant;
+ variant.SetFloat( 1.0f );
+
+ g_EventQueue.AddEvent( pCore, "StartDischarge", 0, pOwner, pOwner );
+ g_EventQueue.AddEvent( pCore, "Stop", variant, 1, pOwner, pOwner );
+
+ pCore->SetThink ( &CCitadelEnergyCore::SUB_Remove );
+ pCore->SetNextThink( gpGlobals->curtime + 10.0f );
+
+ m_nSkin = 0;
+ }
+ }
+ }
+
+ float flScaleFactor = SpriteScaleFactor();
+
+ // Flicker the end sprites
+ if ( ( m_hEndSprites[0] != NULL ) && ( m_hEndSprites[1] != NULL ) )
+ {
+ //Make the end points flicker as fast as possible
+ //FIXME: Make this a property of the CSprite class!
+ for ( int i = 0; i < 2; i++ )
+ {
+ m_hEndSprites[i]->SetBrightness( random->RandomInt( 200, 255 ) );
+ m_hEndSprites[i]->SetScale( random->RandomFloat( 0.1, 0.15 ) * flScaleFactor );
+ }
+ }
+
+ // Flicker the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] == NULL )
+ continue;
+
+ if ( IsMegaPhysCannon() )
+ {
+ m_hGlowSprites[i]->SetBrightness( random->RandomInt( 32, 48 ) );
+ m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.15, 0.2 ) * flScaleFactor );
+ }
+ else
+ {
+ m_hGlowSprites[i]->SetBrightness( random->RandomInt( 16, 24 ) );
+ m_hGlowSprites[i]->SetScale( random->RandomFloat( 0.3, 0.35 ) * flScaleFactor );
+ }
+ }
+
+ // Only do these effects on the mega-cannon
+ if ( IsMegaPhysCannon() )
+ {
+ // Randomly arc between the elements and core
+ if ( random->RandomInt( 0, 100 ) == 0 && !engine->IsPaused() )
+ {
+ CBeam *pBeam = CBeam::BeamCreate( MEGACANNON_BEAM_SPRITE, 1 );
+
+ CBaseEntity *pBeamEnt = pOwner->GetViewModel();
+ pBeam->EntsInit( pBeamEnt, pBeamEnt );
+
+ int startAttachment;
+ int sprite;
+
+ if ( random->RandomInt( 0, 1 ) )
+ {
+ startAttachment = LookupAttachment( "fork1t" );
+ sprite = 0;
+ }
+ else
+ {
+ startAttachment = LookupAttachment( "fork2t" );
+ sprite = 1;
+ }
+
+ int endAttachment = 1;
+
+ pBeam->SetStartAttachment( startAttachment );
+ pBeam->SetEndAttachment( endAttachment );
+ pBeam->SetNoise( random->RandomFloat( 8.0f, 16.0f ) );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->SetScrollRate( 25 );
+ pBeam->SetBrightness( 128 );
+ pBeam->SetWidth( 1 );
+ pBeam->SetEndWidth( random->RandomFloat( 2, 8 ) );
+
+ float lifetime = random->RandomFloat( 0.2f, 0.4f );
+
+ pBeam->LiveForTime( lifetime );
+
+ if ( m_hEndSprites[sprite] != NULL )
+ {
+ // Turn on the sprite for awhile
+ m_hEndSprites[sprite]->TurnOn();
+ m_flEndSpritesOverride[sprite] = gpGlobals->curtime + lifetime;
+ EmitSound( "Weapon_MegaPhysCannon.ChargeZap" );
+ }
+ }
+
+ if ( m_hCenterSprite != NULL )
+ {
+ if ( m_EffectState == EFFECT_HOLDING )
+ {
+ m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) );
+ m_hCenterSprite->SetScale( random->RandomFloat( 0.2, 0.25 ) * flScaleFactor );
+ }
+ else
+ {
+ m_hCenterSprite->SetBrightness( random->RandomInt( 32, 64 ) );
+ m_hCenterSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor );
+ }
+ }
+
+ if ( m_hBlastSprite != NULL )
+ {
+ if ( m_EffectState == EFFECT_HOLDING )
+ {
+ m_hBlastSprite->SetBrightness( random->RandomInt( 125, 150 ) );
+ m_hBlastSprite->SetScale( random->RandomFloat( 0.125, 0.15 ) * flScaleFactor );
+ }
+ else
+ {
+ m_hBlastSprite->SetBrightness( random->RandomInt( 32, 64 ) );
+ m_hBlastSprite->SetScale( random->RandomFloat( 0.075, 0.15 ) * flScaleFactor );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our idle effects even when deploying
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::ItemBusyFrame( void )
+{
+ DoEffectIdle();
+
+ BaseClass::ItemBusyFrame();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::ItemPostFrame()
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner == NULL )
+ {
+ // We found an object. Debounce the button
+ m_nAttack2Debounce = 0;
+ return;
+ }
+
+ //Check for object in pickup range
+ if ( m_bActive == false )
+ {
+ CheckForTarget();
+
+ if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) )
+ {
+ if ( m_nChangeState == ELEMENT_STATE_OPEN )
+ {
+ OpenElements();
+ }
+ else if ( m_nChangeState == ELEMENT_STATE_CLOSED )
+ {
+ CloseElements();
+ }
+
+ m_nChangeState = ELEMENT_STATE_NONE;
+ }
+ }
+
+ // NOTE: Attack2 will be considered to be pressed until the first item is picked up.
+ int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce);
+ if ( nAttack2Mask & IN_ATTACK2 )
+ {
+ SecondaryAttack();
+ }
+ else
+ {
+ // Reset our debouncer
+ m_flLastDenySoundPlayed = false;
+
+ if ( m_bActive == false )
+ {
+ DoEffect( EFFECT_READY );
+ }
+ }
+
+ if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 )
+ {
+ m_nAttack2Debounce = 0;
+ }
+
+ if ( pOwner->m_nButtons & IN_ATTACK )
+ {
+ PrimaryAttack();
+ }
+ else
+ {
+ WeaponIdle();
+ }
+
+ if ( hl2_episodic.GetBool() == true )
+ {
+ if ( IsMegaPhysCannon() )
+ {
+ if ( !( pOwner->m_nButtons & IN_ATTACK ) )
+ {
+ m_flNextPrimaryAttack = gpGlobals->curtime;
+ }
+ }
+ }
+
+ // Update our idle effects (flickers, etc)
+ DoEffectIdle();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+#define PHYSCANNON_DANGER_SOUND_RADIUS 128
+
+void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce )
+{
+ // FIRE!!!
+ if( m_grabController.GetAttached() )
+ {
+ CBaseEntity *pObject = m_grabController.GetAttached();
+
+ gamestats->Event_Punted( pObject );
+
+ DetachObject( false, true );
+
+ // Trace ahead a bit and make a chain of danger sounds ahead of the phys object
+ // to scare potential targets
+ trace_t tr;
+ Vector vecStart = pObject->GetAbsOrigin();
+ Vector vecSpot;
+ int iLength;
+ int i;
+
+ UTIL_TraceLine( vecStart, vecStart + vecDir * flForce, MASK_SHOT, pObject, COLLISION_GROUP_NONE, &tr );
+ iLength = ( tr.startpos - tr.endpos ).Length();
+ vecSpot = vecStart + vecDir * PHYSCANNON_DANGER_SOUND_RADIUS;
+
+ for( i = PHYSCANNON_DANGER_SOUND_RADIUS ; i < iLength ; i += PHYSCANNON_DANGER_SOUND_RADIUS )
+ {
+ CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, PHYSCANNON_DANGER_SOUND_RADIUS, 0.5, pObject );
+ vecSpot = vecSpot + ( vecDir * PHYSCANNON_DANGER_SOUND_RADIUS );
+ }
+
+ // Launch
+ ApplyVelocityBasedForce( pObject, vecDir, tr.endpos, PHYSGUN_FORCE_LAUNCHED );
+
+ // Don't allow the gun to regrab a thrown object!!
+ m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5;
+
+ Vector center = pObject->WorldSpaceCenter();
+
+ //Do repulse effect
+ DoEffect( EFFECT_LAUNCH, &center );
+ }
+
+ // Stop our looping sound
+ if ( GetMotorSound() )
+ {
+ (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
+ (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
+ }
+
+ //Close the elements and suppress checking for a bit
+ m_nChangeState = ELEMENT_STATE_CLOSED;
+ m_flElementDebounce = gpGlobals->curtime + 0.1f;
+ m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTarget -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return false;
+
+ if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() )
+ return false;
+
+ if ( pTarget->HasSpawnFlags( SF_PHYSBOX_ALWAYS_PICK_UP ) || pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) )
+ {
+ // It may seem strange to check this spawnflag before we know the class of this object, since the
+ // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number
+ // of irrelevant entities that fall through to this next casting check, which is slower.
+ CPhysBox *pPhysBox = dynamic_cast<CPhysBox*>(pTarget);
+
+ if ( pPhysBox != NULL )
+ {
+ if ( pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) )
+ return false;
+ else
+ return true;
+ }
+ }
+
+ if ( pTarget->HasSpawnFlags(SF_PHYSPROP_ALWAYS_PICK_UP) )
+ {
+ // It may seem strange to check this spawnflag before we know the class of this object, since the
+ // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number
+ // of irrelevant entities that fall through to this next casting check, which is slower.
+ CPhysicsProp *pPhysProp = dynamic_cast<CPhysicsProp*>(pTarget);
+ if ( pPhysProp != NULL )
+ return true;
+ }
+
+ if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
+ return false;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner && pOwner->GetGroundEntity() == pTarget )
+ return false;
+
+ if ( !IsMegaPhysCannon() )
+ {
+ if ( pTarget->VPhysicsIsFlesh( ) )
+ return false;
+ return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 );
+ }
+
+ if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() )
+ return true;
+
+ if ( dynamic_cast<CRagdollProp*>(pTarget) )
+ return true;
+
+ return CBasePlayer::CanPickupObject( pTarget, 0, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::OpenElements( void )
+{
+ if ( m_bOpen )
+ return;
+
+ WeaponSound( SPECIAL2 );
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ if( !IsMegaPhysCannon() )
+ {
+ pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_RESTART|RUMBLE_FLAG_LOOP);
+ }
+
+ if ( m_flElementPosition < 0.0f )
+ m_flElementPosition = 0.0f;
+
+ m_flElementDestination = 1.0f;
+
+ SendWeaponAnim( ACT_VM_IDLE );
+
+ m_bOpen = true;
+
+ DoEffect( EFFECT_READY );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::CloseElements( void )
+{
+ // The mega cannon cannot be closed!
+ if ( IsMegaPhysCannon() )
+ {
+ OpenElements();
+ return;
+ }
+
+ if ( m_bOpen == false )
+ return;
+
+ WeaponSound( MELEE_HIT );
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ pOwner->RumbleEffect(RUMBLE_PHYSCANNON_OPEN, 0, RUMBLE_FLAG_STOP);
+
+ if ( m_flElementPosition > 1.0f )
+ m_flElementPosition = 1.0f;
+
+ m_flElementDestination = 0.0f;
+
+ SendWeaponAnim( ACT_VM_IDLE );
+
+ m_bOpen = false;
+
+ if ( GetMotorSound() )
+ {
+ (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
+ (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
+ }
+
+ DoEffect( EFFECT_CLOSED );
+}
+
+#define PHYSCANNON_MAX_MASS 500
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CWeaponPhysCannon::GetLoadPercentage( void )
+{
+ float loadWeight = m_grabController.GetLoadWeight();
+ loadWeight /= physcannon_maxmass.GetFloat();
+ loadWeight = clamp( loadWeight, 0.0f, 1.0f );
+ return loadWeight;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CSoundPatch
+//-----------------------------------------------------------------------------
+CSoundPatch *CWeaponPhysCannon::GetMotorSound( void )
+{
+ if ( m_sndMotor == NULL )
+ {
+ CPASAttenuationFilter filter( this );
+
+ if ( IsMegaPhysCannon() )
+ {
+ m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_MegaPhysCannon.HoldSound", ATTN_NORM );
+ }
+ else
+ {
+ m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM );
+ }
+ }
+
+ return m_sndMotor;
+}
+
+
+//-----------------------------------------------------------------------------
+// Shuts down sounds
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::StopLoopingSounds()
+{
+ if ( m_sndMotor != NULL )
+ {
+ (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor );
+ m_sndMotor = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DestroyEffects( void )
+{
+ //Turn off main glow
+ if ( m_hCenterSprite != NULL )
+ {
+ UTIL_Remove( m_hCenterSprite );
+ m_hCenterSprite = NULL;
+ }
+
+ if ( m_hBlastSprite != NULL )
+ {
+ UTIL_Remove( m_hBlastSprite );
+ m_hBlastSprite = NULL;
+ }
+
+ // Turn off beams
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ UTIL_Remove( m_hBeams[i] );
+ m_hBeams[i] = NULL;
+ }
+ }
+
+ // Turn off sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ UTIL_Remove( m_hGlowSprites[i] );
+ m_hGlowSprites[i] = NULL;
+ }
+ }
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ UTIL_Remove( m_hEndSprites[i] );
+ m_hEndSprites[i] = NULL;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::StopEffects( bool stopSound )
+{
+ // Turn off our effect state
+ DoEffect( EFFECT_NONE );
+
+ //Turn off main glow
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->TurnOff();
+ }
+
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOff();
+ }
+
+ //Turn off beams
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 0 );
+ }
+ }
+
+ //Turn off sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOff();
+ }
+ }
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+
+ //Shut off sounds
+ if ( stopSound && GetMotorSound() != NULL )
+ {
+ (CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::StartEffects( void )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner == NULL )
+ return;
+
+ bool bIsMegaCannon = IsMegaPhysCannon();
+
+ int i;
+ float flScaleFactor = SpriteScaleFactor();
+ CBaseEntity *pBeamEnt = pOwner->GetViewModel();
+
+ // Create the beams
+ for ( i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] )
+ continue;
+
+ const char *beamAttachNames[] =
+ {
+ "fork1t",
+ "fork2t",
+ "fork1t",
+ "fork2t",
+ "fork1t",
+ "fork2t",
+ };
+
+ m_hBeams[i] = CBeam::BeamCreate(
+ bIsMegaCannon ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 1.0f );
+ m_hBeams[i]->EntsInit( pBeamEnt, pBeamEnt );
+
+ int startAttachment = LookupAttachment( beamAttachNames[i] );
+ int endAttachment = 1;
+
+ m_hBeams[i]->FollowEntity( pBeamEnt );
+
+ m_hBeams[i]->AddSpawnFlags( SF_BEAM_TEMPORARY );
+ m_hBeams[i]->SetStartAttachment( startAttachment );
+ m_hBeams[i]->SetEndAttachment( endAttachment );
+ m_hBeams[i]->SetNoise( random->RandomFloat( 8.0f, 16.0f ) );
+ m_hBeams[i]->SetColor( 255, 255, 255 );
+ m_hBeams[i]->SetScrollRate( 25 );
+ m_hBeams[i]->SetBrightness( 128 );
+ m_hBeams[i]->SetWidth( 0 );
+ m_hBeams[i]->SetEndWidth( random->RandomFloat( 2, 4 ) );
+ }
+
+ //Create the glow sprites
+ for ( i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] )
+ continue;
+
+ const char *attachNames[] =
+ {
+ "fork1b",
+ "fork1m",
+ "fork1t",
+ "fork2b",
+ "fork2m",
+ "fork2t"
+ };
+
+ m_hGlowSprites[i] = CSprite::SpriteCreate(
+ bIsMegaCannon ? MEGACANNON_GLOW_SPRITE : PHYSCANNON_GLOW_SPRITE,
+ GetAbsOrigin(), false );
+
+ m_hGlowSprites[i]->SetAsTemporary();
+
+ m_hGlowSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) );
+
+ if ( bIsMegaCannon )
+ {
+ m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 128, kRenderFxNone );
+ }
+ else
+ {
+ m_hGlowSprites[i]->SetTransparency( kRenderTransAdd, 255, 128, 0, 64, kRenderFxNoDissipation );
+ }
+
+ m_hGlowSprites[i]->SetBrightness( 255, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f );
+ }
+
+ //Create the endcap sprites
+ for ( i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] == NULL )
+ {
+ const char *attachNames[] =
+ {
+ "fork1t",
+ "fork2t"
+ };
+
+ m_hEndSprites[i] = CSprite::SpriteCreate(
+ bIsMegaCannon ? MEGACANNON_ENDCAP_SPRITE : PHYSCANNON_ENDCAP_SPRITE,
+ GetAbsOrigin(), false );
+
+ m_hEndSprites[i]->SetAsTemporary();
+ m_hEndSprites[i]->SetAttachment( pOwner->GetViewModel(), LookupAttachment( attachNames[i] ) );
+ m_hEndSprites[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_hEndSprites[i]->SetBrightness( 255, 0.2f );
+ m_hEndSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f );
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+
+ //Create the center glow
+ if ( m_hCenterSprite == NULL )
+ {
+ m_hCenterSprite = CSprite::SpriteCreate(
+ bIsMegaCannon ? MEGACANNON_CENTER_GLOW : PHYSCANNON_CENTER_GLOW,
+ GetAbsOrigin(), false );
+
+ m_hCenterSprite->SetAsTemporary();
+ m_hCenterSprite->SetAttachment( pOwner->GetViewModel(), 1 );
+ m_hCenterSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ m_hCenterSprite->SetBrightness( 255, 0.2f );
+ m_hCenterSprite->SetScale( 0.1f, 0.2f );
+ }
+
+ //Create the blast sprite
+ if ( m_hBlastSprite == NULL )
+ {
+ m_hBlastSprite = CSprite::SpriteCreate(
+ bIsMegaCannon ? MEGACANNON_BLAST_SPRITE : PHYSCANNON_BLAST_SPRITE,
+ GetAbsOrigin(), false );
+
+ m_hBlastSprite->SetAsTemporary();
+ m_hBlastSprite->SetAttachment( pOwner->GetViewModel(), 1 );
+ m_hBlastSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ m_hBlastSprite->SetBrightness( 255, 0.2f );
+ m_hBlastSprite->SetScale( 0.1f, 0.2f );
+ m_hBlastSprite->TurnOff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Closing effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectClosed( void )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ // Turn off the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 0.0, 0.1f );
+ m_hCenterSprite->SetScale( 0.0f, 0.1f );
+ m_hCenterSprite->TurnOff();
+ }
+
+ // Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+
+ // Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 0 );
+ }
+ }
+
+ // Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 16.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f );
+ }
+ }
+
+ // Prepare for scale down
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOn();
+ m_hBlastSprite->SetScale( 1.0f, 0.0f );
+ m_hBlastSprite->SetBrightness( 0, 0.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Closing effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoMegaEffectClosed( void )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ // Turn off the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 0.0, 0.1f );
+ m_hCenterSprite->SetScale( 0.0f, 0.1f );
+ m_hCenterSprite->TurnOff();
+ }
+
+ // Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+
+ // Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 0 );
+ }
+ }
+
+ // Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 16.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.3f * flScaleFactor, 0.2f );
+ }
+ }
+
+ // Prepare for scale down
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOn();
+ m_hBlastSprite->SetScale( 1.0f, 0.0f );
+ m_hBlastSprite->SetBrightness( 0, 0.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Ready effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectReady( )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ //Turn on the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 128, 0.2f );
+ m_hCenterSprite->SetScale( 0.15f, 0.2f );
+ m_hCenterSprite->TurnOn();
+ }
+
+ //Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+
+ //Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 0 );
+ }
+ }
+
+ //Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 32.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.4f * flScaleFactor, 0.2f );
+ }
+ }
+
+ //Scale down
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOn();
+ m_hBlastSprite->SetScale( 0.1f, 0.2f );
+ m_hBlastSprite->SetBrightness( 255, 0.1f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Holding effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectHolding( )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ // Turn off the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 255, 0.1f );
+ m_hCenterSprite->SetScale( 0.2f, 0.2f );
+ m_hCenterSprite->TurnOn();
+ }
+
+ // Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOn();
+ }
+ }
+
+ // Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 128 );
+ }
+ }
+
+ // Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 64.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.5f * flScaleFactor, 0.2f );
+ }
+ }
+
+ // Prepare for scale up
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOff();
+ m_hBlastSprite->SetScale( 0.1f, 0.0f );
+ m_hBlastSprite->SetBrightness( 0, 0.0f );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Launch effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectLaunch( Vector *pos )
+{
+ Assert( pos );
+ if ( pos == NULL )
+ return;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner == NULL )
+ return;
+
+ Vector endpos = *pos;
+
+ // Check to store off our view model index
+ CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 8 );
+
+ if ( pBeam != NULL )
+ {
+ pBeam->PointEntInit( endpos, this );
+ pBeam->SetEndAttachment( 1 );
+ pBeam->SetWidth( 6.4 );
+ pBeam->SetEndWidth( 12.8 );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->LiveForTime( 0.1f );
+ pBeam->RelinkBeam();
+ pBeam->SetNoise( 2 );
+ }
+
+ Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() );
+ VectorNormalize( shotDir );
+
+ //End hit
+ //FIXME: Probably too big
+ CPVSFilter filter( endpos );
+ te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 );
+
+ if ( m_hBlastSprite != NULL )
+ {
+ m_hBlastSprite->TurnOn();
+ m_hBlastSprite->SetScale( 2.0f, 0.1f );
+ m_hBlastSprite->SetBrightness( 0.0f, 0.1f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pos -
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoMegaEffectLaunch( Vector *pos )
+{
+ Assert( pos );
+ if ( pos == NULL )
+ return;
+
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner == NULL )
+ return;
+
+ Vector endpos = *pos;
+
+ // Check to store off our view model index
+ CBaseViewModel *vm = pOwner->GetViewModel();
+
+ int numBeams = random->RandomInt( 1, 2 );
+
+ CBeam *pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 );
+
+ if ( pBeam != NULL )
+ {
+ pBeam->PointEntInit( endpos, vm );
+ pBeam->SetEndAttachment( 1 );
+ pBeam->SetWidth( 2 );
+ pBeam->SetEndWidth( 12 );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->LiveForTime( 0.1f );
+ pBeam->RelinkBeam();
+ pBeam->SetNoise( 0 );
+ }
+
+ for ( int i = 0; i < numBeams; i++ )
+ {
+ pBeam = CBeam::BeamCreate( IsMegaPhysCannon() ? MEGACANNON_BEAM_SPRITE : PHYSCANNON_BEAM_SPRITE, 0.8 );
+
+ if ( pBeam != NULL )
+ {
+ pBeam->PointEntInit( endpos, vm );
+ pBeam->SetEndAttachment( 1 );
+ pBeam->SetWidth( 2 );
+ pBeam->SetEndWidth( random->RandomInt( 1, 2 ) );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->LiveForTime( 0.1f );
+ pBeam->RelinkBeam();
+ pBeam->SetNoise( random->RandomInt( 8, 12 ) );
+ }
+ }
+
+ Vector shotDir = ( endpos - pOwner->Weapon_ShootPosition() );
+ VectorNormalize( shotDir );
+
+ //End hit
+ //FIXME: Probably too big
+ CPVSFilter filter( endpos );
+ te->GaussExplosion( filter, 0.0f, endpos - ( shotDir * 4.0f ), RandomVector(-1.0f, 1.0f), 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Holding effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoMegaEffectHolding( void )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ // Turn off the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 255, 0.1f );
+ m_hCenterSprite->SetScale( 0.2f, 0.2f );
+ m_hCenterSprite->TurnOn();
+ }
+
+ // Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ m_hEndSprites[i]->TurnOn();
+ }
+ }
+
+ // Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 128 );
+ }
+ }
+
+ // Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 32.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.25f * flScaleFactor, 0.2f );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Ready effects
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoMegaEffectReady( void )
+{
+ float flScaleFactor = SpriteScaleFactor();
+
+ //Turn on the center sprite
+ if ( m_hCenterSprite != NULL )
+ {
+ m_hCenterSprite->SetBrightness( 128, 0.2f );
+ m_hCenterSprite->SetScale( 0.15f, 0.2f );
+ m_hCenterSprite->TurnOn();
+ }
+
+ //Turn off the end-caps
+ for ( int i = 0; i < 2; i++ )
+ {
+ if ( m_hEndSprites[i] != NULL )
+ {
+ if ( m_flEndSpritesOverride[i] < gpGlobals->curtime )
+ {
+ m_hEndSprites[i]->TurnOff();
+ }
+ }
+ }
+
+ //Turn off the lightning
+ for ( int i = 0; i < NUM_BEAMS; i++ )
+ {
+ if ( m_hBeams[i] != NULL )
+ {
+ m_hBeams[i]->SetBrightness( 0 );
+ }
+ }
+
+ //Turn on the glow sprites
+ for ( int i = 0; i < NUM_SPRITES; i++ )
+ {
+ if ( m_hGlowSprites[i] != NULL )
+ {
+ m_hGlowSprites[i]->TurnOn();
+ m_hGlowSprites[i]->SetBrightness( 24.0f, 0.2f );
+ m_hGlowSprites[i]->SetScale( 0.2f * flScaleFactor, 0.2f );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shutdown for the weapon when it's holstered
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffectNone( void )
+{
+ if ( m_hBlastSprite != NULL )
+ {
+ // Become small
+ m_hBlastSprite->SetScale( 0.001f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : effectType -
+// *pos -
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoMegaEffect( int effectType, Vector *pos )
+{
+ switch( effectType )
+ {
+ case EFFECT_CLOSED:
+ DoMegaEffectClosed();
+ break;
+
+ case EFFECT_READY:
+ DoMegaEffectReady();
+ break;
+
+ case EFFECT_HOLDING:
+ DoMegaEffectHolding();
+ break;
+
+ case EFFECT_LAUNCH:
+ DoMegaEffectLaunch( pos );
+ break;
+
+ default:
+ case EFFECT_NONE:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : effectType -
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos )
+{
+ // Make sure we're active
+ StartEffects();
+
+ m_EffectState = effectType;
+
+ // Do different effects when upgraded
+ if ( IsMegaPhysCannon() )
+ {
+ DoMegaEffect( effectType, pos );
+ return;
+ }
+
+ switch( effectType )
+ {
+ case EFFECT_CLOSED:
+ DoEffectClosed( );
+ break;
+
+ case EFFECT_READY:
+ DoEffectReady( );
+ break;
+
+ case EFFECT_HOLDING:
+ DoEffectHolding();
+ break;
+
+ case EFFECT_LAUNCH:
+ DoEffectLaunch( pos );
+ break;
+
+ default:
+ case EFFECT_NONE:
+ DoEffectNone();
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : iIndex -
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const
+{
+ // Just do this normally if we're a normal physcannon
+ if ( PlayerHasMegaPhysCannon() == false )
+ return BaseClass::GetShootSound( iIndex );
+
+ // We override this if we're the charged up version
+ switch( iIndex )
+ {
+ case EMPTY:
+ return "Weapon_MegaPhysCannon.DryFire";
+ break;
+
+ case SINGLE:
+ return "Weapon_MegaPhysCannon.Launch";
+ break;
+
+ case SPECIAL1:
+ return "Weapon_MegaPhysCannon.Pickup";
+ break;
+
+ case MELEE_MISS:
+ return "Weapon_MegaPhysCannon.Drop";
+ break;
+
+ default:
+ break;
+ }
+
+ return BaseClass::GetShootSound( iIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds the specified object to the list of objects that have been
+// propelled by this physgun, along with a timestamp of when the
+// object was added to the list. This list is checked when a physics
+// object strikes another entity, to resolve whether the player is
+// accountable for the impact.
+//
+// Input : pObject - pointer to the object being thrown by the physcannon.
+//-----------------------------------------------------------------------------
+void CWeaponPhysCannon::RecordThrownObject( CBaseEntity *pObject )
+{
+ thrown_objects_t thrown;
+ thrown.hEntity = pObject;
+ thrown.fTimeThrown = gpGlobals->curtime;
+
+ // Get rid of stale and dead objects in the list.
+ PurgeThrownObjects();
+
+ // See if this object is already in the list.
+ int count = m_ThrownEntities.Count();
+
+ for( int i = 0 ; i < count ; i++ )
+ {
+ if( m_ThrownEntities[i].hEntity == pObject )
+ {
+ // Just update the time.
+ //Msg("++UPDATING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() );
+ m_ThrownEntities[i] = thrown;
+ return;
+ }
+ }
+
+ //Msg("++ADDING: %s (%d)\n", pObject->GetClassname(), pObject->entindex() );
+
+ m_ThrownEntities.AddToTail(thrown);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Go through the objects in the thrown objects list and discard any
+// objects that have gone 'stale'. (Were thrown several seconds ago), or
+// have been destroyed or removed.
+//
+//-----------------------------------------------------------------------------
+#define PHYSCANNON_THROWN_LIST_TIMEOUT 10.0f
+void CWeaponPhysCannon::PurgeThrownObjects()
+{
+ bool bListChanged;
+
+ // This is bubble-sorty, but the list is also very short.
+ do
+ {
+ bListChanged = false;
+
+ int count = m_ThrownEntities.Count();
+ for( int i = 0 ; i < count ; i++ )
+ {
+ bool bRemove = false;
+
+ if( !m_ThrownEntities[i].hEntity.Get() )
+ {
+ bRemove = true;
+ }
+ else if( gpGlobals->curtime > (m_ThrownEntities[i].fTimeThrown + PHYSCANNON_THROWN_LIST_TIMEOUT) )
+ {
+ bRemove = true;
+ }
+ else
+ {
+ IPhysicsObject *pObject = m_ThrownEntities[i].hEntity->VPhysicsGetObject();
+
+ if( pObject && pObject->IsAsleep() )
+ {
+ bRemove = true;
+ }
+ }
+
+ if( bRemove )
+ {
+ //Msg("--REMOVING: %s (%d)\n", m_ThrownEntities[i].hEntity->GetClassname(), m_ThrownEntities[i].hEntity->entindex() );
+ m_ThrownEntities.Remove(i);
+ bListChanged = true;
+ break;
+ }
+ }
+ } while( bListChanged );
+}
+
+bool CWeaponPhysCannon::IsAccountableForObject( CBaseEntity *pObject )
+{
+ // Clean out the stale and dead items.
+ PurgeThrownObjects();
+
+ // Now if this object is in the list, the player is accountable for it striking something.
+ int count = m_ThrownEntities.Count();
+
+ for( int i = 0 ; i < count ; i++ )
+ {
+ if( m_ThrownEntities[i].hEntity == pObject )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// EXTERNAL API
+//-----------------------------------------------------------------------------
+void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis )
+{
+ CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
+ if ( pCannon )
+ {
+ if ( pOnlyIfHoldingThis )
+ {
+ pCannon->DropIfEntityHeld( pOnlyIfHoldingThis );
+ }
+ else
+ {
+ pCannon->ForceDrop();
+ }
+ }
+}
+
+void PhysCannonBeginUpgrade( CBaseAnimating *pAnim )
+{
+ CWeaponPhysCannon *pWeaponPhyscannon = assert_cast< CWeaponPhysCannon* >( pAnim );
+ pWeaponPhyscannon->BeginUpgrade();
+}
+
+
+bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity )
+{
+ CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
+
+ return pController ? pController->IsHoldingEntity( pHeldEntity ) : false;
+}
+
+void ShutdownPickupController( CBaseEntity *pPickupControllerEntity )
+{
+ CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
+
+ pController->Shutdown( false );
+}
+
+
+float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject )
+{
+ float mass = 0.0f;
+ CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
+ if ( pCannon )
+ {
+ CGrabController &grab = pCannon->GetGrabController();
+ mass = grab.GetSavedMass( pHeldObject );
+ }
+
+ return mass;
+}
+
+CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon )
+{
+ CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
+ if ( pCannon )
+ {
+ CGrabController &grab = pCannon->GetGrabController();
+ return grab.GetAttached();
+ }
+
+ return NULL;
+}
+
+CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer )
+{
+ CBaseEntity *pObject = NULL;
+ CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity());
+
+ if ( pPlayerPickupController )
+ {
+ pObject = pPlayerPickupController->GetGrabController().GetAttached();
+ }
+
+ return pObject;
+}
+
+CBasePlayer *GetPlayerHoldingEntity( CBaseEntity *pEntity )
+{
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ if ( GetPlayerHeldEntity( pPlayer ) == pEntity || PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() ) == pEntity )
+ return pPlayer;
+ }
+ }
+ return NULL;
+}
+
+CGrabController *GetGrabControllerForPlayer( CBasePlayer *pPlayer )
+{
+ CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity());
+ if( pPlayerPickupController )
+ return &(pPlayerPickupController->GetGrabController());
+
+ return NULL;
+}
+
+CGrabController *GetGrabControllerForPhysCannon( CBaseCombatWeapon *pActiveWeapon )
+{
+ CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
+ if ( pCannon )
+ {
+ return &(pCannon->GetGrabController());
+ }
+
+ return NULL;
+}
+
+void GetSavedParamsForCarriedPhysObject( CGrabController *pGrabController, IPhysicsObject *pObject, float *pSavedMassOut, float *pSavedRotationalDampingOut )
+{
+ CBaseEntity *pHeld = pGrabController->m_attachedEntity;
+ if( pHeld )
+ {
+ if( pObject->GetGameData() == (void*)pHeld )
+ {
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( pList[i] == pObject )
+ {
+ if( pSavedMassOut )
+ *pSavedMassOut = pGrabController->m_savedMass[i];
+
+ if( pSavedRotationalDampingOut )
+ *pSavedRotationalDampingOut = pGrabController->m_savedRotDamping[i];
+
+ return;
+ }
+ }
+ }
+ }
+
+ if( pSavedMassOut )
+ *pSavedMassOut = 0.0f;
+
+ if( pSavedRotationalDampingOut )
+ *pSavedRotationalDampingOut = 0.0f;
+
+ return;
+}
+
+void UpdateGrabControllerTargetPosition( CBasePlayer *pPlayer, Vector *vPosition, QAngle *qAngles )
+{
+ CGrabController *pGrabController = GetGrabControllerForPlayer( pPlayer );
+
+ if ( !pGrabController )
+ return;
+
+ pGrabController->UpdateObject( pPlayer, 12 );
+ pGrabController->GetTargetPosition( vPosition, qAngles );
+}
+
+
+bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject )
+{
+ CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pPhysCannon);
+ if ( pCannon )
+ {
+ return pCannon->IsAccountableForObject(pObject);
+ }
+
+ return false;
+}
+
+float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject )
+{
+ float mass = 0.0f;
+ CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
+ if ( pController )
+ {
+ CGrabController &grab = pController->GetGrabController();
+ mass = grab.GetSavedMass( pHeldObject );
+ }
+ return mass;
+}
+
+
+void GrabController_SetPortalPenetratingEntity( CGrabController *pController, CBaseEntity *pPenetrated )
+{
+ pController->SetPortalPenetratingEntity( pPenetrated );
+}
diff --git a/game/server/portal/weapon_physcannon.h b/game/server/portal/weapon_physcannon.h
new file mode 100644
index 0000000..d544777
--- /dev/null
+++ b/game/server/portal/weapon_physcannon.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef WEAPON_PHYSCANNON_H
+#define WEAPON_PHYSCANNON_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CGrabController;
+
+//-----------------------------------------------------------------------------
+// Do we have the super-phys gun?
+//-----------------------------------------------------------------------------
+bool PlayerHasMegaPhysCannon();
+
+// force the physcannon to drop an object (if carried)
+void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis );
+void PhysCannonBeginUpgrade( CBaseAnimating *pAnim );
+
+bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupController, CBaseEntity *pHeldEntity );
+void ShutdownPickupController( CBaseEntity *pPickupControllerEntity );
+float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject );
+float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject );
+
+CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon );
+CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer );
+CBasePlayer *GetPlayerHoldingEntity( CBaseEntity *pEntity );
+
+CGrabController *GetGrabControllerForPlayer( CBasePlayer *pPlayer );
+CGrabController *GetGrabControllerForPhysCannon( CBaseCombatWeapon *pActiveWeapon );
+void GetSavedParamsForCarriedPhysObject( CGrabController *pGrabController, IPhysicsObject *pObject, float *pSavedMassOut, float *pSavedRotationalDampingOut );
+void UpdateGrabControllerTargetPosition( CBasePlayer *pPlayer, Vector *vPosition, QAngle *qAngles );
+bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject );
+
+void GrabController_SetPortalPenetratingEntity( CGrabController *pController, CBaseEntity *pPenetrated );
+
+#endif // WEAPON_PHYSCANNON_H
diff --git a/game/server/portal/weapon_portalgun.cpp b/game/server/portal/weapon_portalgun.cpp
new file mode 100644
index 0000000..1fe9e82
--- /dev/null
+++ b/game/server/portal/weapon_portalgun.cpp
@@ -0,0 +1,736 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "BasePropDoor.h"
+#include "portal_player.h"
+#include "te_effect_dispatch.h"
+#include "gameinterface.h"
+#include "prop_combine_ball.h"
+#include "portal_shareddefs.h"
+#include "triggers.h"
+#include "collisionutils.h"
+#include "cbaseanimatingprojectile.h"
+#include "weapon_physcannon.h"
+#include "prop_portal_shared.h"
+#include "portal_placement.h"
+#include "weapon_portalgun_shared.h"
+#include "physicsshadowclone.h"
+#include "particle_parse.h"
+
+
+#define BLAST_SPEED_NON_PLAYER 1000.0f
+#define BLAST_SPEED 3000.0f
+
+
+IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPortalgun, DT_WeaponPortalgun )
+
+BEGIN_NETWORK_TABLE( CWeaponPortalgun, DT_WeaponPortalgun )
+ SendPropBool( SENDINFO( m_bCanFirePortal1 ) ),
+ SendPropBool( SENDINFO( m_bCanFirePortal2 ) ),
+ SendPropInt( SENDINFO( m_iLastFiredPortal ) ),
+ SendPropBool( SENDINFO( m_bOpenProngs ) ),
+ SendPropFloat( SENDINFO( m_fCanPlacePortal1OnThisSurface ) ),
+ SendPropFloat( SENDINFO( m_fCanPlacePortal2OnThisSurface ) ),
+ SendPropFloat( SENDINFO( m_fEffectsMaxSize1 ) ), // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ SendPropFloat( SENDINFO( m_fEffectsMaxSize2 ) ),
+ SendPropInt( SENDINFO( m_EffectState ) ),
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CWeaponPortalgun )
+
+ DEFINE_KEYFIELD( m_bCanFirePortal1, FIELD_BOOLEAN, "CanFirePortal1" ),
+ DEFINE_KEYFIELD( m_bCanFirePortal2, FIELD_BOOLEAN, "CanFirePortal2" ),
+ DEFINE_FIELD( m_iLastFiredPortal, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bOpenProngs, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fCanPlacePortal1OnThisSurface, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fCanPlacePortal2OnThisSurface, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fEffectsMaxSize1, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fEffectsMaxSize2, FIELD_FLOAT ),
+ DEFINE_FIELD( m_EffectState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iPortalLinkageGroupID, FIELD_CHARACTER ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "ChargePortal1", InputChargePortal1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ChargePortal2", InputChargePortal2 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FirePortal1", FirePortal1 ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FirePortal2", FirePortal2 ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "FirePortalDirection1", FirePortalDirection1 ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "FirePortalDirection2", FirePortalDirection2 ),
+
+ DEFINE_SOUNDPATCH( m_pMiniGravHoldSound ),
+
+ DEFINE_OUTPUT ( m_OnFiredPortal1, "OnFiredPortal1" ),
+ DEFINE_OUTPUT ( m_OnFiredPortal2, "OnFiredPortal2" ),
+
+ DEFINE_FUNCTION( Think ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( weapon_portalgun, CWeaponPortalgun );
+PRECACHE_WEAPON_REGISTER(weapon_portalgun);
+
+
+extern ConVar sv_portal_placement_debug;
+extern ConVar sv_portal_placement_never_fail;
+
+
+void CWeaponPortalgun::Spawn( void )
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ SetThink( &CWeaponPortalgun::Think );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if( GameRules()->IsMultiplayer() )
+ {
+ CBaseEntity *pOwner = GetOwner();
+ if( pOwner && pOwner->IsPlayer() )
+ m_iPortalLinkageGroupID = pOwner->entindex();
+
+ Assert( (m_iPortalLinkageGroupID >= 0) && (m_iPortalLinkageGroupID < 256) );
+ }
+}
+
+void CWeaponPortalgun::Activate()
+{
+ BaseClass::Activate();
+
+ CreateSounds();
+
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( pPlayer )
+ {
+ CBaseEntity *pHeldObject = GetPlayerHeldEntity( pPlayer );
+ OpenProngs( ( pHeldObject ) ? ( false ) : ( true ) );
+ OpenProngs( ( pHeldObject ) ? ( true ) : ( false ) );
+
+ if( GameRules()->IsMultiplayer() )
+ m_iPortalLinkageGroupID = pPlayer->entindex();
+
+ Assert( (m_iPortalLinkageGroupID >= 0) && (m_iPortalLinkageGroupID < 256) );
+ }
+
+ // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ m_fEffectsMaxSize1 = 4.0f;
+ m_fEffectsMaxSize2 = 4.0f;
+}
+
+void CWeaponPortalgun::OnPickedUp( CBaseCombatCharacter *pNewOwner )
+{
+ if( GameRules()->IsMultiplayer() )
+ {
+ if( pNewOwner && pNewOwner->IsPlayer() )
+ m_iPortalLinkageGroupID = pNewOwner->entindex();
+
+ Assert( (m_iPortalLinkageGroupID >= 0) && (m_iPortalLinkageGroupID < 256) );
+ }
+
+ BaseClass::OnPickedUp( pNewOwner );
+}
+
+void CWeaponPortalgun::CreateSounds()
+{
+ if (!m_pMiniGravHoldSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+
+ m_pMiniGravHoldSound = controller.SoundCreate( filter, entindex(), "Weapon_Portalgun.HoldSound" );
+ controller.Play( m_pMiniGravHoldSound, 0, 100 );
+ }
+}
+
+void CWeaponPortalgun::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pMiniGravHoldSound );
+ m_pMiniGravHoldSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+void CWeaponPortalgun::DoEffectBlast( bool bPortal2, int iPlacedBy, const Vector &ptStart, const Vector &ptFinalPos, const QAngle &qStartAngles, float fDelay )
+{
+ CEffectData fxData;
+ fxData.m_vOrigin = ptStart;
+ fxData.m_vStart = ptFinalPos;
+ fxData.m_flScale = gpGlobals->curtime + fDelay;
+ fxData.m_vAngles = qStartAngles;
+ fxData.m_nColor = ( ( bPortal2 ) ? ( 2 ) : ( 1 ) );
+ fxData.m_nDamageType = iPlacedBy;
+ DispatchEffect( "PortalBlast", fxData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the turret is currently in
+//-----------------------------------------------------------------------------
+bool CWeaponPortalgun::PreThink( void )
+{
+ //Animate
+ StudioFrameAdvance();
+
+ //Do not interrupt current think function
+ return false;
+}
+
+void CWeaponPortalgun::Think( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink() )
+ return;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ CPortal_Player *pPlayer = ToPortalPlayer( GetOwner() );
+
+ if ( !pPlayer || pPlayer->GetActiveWeapon() != this )
+ {
+ m_fCanPlacePortal1OnThisSurface = 1.0f;
+ m_fCanPlacePortal2OnThisSurface = 1.0f;
+ return;
+ }
+
+ // Test portal placement
+ m_fCanPlacePortal1OnThisSurface = ( ( m_bCanFirePortal1 ) ? ( FirePortal( false, 0, 1 ) ) : ( 0.0f ) );
+ m_fCanPlacePortal2OnThisSurface = ( ( m_bCanFirePortal2 ) ? ( FirePortal( true, 0, 2 ) ) : ( 0.0f ) );
+
+ // Draw obtained portal color chips
+ int iSlot1State = ( ( m_bCanFirePortal1 ) ? ( 0 ) : ( 1 ) ); // FIXME: Portal gun might have only red but not blue;
+ int iSlot2State = ( ( m_bCanFirePortal2 ) ? ( 0 ) : ( 1 ) );
+
+ SetBodygroup( 1, iSlot1State );
+ SetBodygroup( 2, iSlot2State );
+
+ if ( pPlayer->GetViewModel() )
+ {
+ pPlayer->GetViewModel()->SetBodygroup( 1, iSlot1State );
+ pPlayer->GetViewModel()->SetBodygroup( 2, iSlot2State );
+ }
+
+ // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ if ( m_fEffectsMaxSize1 > 4.0f )
+ {
+ m_fEffectsMaxSize1 -= gpGlobals->frametime * 400.0f;
+ if ( m_fEffectsMaxSize1 < 4.0f )
+ m_fEffectsMaxSize1 = 4.0f;
+ }
+
+ if ( m_fEffectsMaxSize2 > 4.0f )
+ {
+ m_fEffectsMaxSize2 -= gpGlobals->frametime * 400.0f;
+ if ( m_fEffectsMaxSize2 < 4.0f )
+ m_fEffectsMaxSize2 = 4.0f;
+ }
+}
+
+void CWeaponPortalgun::OpenProngs( bool bOpenProngs )
+{
+ if ( m_bOpenProngs == bOpenProngs )
+ {
+ return;
+ }
+
+ m_bOpenProngs = bOpenProngs;
+
+ DoEffect( ( m_bOpenProngs ) ? ( EFFECT_HOLDING ) : ( EFFECT_READY ) );
+
+ SendWeaponAnim( ( m_bOpenProngs ) ? ( ACT_VM_PICKUP ) : ( ACT_VM_RELEASE ) );
+}
+
+void CWeaponPortalgun::InputChargePortal1( inputdata_t &inputdata )
+{
+ DispatchParticleEffect( "portal_1_charge", PATTACH_POINT_FOLLOW, this, "muzzle" );
+}
+
+void CWeaponPortalgun::InputChargePortal2( inputdata_t &inputdata )
+{
+ DispatchParticleEffect( "portal_2_charge", PATTACH_POINT_FOLLOW, this, "muzzle" );
+}
+
+void CWeaponPortalgun::FirePortal1( inputdata_t &inputdata )
+{
+ FirePortal( false );
+ m_iLastFiredPortal = 1;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if( pOwner && pOwner->IsPlayer() )
+ {
+ WeaponSound( SINGLE );
+ }
+ else
+ {
+ WeaponSound( SINGLE_NPC );
+ }
+}
+
+void CWeaponPortalgun::FirePortal2( inputdata_t &inputdata )
+{
+ FirePortal( true );
+ m_iLastFiredPortal = 2;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if( pOwner && pOwner->IsPlayer() )
+ {
+ WeaponSound( WPN_DOUBLE );
+ }
+ else
+ {
+ WeaponSound( DOUBLE_NPC );
+ }
+}
+
+void CWeaponPortalgun::FirePortalDirection1( inputdata_t &inputdata )
+{
+ Vector vDirection;
+ inputdata.value.Vector3D( vDirection );
+ FirePortal( false, &vDirection );
+ m_iLastFiredPortal = 1;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if( pOwner && pOwner->IsPlayer() )
+ {
+ WeaponSound( SINGLE );
+ }
+ else
+ {
+ WeaponSound( SINGLE_NPC );
+ }
+}
+
+void CWeaponPortalgun::FirePortalDirection2( inputdata_t &inputdata )
+{
+ Vector vDirection;
+ inputdata.value.Vector3D( vDirection );
+ FirePortal( true, &vDirection );
+ m_iLastFiredPortal = 2;
+
+ CBaseCombatCharacter *pOwner = GetOwner();
+
+ if( pOwner && pOwner->IsPlayer() )
+ {
+ WeaponSound( WPN_DOUBLE );
+ }
+ else
+ {
+ WeaponSound( DOUBLE_NPC );
+ }
+}
+
+float CWeaponPortalgun::TraceFirePortal( bool bPortal2, const Vector &vTraceStart, const Vector &vDirection, trace_t &tr, Vector &vFinalPosition, QAngle &qFinalAngles, int iPlacedBy, bool bTest /*= false*/ )
+{
+ CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
+ UTIL_Portal_Trace_Filter( &baseFilter );
+ CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
+
+ Ray_t rayEyeArea;
+ rayEyeArea.Init( vTraceStart + vDirection * 24.0f, vTraceStart + vDirection * -24.0f );
+
+ float fMustBeCloserThan = 2.0f;
+
+ CProp_Portal *pNearPortal = UTIL_Portal_FirstAlongRay( rayEyeArea, fMustBeCloserThan );
+
+ if ( !pNearPortal )
+ {
+ // Check for portal near and infront of you
+ rayEyeArea.Init( vTraceStart + vDirection * -24.0f, vTraceStart + vDirection * 48.0f );
+
+ fMustBeCloserThan = 2.0f;
+
+ pNearPortal = UTIL_Portal_FirstAlongRay( rayEyeArea, fMustBeCloserThan );
+ }
+
+ if ( pNearPortal && pNearPortal->IsActivedAndLinked() )
+ {
+ iPlacedBy = PORTAL_PLACED_BY_PEDESTAL;
+
+ Vector vPortalForward;
+ pNearPortal->GetVectors( &vPortalForward, 0, 0 );
+
+ if ( vDirection.Dot( vPortalForward ) < 0.01f )
+ {
+ // If shooting out of the world, fizzle
+ if ( !bTest )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2, true );
+
+ pPortal->m_iDelayedFailure = ( ( pNearPortal->m_bIsPortal2 ) ? ( PORTAL_FIZZLE_NEAR_RED ) : ( PORTAL_FIZZLE_NEAR_BLUE ) );
+ VectorAngles( vPortalForward, pPortal->m_qDelayedAngles );
+ pPortal->m_vDelayedPosition = pNearPortal->GetAbsOrigin();
+
+ vFinalPosition = pPortal->m_vDelayedPosition;
+ qFinalAngles = pPortal->m_qDelayedAngles;
+
+ UTIL_TraceLine( vTraceStart - vDirection * 16.0f, vTraceStart + (vDirection * m_fMaxRange1), MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ return PORTAL_ANALOG_SUCCESS_NEAR;
+ }
+
+ UTIL_TraceLine( vTraceStart - vDirection * 16.0f, vTraceStart + (vDirection * m_fMaxRange1), MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
+ }
+ }
+
+ // Trace to see where the portal hit
+ UTIL_TraceLine( vTraceStart, vTraceStart + (vDirection * m_fMaxRange1), MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ if ( !tr.DidHit() || tr.startsolid )
+ {
+ // If it didn't hit anything, fizzle
+ if ( !bTest )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2, true );
+
+ pPortal->m_iDelayedFailure = PORTAL_FIZZLE_NONE;
+ VectorAngles( -vDirection, pPortal->m_qDelayedAngles );
+ pPortal->m_vDelayedPosition = tr.endpos;
+
+ vFinalPosition = pPortal->m_vDelayedPosition;
+ qFinalAngles = pPortal->m_qDelayedAngles;
+ }
+
+ return PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE;
+ }
+
+ // Trace to the surface to see if there's a rotating door in the way
+ CBaseEntity *list[1024];
+
+ Ray_t ray;
+ ray.Init( vTraceStart, tr.endpos );
+
+ int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, 0 );
+
+ // Loop through all entities along the ray between the gun and the surface
+ for ( int i = 0; i < nCount; i++ )
+ {
+ // If the entity is a rotating door
+ if( FClassnameIs( list[i], "prop_door_rotating" ) )
+ {
+ // Check more precise door collision
+ CBasePropDoor *pRotatingDoor = static_cast<CBasePropDoor *>( list[i] );
+
+ Ray_t rayDoor;
+ rayDoor.Init( vTraceStart, vTraceStart + (vDirection * m_fMaxRange1) );
+
+ trace_t trDoor;
+ pRotatingDoor->TestCollision( rayDoor, 0, trDoor );
+
+ if ( trDoor.DidHit() )
+ {
+ // There's a door in the way
+ tr = trDoor;
+
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ Vector vMin;
+ Vector vMax;
+ Vector vZero = Vector( 0.0f, 0.0f, 0.0f );
+ list[ i ]->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
+ NDebugOverlay::Box( vZero, vMin, vMax, 0, 255, 0, 128, 0.5f );
+ }
+
+ if ( !bTest )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2, true );
+
+ pPortal->m_iDelayedFailure = PORTAL_FIZZLE_CANT_FIT;
+ VectorAngles( tr.plane.normal, pPortal->m_qDelayedAngles );
+ pPortal->m_vDelayedPosition = trDoor.endpos;
+
+ vFinalPosition = pPortal->m_vDelayedPosition;
+ qFinalAngles = pPortal->m_qDelayedAngles;
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CANT_FIT;
+ }
+ }
+ else if ( FClassnameIs( list[i], "trigger_portal_cleanser" ) )
+ {
+ CBaseTrigger *pTrigger = static_cast<CBaseTrigger*>( list[i] );
+
+ if ( pTrigger && !pTrigger->m_bDisabled )
+ {
+ Vector vMin;
+ Vector vMax;
+ pTrigger->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
+
+ IntersectRayWithBox( ray.m_Start, ray.m_Delta, vMin, vMax, 0.0f, &tr );
+
+ tr.plane.normal = -vDirection;
+
+ if ( !bTest )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2, true );
+
+ pPortal->m_iDelayedFailure = PORTAL_FIZZLE_CLEANSER;
+ VectorAngles( tr.plane.normal, pPortal->m_qDelayedAngles );
+ pPortal->m_vDelayedPosition = tr.endpos;
+
+ vFinalPosition = pPortal->m_vDelayedPosition;
+ qFinalAngles = pPortal->m_qDelayedAngles;
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CLEANSER;
+ }
+ }
+ }
+
+ Vector vUp( 0.0f, 0.0f, 1.0f );
+ if( ( tr.plane.normal.x > -0.001f && tr.plane.normal.x < 0.001f ) && ( tr.plane.normal.y > -0.001f && tr.plane.normal.y < 0.001f ) )
+ {
+ //plane is a level floor/ceiling
+ vUp = vDirection;
+ }
+
+ // Check that the placement succeed
+ VectorAngles( tr.plane.normal, vUp, qFinalAngles );
+
+ vFinalPosition = tr.endpos;
+ return VerifyPortalPlacement( CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2 ), vFinalPosition, qFinalAngles, iPlacedBy, bTest );
+}
+
+float CWeaponPortalgun::FirePortal( bool bPortal2, Vector *pVector /*= 0*/, bool bTest /*= false*/ )
+{
+ bool bPlayer = false;
+ Vector vEye;
+ Vector vDirection;
+ Vector vTracerOrigin;
+
+ CBaseEntity *pOwner = GetOwner();
+
+ if ( pOwner && pOwner->IsPlayer() )
+ {
+ bPlayer = true;
+ }
+
+ if( bPlayer )
+ {
+ CPortal_Player *pPlayer = (CPortal_Player *)pOwner;
+
+ if ( !bTest && pPlayer )
+ {
+ pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY, 0 );
+ }
+
+ Vector forward, right, up;
+ AngleVectors( pPlayer->EyeAngles(), &forward, &right, &up );
+ pPlayer->EyeVectors( &vDirection, NULL, NULL );
+ vEye = pPlayer->EyePosition();
+
+ // Check if the players eye is behind the portal they're in and translate it
+ VMatrix matThisToLinked;
+ CProp_Portal *pPlayerPortal = pPlayer->m_hPortalEnvironment;
+
+ if ( pPlayerPortal )
+ {
+ Vector ptPortalCenter;
+ Vector vPortalForward;
+
+ ptPortalCenter = pPlayerPortal->GetAbsOrigin();
+ pPlayerPortal->GetVectors( &vPortalForward, NULL, NULL );
+
+ Vector vEyeToPortalCenter = ptPortalCenter - vEye;
+
+ float fPortalDist = vPortalForward.Dot( vEyeToPortalCenter );
+ if( fPortalDist > 0.0f )
+ {
+ // Eye is behind the portal
+ matThisToLinked = pPlayerPortal->MatrixThisToLinked();
+ }
+ else
+ {
+ pPlayerPortal = NULL;
+ }
+ }
+
+ if ( pPlayerPortal )
+ {
+ UTIL_Portal_VectorTransform( matThisToLinked, forward, forward );
+ UTIL_Portal_VectorTransform( matThisToLinked, right, right );
+ UTIL_Portal_VectorTransform( matThisToLinked, up, up );
+ UTIL_Portal_VectorTransform( matThisToLinked, vDirection, vDirection );
+ UTIL_Portal_PointTransform( matThisToLinked, vEye, vEye );
+
+ if ( pVector )
+ {
+ UTIL_Portal_VectorTransform( matThisToLinked, *pVector, *pVector );
+ }
+ }
+
+ vTracerOrigin = vEye
+ + forward * 30.0f
+ + right * 4.0f
+ + up * (-5.0f);
+ }
+ else
+ {
+ // This portalgun is not held by the player-- Fire using the muzzle attachment
+ Vector vecShootOrigin;
+ QAngle angShootDir;
+ GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir );
+ vEye = vecShootOrigin;
+ vTracerOrigin = vecShootOrigin;
+ AngleVectors( angShootDir, &vDirection, NULL, NULL );
+ }
+
+ if ( !bTest )
+ {
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ }
+
+ if ( pVector )
+ {
+ vDirection = *pVector;
+ }
+
+ Vector vTraceStart = vEye + (vDirection * m_fMinRange1);
+
+ Vector vFinalPosition;
+ QAngle qFinalAngles;
+
+ PortalPlacedByType ePlacedBy = ( bPlayer ) ? ( PORTAL_PLACED_BY_PLAYER ) : ( PORTAL_PLACED_BY_PEDESTAL );
+
+ trace_t tr;
+ float fPlacementSuccess = TraceFirePortal( bPortal2, vTraceStart, vDirection, tr, vFinalPosition, qFinalAngles, ePlacedBy, bTest );
+
+ if ( sv_portal_placement_never_fail.GetBool() )
+ {
+ fPlacementSuccess = 1.0f;
+ }
+
+ if ( !bTest )
+ {
+ CProp_Portal *pPortal = CProp_Portal::FindPortal( m_iPortalLinkageGroupID, bPortal2, true );
+
+ // If it was a failure, put the effect at exactly where the player shot instead of where the portal bumped to
+ if ( fPlacementSuccess < 0.5f )
+ vFinalPosition = tr.endpos;
+
+ pPortal->PlacePortal( vFinalPosition, qFinalAngles, fPlacementSuccess, true );
+
+ float fDelay = vTracerOrigin.DistTo( tr.endpos ) / ( ( bPlayer ) ? ( BLAST_SPEED ) : ( BLAST_SPEED_NON_PLAYER ) );
+
+ QAngle qFireAngles;
+ VectorAngles( vDirection, qFireAngles );
+ DoEffectBlast( pPortal->m_bIsPortal2, ePlacedBy, vTracerOrigin, vFinalPosition, qFireAngles, fDelay );
+
+ pPortal->SetContextThink( &CProp_Portal::DelayedPlacementThink, gpGlobals->curtime + fDelay, s_pDelayedPlacementContext );
+ pPortal->m_vDelayedPosition = vFinalPosition;
+ pPortal->m_hPlacedBy = this;
+ }
+
+ return fPlacementSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponPortalgun::StartEffects( void )
+{
+}
+
+void CWeaponPortalgun::DestroyEffects( void )
+{
+ // Stop everything
+ StopEffects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Ready effects
+//-----------------------------------------------------------------------------
+void CWeaponPortalgun::DoEffectReady( void )
+{
+ if ( m_pMiniGravHoldSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pMiniGravHoldSound, 0.0, 0.1 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Holding effects
+//-----------------------------------------------------------------------------
+void CWeaponPortalgun::DoEffectHolding( void )
+{
+ if ( m_pMiniGravHoldSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pMiniGravHoldSound, 1.0, 0.1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shutdown for the weapon when it's holstered
+//-----------------------------------------------------------------------------
+void CWeaponPortalgun::DoEffectNone( void )
+{
+ if ( m_pMiniGravHoldSound )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundChangeVolume( m_pMiniGravHoldSound, 0.0, 0.1 );
+ }
+}
+
+void CC_UpgradePortalGun( void )
+{
+ CPortal_Player *pPlayer = ToPortalPlayer( UTIL_GetCommandClient() );
+
+ CWeaponPortalgun *pPortalGun = static_cast<CWeaponPortalgun*>( pPlayer->Weapon_OwnsThisType( "weapon_portalgun" ) );
+ if ( pPortalGun )
+ {
+ pPortalGun->SetCanFirePortal1();
+ pPortalGun->SetCanFirePortal2();
+ }
+}
+
+static ConCommand upgrade_portal("upgrade_portalgun", CC_UpgradePortalGun, "Equips the player with a single portal portalgun. Use twice for a dual portal portalgun.\n\tArguments: none ", FCVAR_CHEAT);
+
+
+
+
+static void change_portalgun_linkage_id_f( const CCommand &args )
+{
+ if( sv_cheats->GetBool() == false ) //heavy handed version since setting the concommand with FCVAR_CHEATS isn't working like I thought
+ return;
+
+ if( args.ArgC() < 2 )
+ return;
+
+ unsigned char iNewID = (unsigned char)atoi( args[1] );
+
+ CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
+
+ int iWeaponCount = pPlayer->WeaponCount();
+ for( int i = 0; i != iWeaponCount; ++i )
+ {
+ CBaseCombatWeapon *pWeapon = pPlayer->GetWeapon(i);
+ if( pWeapon == NULL )
+ continue;
+
+ if( dynamic_cast<CWeaponPortalgun *>(pWeapon) != NULL )
+ {
+ CWeaponPortalgun *pPortalGun = (CWeaponPortalgun *)pWeapon;
+ pPortalGun->m_iPortalLinkageGroupID = iNewID;
+ break;
+ }
+ }
+}
+
+ConCommand change_portalgun_linkage_id( "change_portalgun_linkage_id", change_portalgun_linkage_id_f, "Changes the portal linkage ID for the portal gun held by the commanding player.", FCVAR_CHEAT );
diff --git a/game/server/portal/weapon_portalgun.h b/game/server/portal/weapon_portalgun.h
new file mode 100644
index 0000000..b887e92
--- /dev/null
+++ b/game/server/portal/weapon_portalgun.h
@@ -0,0 +1,141 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef WEAPON_PORTALGUN_H
+#define WEAPON_PORTALGUN_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "weapon_portalbasecombatweapon.h"
+
+#include "prop_portal.h"
+
+
+class CWeaponPortalgun : public CBasePortalCombatWeapon
+{
+ DECLARE_DATADESC();
+
+public:
+ DECLARE_CLASS( CWeaponPortalgun, CBasePortalCombatWeapon );
+
+ DECLARE_NETWORKCLASS();
+ DECLARE_PREDICTABLE();
+
+private:
+ CNetworkVar( bool, m_bCanFirePortal1 ); // Is able to use primary fire
+ CNetworkVar( bool, m_bCanFirePortal2 ); // Is able to use secondary fire
+ CNetworkVar( int, m_iLastFiredPortal ); // Which portal was placed last
+ CNetworkVar( bool, m_bOpenProngs ); // Which portal was placed last
+ CNetworkVar( float, m_fCanPlacePortal1OnThisSurface ); // Tells the gun if it can place on the surface it's pointing at
+ CNetworkVar( float, m_fCanPlacePortal2OnThisSurface ); // Tells the gun if it can place on the surface it's pointing at
+
+public:
+ unsigned char m_iPortalLinkageGroupID; //which portal linkage group this gun is tied to, usually set by mapper, or inherited from owning player's index
+
+ // HACK HACK! Used to make the gun visually change when going through a cleanser!
+ CNetworkVar( float, m_fEffectsMaxSize1 );
+ CNetworkVar( float, m_fEffectsMaxSize2 );
+
+public:
+ virtual const Vector& GetBulletSpread( void )
+ {
+ static Vector cone = VECTOR_CONE_10DEGREES;
+ return cone;
+ }
+
+ virtual void Precache ( void );
+
+ virtual void CreateSounds( void );
+ virtual void StopLoopingSounds( void );
+
+ virtual void OnRestore( void );
+ virtual void UpdateOnRemove( void );
+ void Spawn( void );
+ virtual void Activate();
+ void DoEffectBlast( bool bPortal2, int iPlacedBy, const Vector &ptStart, const Vector &ptFinalPos, const QAngle &qStartAngles, float fDelay );
+ virtual void OnPickedUp( CBaseCombatCharacter *pNewOwner );
+
+ virtual bool ShouldDrawCrosshair( void );
+ float GetPortal1Placablity( void ) { return m_fCanPlacePortal1OnThisSurface; }
+ float GetPortal2Placablity( void ) { return m_fCanPlacePortal2OnThisSurface; }
+ void SetLastFiredPortal( int iLastFiredPortal ) { m_iLastFiredPortal = iLastFiredPortal; }
+ int GetLastFiredPortal( void ) { return m_iLastFiredPortal; }
+
+ bool Reload( void );
+ void FillClip( void );
+ void CheckHolsterReload( void );
+ void ItemHolsterFrame( void );
+ bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+ bool Deploy( void );
+
+ void SetCanFirePortal1( bool bCanFire = true );
+ void SetCanFirePortal2( bool bCanFire = true );
+ float CanFirePortal1( void ) { return m_bCanFirePortal1; }
+ float CanFirePortal2( void ) { return m_bCanFirePortal2; }
+
+ void PrimaryAttack( void );
+ void SecondaryAttack( void );
+
+ void DelayAttack( float fDelay );
+
+ virtual bool PreThink( void );
+ virtual void Think( void );
+
+ void OpenProngs( bool bOpenProngs );
+
+ void InputChargePortal1( inputdata_t &inputdata );
+ void InputChargePortal2( inputdata_t &inputdata );
+ void FirePortal1( inputdata_t &inputdata );
+ void FirePortal2( inputdata_t &inputdata );
+ void FirePortalDirection1( inputdata_t &inputdata );
+ void FirePortalDirection2( inputdata_t &inputdata );
+
+ float TraceFirePortal( bool bPortal2, const Vector &vTraceStart, const Vector &vDirection, trace_t &tr, Vector &vFinalPosition, QAngle &qFinalAngles, int iPlacedBy, bool bTest = false );
+ float FirePortal( bool bPortal2, Vector *pVector = 0, bool bTest = false );
+
+ CSoundPatch *m_pMiniGravHoldSound;
+
+ // Outputs for portalgun
+ COutputEvent m_OnFiredPortal1; // Fires when the gun's first (blue) portal is fired
+ COutputEvent m_OnFiredPortal2; // Fires when the gun's second (red) portal is fired
+
+ void DryFire( void );
+ virtual float GetFireRate( void ) { return 0.7; };
+ void WeaponIdle( void );
+
+ PortalWeaponID GetWeaponID( void ) const { return WEAPON_PORTALGUN; }
+
+protected:
+
+ void StartEffects( void ); // Initialize all sprites and beams
+ void StopEffects( bool stopSound = true ); // Hide all effects temporarily
+ void DestroyEffects( void ); // Destroy all sprites and beams
+
+ // Portalgun effects
+ void DoEffect( int effectType, Vector *pos = NULL );
+
+ void DoEffectClosed( void );
+ void DoEffectReady( void );
+ void DoEffectHolding( void );
+ void DoEffectNone( void );
+
+ CNetworkVar( int, m_EffectState ); // Current state of the effects on the gun
+
+public:
+
+ DECLARE_ACTTABLE();
+
+ CWeaponPortalgun(void);
+
+private:
+ CWeaponPortalgun( const CWeaponPortalgun & );
+
+};
+
+
+#endif // WEAPON_PORTALGUN_H