//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// // // Purpose: First-class cube entity so we can query by type and generally make inferences // that are harder to do without an entity of that type. // //=====================================================================================// #include "cbase.h" #include "props.h" #include "ai_utils.h" #include "physics_saverestore.h" #include "phys_controller.h" #include "portal_base2d.h" #include "portal/weapon_physcannon.h" #include "datacache/imdlcache.h" #include "prop_weightedcube.h" #include "portal_player.h" #include "portal_player_shared.h" #include "world.h" #include "vcollide_parse.h" #include "portal_gamestats.h" #include "saverestore_utlvector.h" #include "trigger_portal_cleanser.h" #include "portal_mp_gamerules.h" #include "cvisibilitymonitor.h" ConVar reflector_cube_disabled_think_rate( "reflector_cube_disabled_think_rate", "0.1f", FCVAR_DEVELOPMENTONLY, "The rate at which the cube should think when it is disabled." ); ConVar reflector_cube_disabled_nudge_time( "reflector_cube_disabled_nudge_time", "0.5f", FCVAR_DEVELOPMENTONLY, "The amount of time the cube needs to be touched before it gets enabled again." ); ConVar reflector_cube_disabled_use_touch_check( "reflector_cube_disabled_use_touch_check", "0", FCVAR_DEVELOPMENTONLY, "Use touch checks to determine when to enable the cube." ); ConVar sv_portal2_pickup_hint_range( "sv_portal2_pickup_hint_range", "350.0", FCVAR_NONE ); // FIXME: Bring this back for DLC2 //extern ConVar sv_schrodinger_laser_world_aligned; //Standard cube skins enum StandardCubeSkinType_t { CUBE_STANDARD_CLEAN_SKIN = 0, CUBE_STANDARD_CLEAN_ACTIVATED_SKIN = 2, CUBE_STANDARD_RUSTED_SKIN = 3, CUBE_STANDARD_RUSTED_ACTIVATED_SKIN = 5, CUBE_STANDARD_BOUNCE_SKIN = 6, CUBE_STANDARD_BOUNCE_ACTIVATED_SKIN = 10, CUBE_STANDARD_SPEED_SKIN = 7, CUBE_STANDARD_SPEED_ACTIVATED_SKIN = 11 }; //Companion cube skins enum CompanionCubeSkinType_t { CUBE_COMPANION_CLEAN_SKIN = 1, CUBE_COMPANION_CLEAN_ACTIVATED_SKIN = 4, CUBE_COMPANION_BOUNCE_SKIN = 8, CUBE_COMPANION_BOUNCE_ACTIVATED_SKIN = 8, CUBE_COMPANION_SPEED_SKIN = 9, CUBE_COMPANION_SPEED_ACTIVATED_SKIN = 9 }; //Reflective cubs skins enum ReflectiveCubeSkinType_t { CUBE_REFLECTIVE_CLEAN_SKIN = 0, CUBE_REFLECTIVE_RUSTED_SKIN = 1, CUBE_REFLECTIVE_BOUNCE_SKIN = 2, CUBE_REFLECTIVE_SPEED_SKIN = 3 }; //Sphere skins enum WeightedSpherSkinType_t { CUBE_SPHERE_CLEAN_SKIN = 0, CUBE_SPHERE_CLEAN_ACTIVATED_SKIN = 1, CUBE_SPHERE_BOUNCE_SKIN = 2, CUBE_SPHERE_BOUNCE_ACTIVATED_SKIN = 2, CUBE_SPHERE_SPEED_SKIN = 3, CUBE_SPHERE_SPEED_ACTIVATED_SKIN = 3 }; //Antique cube skins enum AntiqueCubeSkinType_t { CUBE_ANTIQUE_CLEAN_SKIN = 0, CUBE_ANTIQUE_BOUNCE_SKIN = 1, CUBE_ANTIQUE_SPEED_SKIN = 2 }; //Schrodinger cube skins enum SchrodingerCubeSkinType_t { CUBE_SCHRODINGER_CLEAN_SKIN = 4, CUBE_SCHRODINGER_BOUNCE_SKIN = 5, CUBE_SCHRODINGER_SPEED_SKIN = 6 }; const char SCHRODINGER_THINK_CONTEXT[] = "Schrodinger Think Context"; LINK_ENTITY_TO_CLASS( cube_rotationcontroller, CCubeRotationController ); //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CCubeRotationController ) DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_flSuspendTime, FIELD_TIME ), DEFINE_FIELD( m_worldGoalAxis, FIELD_VECTOR ), DEFINE_FIELD( m_localTestAxis, FIELD_VECTOR ), DEFINE_PHYSPTR( m_pController ), DEFINE_FIELD( m_angularLimit, FIELD_FLOAT ), DEFINE_FIELD( m_pParent, FIELD_CLASSPTR ), END_DATADESC() IMPLEMENT_AUTO_LIST( IPropWeightedCubeAutoList ); CCubeRotationController::~CCubeRotationController() { if ( m_pController ) { physenv->DestroyMotionController( m_pController ); m_pController = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCubeRotationController::Spawn( void ) { m_bEnabled = false; // align the object's local Z axis m_localTestAxis.Init( 1, 0, 0 ); // with the world's Z axis m_worldGoalAxis.Init( 0, 0, 1 ); // recover from up to 25 degrees / sec angular velocity m_angularLimit = 25; m_flSuspendTime = 0; SetMoveType( MOVETYPE_NONE ); } //----------------------------------------------------------------------------- // Purpose: Set the vector we'll try to match //----------------------------------------------------------------------------- void CCubeRotationController::SetAlignmentVector( const Vector &vecAlign ) { m_worldGoalAxis = vecAlign; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCubeRotationController::Activate( void ) { BaseClass::Activate(); if ( m_pParent == NULL ) { UTIL_Remove(this); return; } IPhysicsObject *pPhys = m_pParent->VPhysicsGetObject(); if ( pPhys == NULL ) { UTIL_Remove(this); return; } //Setup the motion controller if ( !m_pController ) { m_pController = physenv->CreateMotionController( (IMotionEvent *)this ); m_pController->AttachObject( pPhys, true ); } else { m_pController->SetEventHandler( this ); } } //----------------------------------------------------------------------------- // Purpose: Simulation will be suspended after this amount of time //----------------------------------------------------------------------------- void CCubeRotationController::SuspendAfter( float flSuspendTime ) { m_flSuspendTime = flSuspendTime; } //----------------------------------------------------------------------------- // Purpose: Actual simulation for tip controller //----------------------------------------------------------------------------- IMotionEvent::simresult_e CCubeRotationController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) { if ( Enabled() == false ) return SIM_NOTHING; // Don't simulate if we're being carried by the player // if ( m_pParent->IsBeingCarriedByPlayer() ) // return SIM_NOTHING; float flAngularLimit = m_angularLimit; // If we were just dropped by a friendly player, stabilise better /* if ( m_pParent->WasJustDroppedByPlayer() ) { // Increase the controller strength a little flAngularLimit += 20; } else */ { // If the turret has some vertical velocity, don't simulate /* Vector vecVelocity; AngularImpulse angImpulse; pObject->GetVelocity( &vecVelocity, &angImpulse ); if ( (vecVelocity.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerVelocity) || (angImpulse.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerAngularVelocity) ) return SIM_NOTHING; */ } linear.Init(); AngularImpulse angVel; pObject->GetVelocity( NULL, &angVel ); matrix3x4_t matrix; // get the object's local to world transform pObject->GetPositionMatrix( &matrix ); // Get the alignment axis in object space Vector currentLocalTargetAxis; VectorIRotate( m_worldGoalAxis, matrix, currentLocalTargetAxis ); float invDeltaTime = (1/deltaTime); angular = ComputeRotSpeedToAlignAxes( m_localTestAxis, currentLocalTargetAxis, angVel, 1.0, invDeltaTime * invDeltaTime, flAngularLimit * invDeltaTime ); return SIM_LOCAL_ACCELERATION; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCubeRotationController::Enable( bool state ) { m_bEnabled = state; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CCubeRotationController::Enabled( void ) { if ( m_flSuspendTime > gpGlobals->curtime ) return true; return m_bEnabled; } CEG_NOINLINE CCubeRotationController * CCubeRotationController::CreateRotationController( CBaseEntity *pOwner ) { if ( pOwner == NULL ) return NULL; CCubeRotationController *pController = (CCubeRotationController *) Create( "cube_rotationcontroller", pOwner->GetAbsOrigin(), pOwner->GetAbsAngles() ); if ( pController != NULL ) { pController->m_pParent = pOwner; } return pController; } CEG_PROTECT_STATIC_MEMBER_FUNCTION( CCubeRotationController_CreateRotationController, CCubeRotationController::CreateRotationController ); LINK_ENTITY_TO_CLASS( prop_weighted_cube, CPropWeightedCube ); BEGIN_DATADESC( CPropWeightedCube ) DEFINE_FIELD( m_vecCarryAngles, FIELD_VECTOR ), DEFINE_FIELD( m_pController, FIELD_EHANDLE ), DEFINE_FIELD( m_bMovementDisabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ), DEFINE_FIELD( m_bTouchedByPlayer, FIELD_BOOLEAN ), DEFINE_FIELD( m_nCurrentPaintedType, FIELD_INTEGER ), DEFINE_FIELD( m_bPickupDisabled, FIELD_BOOLEAN ), DEFINE_SOUNDPATCH( m_pSchrodingerSound ), DEFINE_THINKFUNC( SchrodingerThink ), DEFINE_THINKFUNC( DisabledThink ), DEFINE_THINKFUNC( TractorBeamThink ), DEFINE_THINKFUNC( ExitTractorBeamThink ), DEFINE_KEYFIELD( m_bRusted, FIELD_BOOLEAN, "SkinType" ), DEFINE_KEYFIELD( m_nCubeType, FIELD_INTEGER, "CubeType" ), DEFINE_KEYFIELD( m_bNewSkins, FIELD_BOOLEAN, "NewSkins" ), DEFINE_INPUTFUNC( FIELD_VOID, "Dissolve", InputDissolve ), DEFINE_INPUTFUNC( FIELD_VOID, "SilentDissolve", InputSilentDissolve ), DEFINE_INPUTFUNC( FIELD_VOID, "PreDissolveJoke", InputPreDissolveJoke ), DEFINE_INPUTFUNC( FIELD_VOID, "DisablePortalFunnel", InputDisablePortalFunnel ), DEFINE_INPUTFUNC( FIELD_VOID, "EnablePortalFunnel", InputEnablePortalFunnel ), DEFINE_INPUTFUNC( FIELD_VOID, "ExitDisabledState", InputExitDisabledState ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPaint", InputSetPaint ), DEFINE_INPUTFUNC( FIELD_VOID, "DisablePickup", InputDisablePickup ), DEFINE_INPUTFUNC( FIELD_VOID, "EnablePickup", InputEnablePickup ), DEFINE_OUTPUT( m_OnFizzled, "OnFizzled" ), DEFINE_OUTPUT( m_OnOrangePickUp, "OnOrangePickUp" ), DEFINE_OUTPUT( m_OnBluePickUp, "OnBluePickUp" ), DEFINE_OUTPUT( m_OnPainted, "OnPainted" ), END_DATADESC() //no new networked fields, just need entity specific virtual functions defined on the client IMPLEMENT_SERVERCLASS_ST( CPropWeightedCube, DT_PropWeightedCube ) END_SEND_TABLE() const char *CUBE_MODEL = "models/props/metal_box.mdl"; const char *CUBE_REFLECT_MODEL = "models/props/reflection_cube.mdl"; const char *CUBE_SPHERE_MODEL = "models/props_gameplay/mp_ball.mdl"; const char *CUBE_FX_FIZZLER_MODEL = "models/props/metal_box_fx_fizzler.mdl"; const char *CUBE_ANTIQUE_MODEL = "models/props_underground/underground_weighted_cube.mdl"; const char *CUBE_SCHRODINGER_MODEL = "models/props/reflection_cube.mdl"; CHandle< CPropWeightedCube > CPropWeightedCube::m_hSchrodingerDangling; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPropWeightedCube::CPropWeightedCube() : m_bMovementDisabled( false ), m_bRusted( false ), m_bActivated( false ), m_nCubeType( CUBE_STANDARD ), m_bTouchedByPlayer( false ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CEG_NOINLINE void CPropWeightedCube::Spawn( void ) { // Start out with nothing m_vecCarryAngles.Init(0,0,0); ConvertOldSkins(); Precache(); m_nCurrentPaintedType = NO_POWER; m_bPickupDisabled = false; SetCubeType(); CEG_PROTECT_VIRTUAL_FUNCTION( CPropWeightedCube_Spawn ); m_nBouncyMaterialIndex = physprops->GetSurfaceIndex( "WeightedCube_Bounce" ); SetInteraction( PROPINTER_PHYSGUN_ALLOW_OVERHEAD ); BaseClass::Spawn(); SetCollisionGroup( COLLISION_GROUP_WEIGHTED_CUBE ); if ( m_nCubeType == CUBE_SCHRODINGER ) { SetContextThink( &CPropWeightedCube::SchrodingerThink, gpGlobals->curtime + reflector_cube_disabled_think_rate.GetFloat(), SCHRODINGER_THINK_CONTEXT ); } #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) g_PortalGameStats.Event_CubeSpawn(); #endif VisibilityMonitor_AddEntity_NotVisibleThroughGlass( this, sv_portal2_pickup_hint_range.GetFloat() - 50.0f, NULL, NULL ); SetFadeDistance( -1.0f, 0.0f ); SetGlobalFadeScale( 0.0f ); } void CPropWeightedCube::Activate( void ) { SetPaintedMaterial( (PaintPowerType)( m_PrePaintedPower ) ); #if 0 if ( !m_pSchrodingerSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); CPASAttenuationFilter filter( this ); m_pSchrodingerSound = controller.SoundCreate( filter, entindex(), "music.laser_node_02.play" ); controller.Play( m_pSchrodingerSound, 0, RandomFloat( 99, 101 ) ); } #endif BaseClass::Activate(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::UpdateOnRemove( void ) { BaseClass::UpdateOnRemove(); if ( m_pController ) { UTIL_Remove( m_pController ); } CPropWeightedCube *pTwin = m_hSchrodingerTwin.Get(); if ( pTwin && !pTwin->IsMarkedForDeletion() ) { CTriggerPortalCleanser::FizzleBaseAnimating( NULL, pTwin ); } #if 0 CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundDestroy( m_pSchrodingerSound ); m_pSchrodingerSound = NULL; BaseClass::StopLoopingSounds(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::Precache( void ) { ConvertOldSkins(); switch ( m_nCubeType ) { default: case CUBE_STANDARD: case CUBE_COMPANION: PrecacheModel( CUBE_MODEL ); break; case CUBE_REFLECTIVE: PrecacheModel( CUBE_REFLECT_MODEL ); break; case CUBE_SPHERE: PrecacheModel( CUBE_SPHERE_MODEL ); break; case CUBE_ANTIQUE: PrecacheModel( CUBE_ANTIQUE_MODEL ); case CUBE_SCHRODINGER: PrecacheModel( CUBE_SCHRODINGER_MODEL ); PrecacheScriptSound( "music.laser_node_02.play" ); PrecacheScriptSound( "prop_laser_catcher.poweron" ); PrecacheScriptSound( "prop_laser_catcher.poweroff" ); break; } PrecacheModel( CUBE_FX_FIZZLER_MODEL ); PrecacheScriptSound( "WeightedCube.JumpPowerActivateShort" ); PrecacheScriptSound( "WeightedCube.JumpPowerActivateLong" ); BaseClass::Precache(); } int CPropWeightedCube::ObjectCaps( void ) { int flags = (BaseClass::ObjectCaps()|FCAP_IMPULSE_USE); if ( GetPaintedPower() == BOUNCE_POWER ) { flags |= FCAP_USE_IN_RADIUS; } return flags; } int CPropWeightedCube::UpdateTransmitState() { if ( HasLaser() ) { return SetTransmitState( FL_EDICT_ALWAYS ); } return BaseClass::UpdateTransmitState(); } void CPropWeightedCube::ConvertOldSkins( void ) { //HACK HACK: Make the cubes choose skins using the new method even though the maps have not been updated to use them. if( !m_bNewSkins ) { if( m_nSkin > 1 ) { m_nSkin--; } m_nCubeType = static_cast( m_nSkin.Get() ); m_bNewSkins = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::SetCubeType( void ) { // FIXME: Remove for DLC2 if ( m_nCubeType == CUBE_SCHRODINGER ) { m_nCubeType = CUBE_REFLECTIVE; } switch( m_nCubeType ) { //Standard cube case CUBE_STANDARD: case CUBE_COMPANION: { SetModelName( MAKE_STRING( CUBE_MODEL ) ); break; } //Reflective cube case CUBE_REFLECTIVE: { SetModelName( MAKE_STRING( CUBE_REFLECT_MODEL ) ); m_pController = CCubeRotationController::CreateRotationController( this ); AddSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ); break; } //Sphere case CUBE_SPHERE: { SetModelName( MAKE_STRING( CUBE_SPHERE_MODEL ) ); break; } //Antique cube case CUBE_ANTIQUE: { SetModelName( MAKE_STRING( CUBE_ANTIQUE_MODEL ) ); break; } //Schrodinger cube case CUBE_SCHRODINGER: { SetModelName( MAKE_STRING( CUBE_SCHRODINGER_MODEL ) ); m_pController = CCubeRotationController::CreateRotationController( this ); AddSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ); if ( m_hSchrodingerDangling.Get() == NULL ) { m_hSchrodingerDangling = this; } else { m_hSchrodingerDangling->m_hSchrodingerTwin = this; m_hSchrodingerTwin = m_hSchrodingerDangling; m_hSchrodingerDangling = NULL; } break; } } SetCubeSkin(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::SetActivated( bool bActivate ) { m_bActivated = bActivate; SetCubeSkin(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::SetCubeSkin( void ) { switch( m_nCubeType ) { //Standard cube case CUBE_STANDARD: { //Rusted cubes don't show paint if( m_bRusted ) { if( m_bActivated ) { SetSkin( CUBE_STANDARD_RUSTED_ACTIVATED_SKIN ); } else { SetSkin( CUBE_STANDARD_RUSTED_SKIN ); } } else { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { if( m_bActivated ) { RANDOM_CEG_TEST_SECRET_PERIOD( 98, 106 ); SetSkin( CUBE_STANDARD_BOUNCE_ACTIVATED_SKIN ); } else { SetSkin( CUBE_STANDARD_BOUNCE_SKIN ); } } break; //Speed painted case SPEED_POWER: { if( m_bActivated ) { SetSkin( CUBE_STANDARD_SPEED_ACTIVATED_SKIN ); } else { SetSkin( CUBE_STANDARD_SPEED_SKIN ); } } break; //Not painted default: { if( m_bActivated ) { SetSkin( CUBE_STANDARD_CLEAN_ACTIVATED_SKIN ); } else { SetSkin( CUBE_STANDARD_CLEAN_SKIN ); } } break; } } } break; //Companion cube case CUBE_COMPANION: { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { if( m_bActivated ) { SetSkin( CUBE_COMPANION_BOUNCE_ACTIVATED_SKIN ); } else { SetSkin( CUBE_COMPANION_BOUNCE_SKIN ); } } break; //Speed painted case SPEED_POWER: { if( m_bActivated ) { SetSkin( CUBE_COMPANION_SPEED_ACTIVATED_SKIN ); } else { SetSkin( CUBE_COMPANION_SPEED_SKIN ); } } break; //Not painted default: { if( m_bActivated ) { SetSkin( CUBE_COMPANION_CLEAN_ACTIVATED_SKIN ); } else { SetSkin( CUBE_COMPANION_CLEAN_SKIN ); } } break; } } break; //Reflective cube case CUBE_REFLECTIVE: { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { if( m_bRusted ) { // FIXME SetSkin( CUBE_REFLECTIVE_BOUNCE_SKIN ); } else { SetSkin( CUBE_REFLECTIVE_BOUNCE_SKIN ); } } break; //Speed painted case SPEED_POWER: { if( m_bRusted ) { // FIXME SetSkin( CUBE_REFLECTIVE_SPEED_SKIN ); } else { SetSkin( CUBE_REFLECTIVE_SPEED_SKIN ); } } break; //Not painted default: { if( m_bRusted ) { SetSkin( CUBE_REFLECTIVE_RUSTED_SKIN ); } else { SetSkin( CUBE_REFLECTIVE_CLEAN_SKIN ); } } break; } } break; //Sphere case CUBE_SPHERE: { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { if( m_bActivated ) { SetSkin( CUBE_SPHERE_BOUNCE_ACTIVATED_SKIN ); } else { SetSkin( CUBE_SPHERE_BOUNCE_SKIN ); } } break; //Speed painted case SPEED_POWER: { if( m_bActivated ) { SetSkin( CUBE_SPHERE_SPEED_ACTIVATED_SKIN ); } else { SetSkin( CUBE_SPHERE_SPEED_SKIN ); } } break; //Not painted default: { if( m_bActivated ) { SetSkin( CUBE_SPHERE_CLEAN_ACTIVATED_SKIN ); } else { SetSkin( CUBE_SPHERE_CLEAN_SKIN ); } } break; } } break; //Antique cube case CUBE_ANTIQUE: { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { SetSkin( CUBE_ANTIQUE_BOUNCE_SKIN ); } break; //Speed painted case SPEED_POWER: { SetSkin( CUBE_ANTIQUE_SPEED_SKIN ); } break; //Not painted default: { SetSkin( CUBE_ANTIQUE_CLEAN_SKIN ); } break; } } break; //Antique cube case CUBE_SCHRODINGER: { switch( GetPaintedPower() ) { //Bounce painted case BOUNCE_POWER: { SetSkin( CUBE_SCHRODINGER_BOUNCE_SKIN ); } break; //Speed painted case SPEED_POWER: { SetSkin( CUBE_SCHRODINGER_SPEED_SKIN ); } break; //Not painted default: { SetSkin( CUBE_SCHRODINGER_CLEAN_SKIN ); } break; } } break; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::SetSkin( int skinNum ) { m_nSkin = skinNum; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::InputDissolve( inputdata_t &in ) { CTriggerPortalCleanser::FizzleBaseAnimating( NULL, this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::InputSilentDissolve( inputdata_t &in ) { OnFizzled(); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::InputPreDissolveJoke( inputdata_t &in ) { CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, "@glados" ); if ( pEntity ) { pEntity->RunScript( "CoopCubeFizzle()", "PreDissolveJoke" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::InputDisablePortalFunnel( inputdata_t &in ) { m_bAllowPortalFunnel = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::InputEnablePortalFunnel( inputdata_t &in ) { m_bAllowPortalFunnel = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- QAngle CPropWeightedCube::CalculatePreferredAngles( CBasePlayer *pPlayer ) { return QAngle(0,0,0); } void CPropWeightedCube::UpdatePreferredAngles( CBasePlayer *pPlayer ) { m_vecCarryAngles = CalculatePreferredAngles( pPlayer ); if( HasPreferredCarryAnglesForPlayer( pPlayer ) ) { m_qPreferredPlayerCarryAngles = m_vecCarryAngles; } else { if( m_qPreferredPlayerCarryAngles.Get().x < FLT_MAX ) { m_qPreferredPlayerCarryAngles.GetForModify().Init( FLT_MAX, FLT_MAX, FLT_MAX ); } } } extern void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- QAngle CPropWeightedCube::PreferredCarryAngles( void ) { static QAngle s_prefAngles; s_prefAngles = m_vecCarryAngles; CBasePlayer *pPlayer = GetPlayerHoldingEntity( this ); if ( pPlayer ) { Vector vecRight; pPlayer->GetVectors( NULL, &vecRight, NULL ); Quaternion qRotation; AxisAngleQuaternion( vecRight, pPlayer->EyeAngles().x, qRotation ); matrix3x4_t tmp; ComputePlayerMatrix( pPlayer, tmp ); QAngle qTemp = TransformAnglesToWorldSpace( s_prefAngles, tmp ); Quaternion qExisting; AngleQuaternion( qTemp, qExisting ); Quaternion qFinal; QuaternionMult( qRotation, qExisting, qFinal ); QuaternionAngles( qFinal, qTemp ); s_prefAngles = TransformAnglesToLocalSpace( qTemp, tmp ); } return s_prefAngles; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) { BaseClass::OnPhysGunPickup( pPhysGunUser, reason ); m_bMovementDisabled = false; m_bTouchedByPlayer = true; // Calculate our preferred angles on the first pickup if ( reason == PICKED_UP_BY_CANNON || reason == PICKED_UP_BY_PLAYER ) { UpdatePreferredAngles( pPhysGunUser ); if ( m_pController ) { m_pController->Enable( false ); } CPortal_Player *pPlayer = ToPortalPlayer( pPhysGunUser ); if ( pPlayer ) { // Force a cool-down on the +USE key after a successful grab pPlayer->SetUseKeyCooldownTime( 0.5f ); } } if ( pPhysGunUser ) { if ( pPhysGunUser->GetTeamNumber() == TEAM_RED ) { m_OnOrangePickUp.FireOutput( pPhysGunUser, this ); } else if ( pPhysGunUser->GetTeamNumber() == TEAM_BLUE ) { m_OnBluePickUp.FireOutput( pPhysGunUser, this ); } } } //----------------------------------------------------------------------------- // Purpose: Turn on our rotation controller when we're dropped nicely //----------------------------------------------------------------------------- ConVar sv_box_physgundrop_angle_threshold("sv_box_physgundrop_angle_threshold", "70.f"); void CPropWeightedCube::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason ) { BaseClass::OnPhysGunDrop( pPhysGunUser, reason ); // Only care about this if we're dropped, as opposed to launched or thrown if ( reason != DROPPED_BY_PLAYER && reason != DROPPED_BY_CANNON ) return; // Enable the controller for a short time if ( m_pController ) { m_pController->Activate(); Vector vecForward; AngleVectors( GetAbsAngles(), &vecForward ); m_pController->SetAlignmentVector( vecForward ); m_pController->SuspendAfter( gpGlobals->curtime + 0.5f ); } // When player drop the box and player's up is not world up, try to throw the box in the local down direction bool bThrowBoxLocalDown = false; CPortal_Player *pPortalPlayer = ToPortalPlayer( pPhysGunUser ); if ( pPortalPlayer && !AlmostEqual( DotProduct( pPortalPlayer->GetPortalPlayerLocalData().m_Up, Vector( 0, 0, 1 ) ), 1.f ) ) { // check if player looks too far off the ground, then just drop the box with gravity Vector vForward; pPortalPlayer->GetVectors( &vForward, NULL, NULL ); float flLookAngle = RAD2DEG( acosf( DotProduct( vForward, pPortalPlayer->GetPortalPlayerLocalData().m_StickNormal ) ) ); bThrowBoxLocalDown = flLookAngle >= sv_box_physgundrop_angle_threshold.GetFloat(); if( bThrowBoxLocalDown ) { float flDropSpeed = 400.f; Vector vecDownVelocity = -( flDropSpeed * pPortalPlayer->GetPortalPlayerLocalData().m_Up ); IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( pPhysics ) { pPhysics->SetVelocityInstantaneous( &vecDownVelocity, NULL ); } } } } //----------------------------------------------------------------------------- // Purpose: Only bother with preferred carry angles if we're a reflective cube //----------------------------------------------------------------------------- bool CPropWeightedCube::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer ) { return ( m_nCubeType == CUBE_REFLECTIVE ) || ( /*FIXME: Bring back for DLC2 !sv_schrodinger_laser_world_aligned.GetBool() && */ m_nCubeType == CUBE_SCHRODINGER && m_hLaser.Get() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ) { // On teleport, we record a pointer to the portal we are arriving at if ( eventType == NOTIFY_EVENT_TELEPORT ) { CPortal_Base2D *pEnteredPortal = dynamic_cast( pNotify ); if ( pEnteredPortal && m_pController ) { Vector vecWorldAign = pEnteredPortal->m_matrixThisToLinked.ApplyRotation( m_pController->GetAlignmentVector() ); vecWorldAign.NormalizeInPlace(); m_pController->SetAlignmentVector( vecWorldAign ); } } BaseClass::NotifySystemEvent( pNotify, eventType, params ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropWeightedCube::Paint( PaintPowerType paintType, const Vector &worldContactPt ) { BaseClass::Paint( paintType, worldContactPt ); SetPaintedMaterial( paintType ); SetCubeSkin(); CPropWeightedCube *pTwin = m_hSchrodingerTwin.Get(); if ( pTwin && pTwin->GetPaintedPower() != paintType ) { pTwin->Paint( paintType, worldContactPt ); } } void CPropWeightedCube::SetPaintedMaterial( PaintPowerType paintType ) { if ( m_nCurrentPaintedType != paintType && paintType != NO_POWER ) { m_OnPainted.FireOutput( this, this ); } m_nCurrentPaintedType = paintType; switch( paintType ) { case BOUNCE_POWER: { //Set the box to be bouncy IPhysicsObject* pPhysObject = VPhysicsGetObject(); if( pPhysObject ) { pPhysObject->SetMaterialIndex( m_nBouncyMaterialIndex ); } ExitDisabledState(); break; } case SPEED_POWER: { IPhysicsObject* pPhysObject = VPhysicsGetObject(); if( pPhysObject ) { pPhysObject->SetMaterialIndex( BaseClass::GetSpeedMaterialIndex() ); } break; } case PORTAL_POWER: case REFLECT_POWER: case NO_POWER: default: { // Store our material index IPhysicsObject* pPhysObject = VPhysicsGetObject(); if( pPhysObject ) { pPhysObject->SetMaterialIndex( m_nOriginalMaterialIndex ); } break; } } } CPropWeightedCube* CPropWeightedCube::GetSchrodingerTwin( void ) { return m_hSchrodingerTwin; } void CPropWeightedCube::UpdateSchrodingerSound( void ) { if ( !m_hSchrodingerTwin.Get() ) return; float fDist = m_hSchrodingerTwin->GetDistanceToEntity( this ); if ( m_pSchrodingerSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pSchrodingerSound, RemapValClamped( fDist, 350.0f, 0.0f, 0.0f, 1.0f ), 0.1 ); } } void CPropWeightedCube::SetLaser( CBaseEntity *pLaser ) { m_hLaser = pLaser; if ( pLaser ) { CBasePlayer *pPlayer = GetPlayerHoldingEntity( this ); if ( pPlayer ) { UpdatePreferredAngles( pPlayer ); } if ( GetCubeType() == CUBE_SCHRODINGER ) { // FIXME: Need a better sound for this //EmitSound( "prop_laser_catcher.poweron" ); } } else { if ( GetCubeType() == CUBE_SCHRODINGER ) { // FIXME: Need a better sound for this //EmitSound( "prop_laser_catcher.poweroff" ); } } // need to update transmitstate to prevent laser going through box when box goes outside PVS UpdateTransmitState(); } bool CPropWeightedCube::ShouldEnterDisabledState( void ) { IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); if( pPhysicsObject ) { if( !( pPhysicsObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) && pPhysicsObject->IsAsleep() ) { return true; } } return false; } void CPropWeightedCube::EnterDisabledState( void ) { if ( !m_bMovementDisabled ) { IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); if( pPhysicsObject ) { pPhysicsObject->EnableMotion( false ); } m_bMovementDisabled = true; SetThink( &CPropWeightedCube::DisabledThink ); SetNextThink( gpGlobals->curtime + reflector_cube_disabled_think_rate.GetFloat() ); } } void CPropWeightedCube::ExitDisabledState( void ) { if ( m_bMovementDisabled ) { m_bMovementDisabled = false; EnableMotion(); } } void CPropWeightedCube::InputExitDisabledState( inputdata_t &in ) { ExitDisabledState(); } void CPropWeightedCube::OnEnteredTractorBeam( void ) { SetThink( &CPropWeightedCube::TractorBeamThink ); SetNextThink( gpGlobals->curtime ); } void CPropWeightedCube::OnExitedTractorBeam( void ) { SetThink( &CPropWeightedCube::ExitTractorBeamThink ); SetNextThink( gpGlobals->curtime ); } void CPropWeightedCube::TractorBeamThink( void ) { if ( m_bMovementDisabled ) return; // Stop colliding with player and freeze any rotational speed SetCollisionGroup( COLLISION_GROUP_PLAYER_HELD ); IPhysicsObject *pPhys = VPhysicsGetObject(); if ( pPhys ) { AngularImpulse vZeroRotation( vec3_origin ); pPhys->SetVelocity( NULL, &vZeroRotation ); } // Give players 2 seconds to get out of the way SetThink( &CPropWeightedCube::ExitTractorBeamThink ); SetNextThink( gpGlobals->curtime + 2.0f ); } void CPropWeightedCube::ExitTractorBeamThink( void ) { bool bIntersectingPlayer = false; for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer ) { if ( Intersects( pPlayer ) ) { bIntersectingPlayer = true; break; } } } if ( bIntersectingPlayer ) { SetThink( &CPropWeightedCube::ExitTractorBeamThink ); SetNextThink( gpGlobals->curtime + 0.2f ); } else { // Start colliding with player SetCollisionGroup( COLLISION_GROUP_WEIGHTED_CUBE ); } } void CPropWeightedCube::InputSetPaint( inputdata_t &in ) { Paint( static_cast< PaintPowerType >( in.value.Int() ), Vector( 0.0f, 0.0f, 0.0f ) ); } void CPropWeightedCube::StartTouch( CBaseEntity *pOther ) { if( m_bMovementDisabled ) { if( pOther->IsPlayer() ) { Vector vecPlayerForward; AngleVectors( pOther->EyeAngles(), &vecPlayerForward ); vecPlayerForward.NormalizeInPlace(); Vector vecCubeToPlayer = (GetAbsOrigin() - pOther->EyePosition()).Normalized(); float flPlayerLookDot = DotProduct( vecCubeToPlayer, vecPlayerForward ); float flCubeDirDot = DotProduct( Forward().Normalized(), vecPlayerForward ); //DevMsg( "Dot:%f, CubeDot:%f\n", flPlayerLookDot, flCubeDirDot ); //If the cube is in front of the player if( ( flPlayerLookDot > 0.8f && flCubeDirDot > 0.8f ) || ( flPlayerLookDot > 0.85f ) ) { ExitDisabledState(); } } } if( pOther->IsPlayer() ) { m_bTouchedByPlayer = true; } BaseClass::StartTouch( pOther ); } void CPropWeightedCube::SchrodingerThink( void ) { UpdateSchrodingerSound(); //Keep thinking SetContextThink( &CPropWeightedCube::SchrodingerThink, gpGlobals->curtime + reflector_cube_disabled_think_rate.GetFloat(), SCHRODINGER_THINK_CONTEXT ); } void CPropWeightedCube::DisabledThink( void ) { bool hasPaintPower = false; if( engine->HasPaintmap() ) { if( GetPaintedPower() != NO_POWER ) { hasPaintPower = true; } else { for( int i = 0; i < PAINT_POWER_TYPE_COUNT; ++i ) { if( !IsInactivePower( GetPaintPower(i) ) ) { hasPaintPower = true; break; } } } } //If the cube no longer has a laser attached to it or has a paint power if( !HasLaser() || hasPaintPower ) { ExitDisabledState(); return; } //Keep thinking SetNextThink( gpGlobals->curtime + reflector_cube_disabled_think_rate.GetFloat() ); } void CPropWeightedCube::InputDisablePickup( inputdata_t &in ) { m_bPickupDisabled = true; } void CPropWeightedCube::InputEnablePickup( inputdata_t &in ) { m_bPickupDisabled = false; } void CPropWeightedCube::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if( m_bPickupDisabled == false ) { BaseClass::Use( pActivator, pCaller, useType, value ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool UTIL_IsWeightedCube( CBaseEntity *pEntity ) { if ( pEntity == NULL ) return false; return ( FClassnameIs( pEntity, "prop_weighted_cube" ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool UTIL_IsReflectiveCube( CBaseEntity *pEntity ) { if ( UTIL_IsWeightedCube( pEntity ) == false ) return false; CPropWeightedCube *pCube = assert_cast( pEntity ); return ( pCube && pCube->GetCubeType() == CUBE_REFLECTIVE ); } #ifndef CLIENT_DLL bool UTIL_IsSchrodinger( CBaseEntity *pEntity ) { if ( !UTIL_IsWeightedCube( pEntity ) ) return false; CPropWeightedCube *pCube = assert_cast( pEntity ); if ( !pCube ) return false; return pCube->GetCubeType() == CUBE_SCHRODINGER; } CPropWeightedCube* UTIL_GetSchrodingerTwin( CBaseEntity *pEntity ) { if ( !UTIL_IsSchrodinger( pEntity ) ) return NULL; CPropWeightedCube *pCube = assert_cast( pEntity ); if ( !pCube ) return NULL; return pCube->GetSchrodingerTwin(); } #endif #define PORTAL_REFLECTOR_CUBE_MODEL_NAME "models/props/reflectocube.mdl" #define PORTAL_WEIGHT_BOX_MODEL_NAME "models/props/metal_box.mdl" #ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Creates a weighted cube of a specific type //----------------------------------------------------------------------------- void CPropWeightedCube::CreatePortalWeightedCube( WeightedCubeType_e objectType, bool bAtCursorPosition, const Vector &position ) { MDLCACHE_CRITICAL_SECTION(); bool allowPrecache = CBaseEntity::IsPrecacheAllowed(); CBaseEntity::SetAllowPrecache( true ); // Try to create entity CPropWeightedCube *entity = ( CPropWeightedCube* )CreateEntityByName("prop_weighted_cube"); if (entity) { //entity->PrecacheModel( PORTAL_REFLECTOR_CUBE_MODEL_NAME ); //entity->SetModel( PORTAL_REFLECTOR_CUBE_MODEL_NAME ); entity->SetName( MAKE_STRING("cube") ); entity->AddSpawnFlags( SF_PHYSPROP_ENABLE_PICKUP_OUTPUT ); entity->m_nCubeType = objectType; entity->m_bNewSkins = true; entity->Precache(); if ( !bAtCursorPosition ) { entity->SetAbsOrigin( position ); } DispatchSpawn(entity); if ( bAtCursorPosition ) { // Now attempt to drop into the world CBasePlayer* pPlayer = UTIL_GetCommandClient(); trace_t tr; Vector forward; pPlayer->EyeVectors( &forward ); UTIL_TraceLine(pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, pPlayer, COLLISION_GROUP_WEIGHTED_CUBE, &tr ); if ( tr.fraction != 1.0 ) { tr.endpos.z += 12; entity->Teleport( &tr.endpos, NULL, NULL ); UTIL_DropToFloor( entity, MASK_SOLID ); } } // This entity should send its object caps to the client entity->UpdateObjectCapsCache(); } CBaseEntity::SetAllowPrecache( allowPrecache ); } // Console command functions void CC_Create_PortalWeightedCube() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_STANDARD ); } void CC_Create_PortalCompanionCube() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_COMPANION ); } void CC_Create_PortalReflectorCube() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_REFLECTIVE ); } void CC_Create_PortalWeightedSphere() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_SPHERE ); } void CC_Create_PortalWeightedAntique() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_ANTIQUE ); } void CC_Create_PortalWeightedSchrodinger() { CPropWeightedCube::CreatePortalWeightedCube( CUBE_SCHRODINGER ); } // Console commands for creating cubes static ConCommand ent_create_portal_reflector_cube("ent_create_portal_reflector_cube", CC_Create_PortalReflectorCube, "Creates a laser reflector cube cube where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); static ConCommand ent_create_portal_companion_cube("ent_create_portal_companion_cube", CC_Create_PortalCompanionCube, "Creates a companion cube where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); static ConCommand ent_create_portal_weighted_cube("ent_create_portal_weighted_cube", CC_Create_PortalWeightedCube, "Creates a standard cube where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); static ConCommand ent_create_portal_weighted_sphere("ent_create_portal_weighted_sphere", CC_Create_PortalWeightedSphere, "Creates a weighted sphere where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); static ConCommand ent_create_portal_weighted_antique("ent_create_portal_weighted_antique", CC_Create_PortalWeightedAntique, "Creates an antique cube where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); // FIXME: Bring this back for DLC2 //static ConCommand ent_create_portal_weighted_schrodinger("ent_create_portal_weighted_schrodinger", CC_Create_PortalWeightedSchrodinger, "Creates an Schrodinger cube where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT); #endif // CLIENT_DLL