From 00cdd3176855472912fa09a7bafc3b101ed36cbd Mon Sep 17 00:00:00 2001 From: tupoy-ya Date: Mon, 25 Sep 2023 19:14:16 +0500 Subject: [PATCH] feat(trigger_catapult): Pasted trigger catapult from TF2. --- game/server/portal2/trigger_catapult.cpp | 412 +++++++++++++ game/server/portal2/trigger_catapult.h | 101 +++ game/server/server_portal.vpc | 3 + .../portal2/trigger_catapult_shared.cpp | 573 ++++++++++++++++++ 4 files changed, 1089 insertions(+) create mode 100644 game/server/portal2/trigger_catapult.cpp create mode 100644 game/server/portal2/trigger_catapult.h create mode 100644 game/shared/portal2/trigger_catapult_shared.cpp diff --git a/game/server/portal2/trigger_catapult.cpp b/game/server/portal2/trigger_catapult.cpp new file mode 100644 index 00000000..62a2969a --- /dev/null +++ b/game/server/portal2/trigger_catapult.cpp @@ -0,0 +1,412 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "trigger_catapult.h" + +#include "vcollide_parse.h" +#include "props.h" +#include "movevars_shared.h" +#include "portal_player.h" +#include "saverestore_utlvector.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +const char *CTriggerCatapult::s_szPlayerPassesTriggerFiltersThinkContext = "CTriggerCatapult::PlayerPassesTriggerFiltersThink"; + +extern ConVar catapult_physics_drag_boost; + + +BEGIN_DATADESC( CTriggerCatapult ) + + DEFINE_THINKFUNC( LaunchThink ), + DEFINE_THINKFUNC( PlayerPassesTriggerFiltersThink ), + + DEFINE_KEYFIELD( m_flPlayerVelocity, FIELD_FLOAT, "playerSpeed" ), + DEFINE_KEYFIELD( m_flPhysicsVelocity, FIELD_FLOAT, "physicsSpeed" ), + DEFINE_KEYFIELD( m_vecLaunchAngles, FIELD_VECTOR, "launchDirection" ), + DEFINE_KEYFIELD( m_strLaunchTarget, FIELD_STRING, "launchTarget" ), + DEFINE_KEYFIELD( m_bUseThresholdCheck, FIELD_BOOLEAN, "useThresholdCheck" ), + DEFINE_KEYFIELD( m_bUseExactVelocity, FIELD_BOOLEAN, "useExactVelocity" ), + DEFINE_KEYFIELD( m_flLowerThreshold, FIELD_FLOAT, "lowerThreshold" ), + DEFINE_KEYFIELD( m_flUpperThreshold, FIELD_FLOAT, "upperThreshold" ), + DEFINE_KEYFIELD( m_ExactVelocityChoice, FIELD_INTEGER, "exactVelocityChoiceType" ), + DEFINE_KEYFIELD( m_bOnlyVelocityCheck, FIELD_BOOLEAN, "onlyVelocityCheck" ), + DEFINE_KEYFIELD( m_bApplyAngularImpulse, FIELD_BOOLEAN, "applyAngularImpulse" ), + + DEFINE_KEYFIELD( m_flEntryAngleTolerance, FIELD_FLOAT, "EntryAngleTolerance" ), + DEFINE_KEYFIELD( m_flAirControlSupressionTime, FIELD_FLOAT, "AirCtrlSupressionTime" ), + DEFINE_KEYFIELD( m_bDirectionSuppressAirControl, FIELD_BOOLEAN, "DirectionSuppressAirControl" ), + + DEFINE_FIELD( m_hLaunchTarget, FIELD_EHANDLE ), + DEFINE_ARRAY( m_flRefireDelay, FIELD_TIME, MAX_PLAYERS + 1 ), + + DEFINE_UTLVECTOR( m_hAbortedLaunchees, FIELD_EHANDLE ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPlayerSpeed", InputSetPlayerSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPhysicsSpeed", InputSetPhysicsSpeed ), + DEFINE_INPUTFUNC( FIELD_STRING,"SetLaunchTarget", InputSetLaunchTarget ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetExactVelocityChoiceType", InputSetExactVelocityChoiceType ), + + DEFINE_OUTPUT( m_OnCatapulted, "OnCatapulted" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( trigger_catapult, CTriggerCatapult ); + +//IMPLEMENT_SERVERCLASS_ST( CTriggerCatapult, DT_TriggerCatapult ) +// SendPropArray3( SENDINFO_ARRAY3(m_flRefireDelay), SendPropFloat(SENDINFO_ARRAY(m_flRefireDelay)) ), +// SendPropFloat( SENDINFO( m_flPlayerVelocity ) ), +// SendPropFloat( SENDINFO( m_flPhysicsVelocity ) ), +// SendPropQAngles( SENDINFO( m_vecLaunchAngles ) ), +// //SendPropStringT( SENDINFO( m_strLaunchTarget ) ), +// SendPropInt( SENDINFO( m_ExactVelocityChoice ) ), +// SendPropBool( SENDINFO( m_bUseExactVelocity ) ), +// SendPropBool( SENDINFO( m_bUseThresholdCheck ) ), +// SendPropBool( SENDINFO( m_bOnlyVelocityCheck ) ), +// SendPropFloat( SENDINFO( m_flLowerThreshold ) ), +// SendPropFloat( SENDINFO( m_flUpperThreshold ) ), +// SendPropFloat( SENDINFO( m_flAirControlSupressionTime ) ), +// SendPropBool( SENDINFO( m_bApplyAngularImpulse ) ), +// SendPropFloat( SENDINFO( m_flEntryAngleTolerance ) ), +// SendPropEHandle( SENDINFO( m_hLaunchTarget ) ), +// SendPropBool( SENDINFO( m_bPlayersPassTriggerFilters ) ), +// SendPropBool( SENDINFO( m_bDirectionSuppressAirControl ) ), +//END_SEND_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTriggerCatapult::CTriggerCatapult( void ) +{ + //Defaulting to true; + m_bApplyAngularImpulse = true; + m_flAirControlSupressionTime = -1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::DrawDebugGeometryOverlays( void ) +{ + BaseClass::DrawDebugGeometryOverlays(); + CBaseEntity *pLaunchTarget = m_hLaunchTarget; + if ( pLaunchTarget ) + { + // Help us visualize the target + Vector vecSourcePos = GetAbsOrigin(); + Vector vecTargetPos = pLaunchTarget->GetAbsOrigin(); + + float flSpeed = m_flPlayerVelocity; + float flGravity = sv_gravity.GetFloat(); + + Vector vecVelocity = (vecTargetPos - vecSourcePos); + + // This is a hack to get around air resistance with weighted cubes -- this is not intended for all objects! + // float flDragCoefficient = (pVictim->IsPlayer()) ? 1.0f : ( 1.6f ); + float flDragCoefficient = 0.0f; + + // throw at a constant time + float time = vecVelocity.Length( ) / flSpeed; + vecVelocity = vecVelocity * (1.0 / time) * flDragCoefficient; + + // adjust upward toss to compensate for gravity loss + vecVelocity.z += flGravity * time * 0.5; + + Vector vecApex = vecSourcePos + (vecTargetPos - vecSourcePos) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + // Visualize it! + if( !m_bUseExactVelocity ) + { + NDebugOverlay::Box( vecSourcePos, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Box( vecTargetPos, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Box( vecApex, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Line( vecSourcePos, vecApex, 0, 255, 0, false, 0.05f ); + NDebugOverlay::Line( vecApex, vecTargetPos, 0, 255, 0, false, 0.05f ); + } + else + { + Vector lastPos = vecSourcePos; + vecVelocity = (vecTargetPos - vecSourcePos); + vecVelocity = CalculateLaunchVectorPreserve( vecVelocity, this, pLaunchTarget, true ); + for( int i = 0; i < 20; i++ ) + { + float flTime = 0.2f*(i+1); + + vecApex = vecSourcePos + vecVelocity*flTime; + vecApex.z -= 0.5 * flGravity * (flTime) * (flTime); + NDebugOverlay::Box( vecApex, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Line( vecApex, lastPos, 0, 255, 0, false, 0.05f ); + lastPos = vecApex; + } + } + + // Physics! + flSpeed = m_flPhysicsVelocity; + vecVelocity = (vecTargetPos - vecSourcePos); + + // This is a hack to get around air resistance with weighted cubes -- this is not intended for all objects! + flDragCoefficient = catapult_physics_drag_boost.GetFloat(); + + // throw at a constant time + time = vecVelocity.Length( ) / flSpeed; + vecVelocity = vecVelocity * (1.0 / time) * flDragCoefficient; + + // adjust upward toss to compensate for gravity loss + vecVelocity.z += flGravity * time * 0.5; + + vecApex = vecSourcePos + (vecTargetPos - vecSourcePos) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + // Visualize it! + if( !m_bUseExactVelocity ) + { + NDebugOverlay::Box( vecApex, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Line( vecSourcePos, vecApex, 255, 255, 0, false, 0.05f ); + NDebugOverlay::Line( vecApex, vecTargetPos, 255, 255, 0, false, 0.05f ); + } + else + { + Vector lastPos = vecSourcePos; + vecVelocity = (vecTargetPos - vecSourcePos); + vecVelocity = CalculateLaunchVectorPreserve( vecVelocity, this, pLaunchTarget ); + for( int i = 0; i < 20; i++ ) + { + float flTime = 0.2f*(i+1); + + vecApex = vecSourcePos + vecVelocity*flTime; + vecApex.z -= 0.5 * flGravity * (flTime) * (flTime); + NDebugOverlay::Box( vecApex, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, 8.0f, 0.05f ); + NDebugOverlay::Line( vecApex, lastPos, 255, 255, 0, false, 0.05f ); + lastPos = vecApex; + } + } + } + else + { + // Meh + } +} +//--------------------------------------------------------- +//--------------------------------------------------------- +int CTriggerCatapult::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf(tempstr,sizeof(tempstr), "Launch target: %s", m_strLaunchTarget.ToCStr() ); + EntityText(text_offset,tempstr,0); + text_offset++; + + if ( m_bUseThresholdCheck ) + { + Q_snprintf(tempstr,sizeof(tempstr), "Lower threshold velocity: %.2f", m_flLowerThreshold ); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr), "Upper threshold velocity: %.2f", m_flUpperThreshold ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + Q_snprintf(tempstr,sizeof(tempstr), "Player velocity: %.2f", m_flPlayerVelocity ); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr), "Physics velocity: %.2f", m_flPhysicsVelocity ); + EntityText(text_offset,tempstr,0); + text_offset++; + + // Get the target + CBaseEntity *pLaunchTarget = m_hLaunchTarget; + + // See if we're attempting to hit a target + if ( pLaunchTarget ) + { + Vector vecSourcePos = GetAbsOrigin(); + float flGravity = sv_gravity.GetFloat(); + + { + Vector vecTargetPos = pLaunchTarget->GetAbsOrigin(); + vecTargetPos.z -= 32.0f; + + Vector vecVelocity = (vecTargetPos - vecSourcePos); + + // throw at a constant time + float time = vecVelocity.Length( ) / m_flPlayerVelocity; + vecVelocity = vecVelocity * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecVelocity.z += flGravity * time * 0.5; + + Q_snprintf(tempstr,sizeof(tempstr), "Adjusted Player velocity: %.2f", vecVelocity.Length() ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + { + Vector vecTargetPos = pLaunchTarget->GetAbsOrigin(); + Vector vecVelocity = (vecTargetPos - vecSourcePos ); + + // throw at a constant time + float time = vecVelocity.Length( ) / m_flPhysicsVelocity; + vecVelocity = vecVelocity * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecVelocity.z += flGravity * time * 0.5; + + Q_snprintf(tempstr,sizeof(tempstr), "Adjusted Physics velocity: %.2f", vecVelocity.Length() ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + } + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::Spawn( void ) +{ + BaseClass::Spawn(); + + // Don't let the camera shoot through us! + InitTrigger(); + + for ( int i = 0; i < MAX_PLAYERS + 1; ++i ) + { + m_flRefireDelay[i] = 0.0f; + } + + m_flLowerThreshold = clamp( m_flLowerThreshold, 0.0f, 1.0f ); + m_flUpperThreshold = clamp( m_flUpperThreshold, 0.0f, 1.0f ); + + SetTransmitState( FL_EDICT_PVSCHECK ); + + m_hLaunchTarget = gEntList.FindEntityByName( NULL, m_strLaunchTarget ); + + SetContextThink( &CTriggerCatapult::PlayerPassesTriggerFiltersThink, gpGlobals->curtime + 1.0f, s_szPlayerPassesTriggerFiltersThinkContext ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::InputSetPlayerSpeed( inputdata_t &in ) +{ + m_flPlayerVelocity = in.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::InputSetPhysicsSpeed( inputdata_t &in ) +{ + m_flPhysicsVelocity = in.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::InputSetLaunchTarget( inputdata_t &in ) +{ + m_strLaunchTarget = in.value.StringID(); + + m_hLaunchTarget = gEntList.FindEntityByName( NULL, m_strLaunchTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::InputSetExactVelocityChoiceType( inputdata_t &in ) +{ + m_ExactVelocityChoice = in.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::LaunchThink( void ) +{ + for ( int i = 0; i < m_hAbortedLaunchees.Count(); i++ ) + { + CBaseEntity *pOther = m_hAbortedLaunchees[i].Get(); + bool bShouldRemove = true; + + if ( pOther ) + { + if ( pOther->IsPlayer() ) + { + // Time to get launched and stay in the list in case we're stuck under something again. + bShouldRemove = false; + StartTouch( pOther ); + } + else if ( pOther->VPhysicsGetObject() ) + { + if( ( pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) ) + { + // the sphere should stay in the list. + bShouldRemove = false; + } + else + { + // Time to get launched! + StartTouch( pOther ); + } + } + } + + if ( bShouldRemove ) + { + m_hAbortedLaunchees.Remove( i ); + i--; + } + } + + // see if we are still holding something in the catapult + if( m_hAbortedLaunchees.Count() ) + { + SetThink( &CTriggerCatapult::LaunchThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); + } + else + { + SetThink( NULL ); + } +} + +//Think once a second, looking for a living player. Once one is found, evaluate if they pass our trigger filters and network that down to the client +void CTriggerCatapult::PlayerPassesTriggerFiltersThink( void ) +{ + for( int i = 1; i != gpGlobals->maxClients; ++i ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if( pPlayer && pPlayer->IsAlive() ) + { + m_bPlayersPassTriggerFilters = PassesTriggerFilters( pPlayer ); + SetContextThink( NULL, TICK_NEVER_THINK, s_szPlayerPassesTriggerFiltersThinkContext ); //never test again + return; + } + } + + SetContextThink( &CTriggerCatapult::PlayerPassesTriggerFiltersThink, gpGlobals->curtime + 1.0f, s_szPlayerPassesTriggerFiltersThinkContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::EndTouch( CBaseEntity *pOther ) +{ + m_hAbortedLaunchees.FindAndFastRemove( pOther ); +} diff --git a/game/server/portal2/trigger_catapult.h b/game/server/portal2/trigger_catapult.h new file mode 100644 index 00000000..3fbf66fb --- /dev/null +++ b/game/server/portal2/trigger_catapult.h @@ -0,0 +1,101 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// copied from portal2 code; original code came with client-predicted counterpart, +// but implementing predictable triggers in tf2 wasn't trivial so this is just the +// server component. it works but causes prediction errors. +#ifndef TRIGGER_CATAPULT_H +#define TRIGGER_CATAPULT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "triggers.h" + + +class CTriggerCatapult : public CBaseTrigger +{ + DECLARE_CLASS( CTriggerCatapult, CBaseTrigger ); + DECLARE_DATADESC(); + //DECLARE_SERVERCLASS(); + +public: + + CTriggerCatapult( void ); + + virtual void Spawn( void ); + virtual void StartTouch( CBaseEntity *pOther ); + virtual void EndTouch( CBaseEntity *pOther ); + virtual int DrawDebugTextOverlays(void); + virtual void DrawDebugGeometryOverlays( void ); + + void LaunchThink( void ); + + void PlayerPassesTriggerFiltersThink( void ); + static const char *s_szPlayerPassesTriggerFiltersThinkContext; + +private: + void InputSetPlayerSpeed( inputdata_t &in ); + void InputSetPhysicsSpeed( inputdata_t &in ); + void InputSetLaunchTarget( inputdata_t &in ); + void InputSetExactVelocityChoiceType( inputdata_t &in ); + + void LaunchByTarget( CBaseEntity *pVictim, CBaseEntity *pTarget ); + Vector CalculateLaunchVector( CBaseEntity *pVictim, CBaseEntity *pTarget ); + Vector CalculateLaunchVectorPreserve( Vector vecInitialVelocity, CBaseEntity *pVictim, CBaseEntity *pTarget, bool bForcePlayer = false ); + + void LaunchByDirection( CBaseEntity *pVictim ); + void OnLaunchedVictim( CBaseEntity *pVictim ); + + float m_flRefireDelay[MAX_PLAYERS + 1]; + float m_flPlayerVelocity; + float m_flPhysicsVelocity; + QAngle m_vecLaunchAngles; + string_t m_strLaunchTarget; + int m_ExactVelocityChoice; + bool m_bUseExactVelocity; + bool m_bUseThresholdCheck; + float m_flLowerThreshold; + float m_flUpperThreshold; + float m_flEntryAngleTolerance; + EHANDLE m_hLaunchTarget; + bool m_bOnlyVelocityCheck; + bool m_bApplyAngularImpulse; + bool m_bPlayersPassTriggerFilters; + float m_flAirControlSupressionTime; + bool m_bDirectionSuppressAirControl; + + //CNetworkArray( float, m_flRefireDelay, MAX_PLAYERS + 1 ); // 0 for physics object the rest for each player userid + //CNetworkVar( float, m_flPlayerVelocity ); + //CNetworkVar( float, m_flPhysicsVelocity ); + //CNetworkQAngle( m_vecLaunchAngles ); + ////CNetworkVar( string_t, m_strLaunchTarget ); + //string_t m_strLaunchTarget; + + //CNetworkVar( int, m_ExactVelocityChoice ); + //CNetworkVar( bool, m_bUseExactVelocity ); + + //CNetworkVar( bool, m_bUseThresholdCheck ); + //CNetworkVar( float, m_flLowerThreshold ); + //CNetworkVar( float, m_flUpperThreshold ); + //CNetworkVar( float, m_flEntryAngleTolerance ); + + //CNetworkHandle( CBaseEntity, m_hLaunchTarget ); + + //CNetworkVar( bool, m_bOnlyVelocityCheck ); + //CNetworkVar( bool, m_bApplyAngularImpulse ); + //CNetworkVar( bool, m_bPlayersPassTriggerFilters ); + + //CNetworkVar( float, m_flAirControlSupressionTime ); // After catapult, stop air control for this long (or default of quarter second if this value is negative) + //CNetworkVar( bool, m_bDirectionSuppressAirControl ); // Do we want to use air control suppression for directional catapults. + + COutputEvent m_OnCatapulted; + + CUtlVector< EHANDLE > m_hAbortedLaunchees; +}; + +#endif // TRIGGER_CATAPULT_H diff --git a/game/server/server_portal.vpc b/game/server/server_portal.vpc index d66a2bb7..aa015c08 100644 --- a/game/server/server_portal.vpc +++ b/game/server/server_portal.vpc @@ -367,6 +367,9 @@ $Project "Server (Portal)" $File "movie_display.cpp" + $File "portal2\trigger_catapult.cpp" + $File "$SRCDIR\game\shared\portal2\trigger_catapult_shared.cpp" + //$Folder "Paint" //{ // // shared diff --git a/game/shared/portal2/trigger_catapult_shared.cpp b/game/shared/portal2/trigger_catapult_shared.cpp new file mode 100644 index 00000000..cb93e500 --- /dev/null +++ b/game/shared/portal2/trigger_catapult_shared.cpp @@ -0,0 +1,573 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// copied from portal2 code; original code came with client-predicted counterpart, +// but implementing predictable triggers in tf2 wasn't trivial so this is just the +// server component. it works but causes prediction errors. +#include "cbase.h" + +#include "movevars_shared.h" +#include "portal/portal_player.h" + +#if defined( GAME_DLL ) +#include "trigger_catapult.h" +#include "portal_player.h" +#include "vcollide_parse.h" +#include "props.h" +#else +#include "c_trigger_catapult.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar catapult_physics_drag_boost( "catapult_physics_drag_boost", "2.1", FCVAR_REPLICATED ); + + +//----------------------------------------------------------------------------- +// Purpose: calculates the launch vector between the entity that touched the +// catapult trigger and the catapult target +//----------------------------------------------------------------------------- +Vector CTriggerCatapult::CalculateLaunchVector( CBaseEntity *pVictim, CBaseEntity *pTarget ) +{ +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pVictim->GetPredictable() ) + return vec3_origin; +#endif + + // Find where we're going + Vector vecSourcePos = pVictim->GetAbsOrigin(); + Vector vecTargetPos = pTarget->GetAbsOrigin(); + + // If victim is player, adjust target position so player's center will hit the target + if ( pVictim->IsPlayer() ) + { + vecTargetPos.z -= 32.0f; + } + + float flSpeed = (pVictim->IsPlayer()) ? (float)m_flPlayerVelocity : (float)m_flPhysicsVelocity; // u/sec + float flGravity = GetCurrentGravity(); + + Vector vecVelocity = (vecTargetPos - vecSourcePos); + + // throw at a constant time + float time = vecVelocity.Length( ) / flSpeed; + vecVelocity = vecVelocity * (1.f / time); // CatapultLaunchVelocityMultiplier + + // adjust upward toss to compensate for gravity loss + vecVelocity.z += flGravity * time * 0.5; + + return vecVelocity; +} + +//----------------------------------------------------------------------------- +// Purpose: calculates the launch vector between the entity that touched the +// catapult trigger and the catapult target +//----------------------------------------------------------------------------- +Vector CTriggerCatapult::CalculateLaunchVectorPreserve( Vector vecInitialVelocity, CBaseEntity *pVictim, CBaseEntity *pTarget, bool bForcePlayer ) +{ +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pVictim->GetPredictable() ) + return vec3_origin; +#endif + + // Find where we're going + Vector vecSourcePos = pVictim->GetAbsOrigin(); + Vector vecTargetPos = pTarget->GetAbsOrigin(); + + // If victim is player, adjust target position so player's center will hit the target + if ( pVictim->IsPlayer() || bForcePlayer ) + { + vecTargetPos.z -= 32.0f; + } + + Vector vecDiff = (vecTargetPos - vecSourcePos); + + float flHeight = vecDiff.z; + float flDist = vecDiff.Length2D(); + float flVelocity = (pVictim->IsPlayer() || bForcePlayer ) ? (float)m_flPlayerVelocity : (float)m_flPhysicsVelocity; + float flGravity = -1.0f*GetCurrentGravity(); + + + if( flDist == 0.f ) + { + DevWarning( "Bad location input for catapult!\n" ); + return CalculateLaunchVector(pVictim, pTarget); + } + + float flRadical = flVelocity*flVelocity*flVelocity*flVelocity - flGravity*(flGravity*flDist*flDist - 2.f*flHeight*flVelocity*flVelocity); + + if( flRadical <= 0.f ) + { + DevWarning( "Catapult can't hit target! Add more speed!\n" ); + return CalculateLaunchVector(pVictim, pTarget); + } + + flRadical = ( sqrt( flRadical ) ); + + float flTestAngle1 = flVelocity*flVelocity; + float flTestAngle2 = flTestAngle1; + + flTestAngle1 = -atan( (flTestAngle1 + flRadical) / (flGravity*flDist) ); + flTestAngle2 = -atan( (flTestAngle2 - flRadical) / (flGravity*flDist) ); + + Vector vecTestVelocity1 = vecDiff; + vecTestVelocity1.z = 0; + vecTestVelocity1.NormalizeInPlace(); + + Vector vecTestVelocity2 = vecTestVelocity1; + + vecTestVelocity1 *= flVelocity*cos( flTestAngle1 ); + vecTestVelocity1.z = flVelocity*sin( flTestAngle1 ); + + vecTestVelocity2 *= flVelocity*cos( flTestAngle2 ); + vecTestVelocity2.z = flVelocity*sin( flTestAngle2 ); + + vecInitialVelocity.NormalizeInPlace(); + + if( m_ExactVelocityChoice == 1 ) + { + return vecTestVelocity1; + } + else if( m_ExactVelocityChoice == 2 ) + { + return vecTestVelocity2; + } + + if( vecInitialVelocity.Dot( vecTestVelocity1 ) > vecInitialVelocity.Dot( vecTestVelocity2 ) ) + { + return vecTestVelocity1; + } + return vecTestVelocity2; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::LaunchByTarget( CBaseEntity *pVictim, CBaseEntity *pTarget ) +{ +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pVictim->GetPredictable() ) + return; +#endif + + Vector vecVictim; + if ( pVictim->VPhysicsGetObject() ) + { + pVictim->VPhysicsGetObject()->GetVelocity( &vecVictim, NULL ); + } + else + { + vecVictim = pVictim->GetAbsVelocity(); + } + // get the launch vector + Vector vecVelocity = m_bUseExactVelocity ? + CalculateLaunchVectorPreserve( vecVictim, pVictim, pTarget ): + CalculateLaunchVector( pVictim, pTarget ); + + + // Handle a player + if ( pVictim->IsPlayer() ) + { + // Send us flying + if ( pVictim->GetFlags() & FL_ONGROUND ) + { + pVictim->SetGroundEntity( NULL ); + pVictim->SetGroundChangeTime( gpGlobals->curtime + 0.5f ); + } + + CPortal_Player *pPlayer = ToPortalPlayer( pVictim ); + if ( pPlayer ) + { + float flSupressionTimeInSeconds = 0.25f; + if ( m_flAirControlSupressionTime > 0 ) + { + // If set in the map, use this override time + flSupressionTimeInSeconds = m_flAirControlSupressionTime; + } + //pPlayer->SetAirControlSupressionTime( flSupressionTimeInSeconds * 1000.0f ); // fix units, this method expects milliseconds + pVictim->Teleport( NULL, NULL, &vecVelocity ); + OnLaunchedVictim( pVictim ); + +#if defined( GAME_DLL ) && !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) + //g_PortalGameStats.Event_Catapult_LaunchByTarget( pPlayer, vecVelocity ); +#endif + } + } + else + { + if ( pVictim->GetMoveType() == MOVETYPE_VPHYSICS ) + { + // Launch! + IPhysicsObject *pPhysObject = pVictim->VPhysicsGetObject(); + if ( pPhysObject ) + { + AngularImpulse angImpulse = m_bApplyAngularImpulse ? RandomAngularImpulse( -150.0f, 150.0f ) : vec3_origin; + pPhysObject->SetVelocityInstantaneous( &vecVelocity, &angImpulse ); + + // UNDONE: don't mess with physics properties + +#if defined( GAME_DLL ) + CPhysicsProp *pProp = dynamic_cast(pVictim); + if ( pProp != NULL ) + { + //HACK! + pProp->OnPhysGunDrop( UTIL_GetLocalPlayer(), LAUNCHED_BY_CANNON ); + } +#endif + } + } + OnLaunchedVictim( pVictim ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::LaunchByDirection( CBaseEntity *pVictim ) +{ +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pVictim->GetPredictable() ) + return; +#endif + + Vector vecForward; + AngleVectors( m_vecLaunchAngles, &vecForward, NULL, NULL ); + + // Handle a player + if ( pVictim->IsPlayer() ) + { + // Simply push us forward + Vector vecPush = vecForward * m_flPlayerVelocity; + + // Hack on top of magic + if( CloseEnough( vecPush[0], 0.f ) && CloseEnough( vecPush[1],0.f ) ) + { + vecPush[2] = m_flPlayerVelocity * 1.5f; // FIXME: Magic! + } + + // Send us flying + if ( pVictim->GetFlags() & FL_ONGROUND ) + { + pVictim->SetGroundEntity( NULL ); + pVictim->SetGroundChangeTime( gpGlobals->curtime + 0.5f ); + } + + pVictim->SetAbsVelocity( vecPush ); + OnLaunchedVictim( pVictim ); + + // Do air control suppression + if( m_bDirectionSuppressAirControl ) + { + float flSupressionTimeInSeconds = 0.25f; + if ( m_flAirControlSupressionTime > 0 ) + { + // If set in the map, use this override time + flSupressionTimeInSeconds = m_flAirControlSupressionTime; + } + + //CPortal_Player* pTFPlayer = static_cast(pVictim); + //pTFPlayer->SetAirControlSupressionTime( flSupressionTimeInSeconds * 1000.0f ); // fix units, this method expects milliseconds + } + +#if defined( GAME_DLL ) && !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) + //g_PortalGameStats.Event_Catapult_LaunchByDirection( ToPortalPlayer(pVictim), vecPush ); +#endif + } +#if defined( GAME_DLL ) + else + { + if ( pVictim->GetMoveType() == MOVETYPE_VPHYSICS ) + { + // Launch! + IPhysicsObject *pPhysObject = pVictim->VPhysicsGetObject(); + if ( pPhysObject ) + { + Vector vecVelocity = vecForward * m_flPhysicsVelocity; + vecVelocity[2] = m_flPhysicsVelocity; + + AngularImpulse angImpulse = RandomAngularImpulse( -50.0f, 50.0f ); + + pPhysObject->SetVelocityInstantaneous( &vecVelocity, &angImpulse ); + + // Force this! + float flNull = 0.0f; + pPhysObject->SetDragCoefficient( &flNull, &flNull ); + pPhysObject->SetDamping( &flNull, &flNull ); + + CPhysicsProp *pProp = dynamic_cast(pVictim); + if ( pProp != NULL ) + { + //HACK! + pProp->OnPhysGunDrop( UTIL_GetLocalPlayer(), LAUNCHED_BY_CANNON ); + } + } + } + OnLaunchedVictim( pVictim ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::OnLaunchedVictim( CBaseEntity *pVictim ) +{ +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pVictim->GetPredictable() ) + return; +#endif + +#if defined( GAME_DLL ) + m_OnCatapulted.FireOutput( pVictim, this ); +#endif + + if ( pVictim->IsPlayer() ) + { + CPortal_Player *pPlayer = static_cast< CPortal_Player* >( pVictim ); + int nRefireIndex = pPlayer->entindex(); +#if defined( GAME_DLL ) + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#else + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#endif + } + else + { +#if defined( GAME_DLL ) + m_flRefireDelay[ 0 ] = gpGlobals->curtime + 0.5f; // HACK! +#else + m_flRefireDelay[ 0 ] = gpGlobals->curtime + 0.5f; // HACK! +#endif + } +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerCatapult::StartTouch( CBaseEntity *pOther ) +{ + if ( pOther == NULL ) + return; + +#if defined( CLIENT_DLL ) + if( !GetPredictable() || !pOther->GetPredictable() ) + return; +#endif + + //Warning( "CTriggerCatapult::StartTouch( %i %s %f )\n", entindex(), gpGlobals->IsClient() ? "client" : "server", gpGlobals->curtime ); + + +#if defined( GAME_DLL ) + if ( PassesTriggerFilters( pOther ) == false ) +#else + if( !(pOther->IsPlayer() && m_bPlayersPassTriggerFilters) ) +#endif + { + return; + } + + // Don't refire too quickly + int nRefireIndex = pOther->IsPlayer() ? static_cast< CBasePlayer* >( pOther )->entindex() : 0; + if ( nRefireIndex >= MAX_PLAYERS + 1 ) + { + Warning( "CTriggerCatapult::StartTouch Trying to store a refire index for an entity( %d ) outside the expected range ( < %d ).\n", nRefireIndex, MAX_PLAYERS + 1 ); + nRefireIndex = 0; + } + + if ( m_flRefireDelay[ nRefireIndex ] > gpGlobals->curtime ) + { + // but also don't forget to try again + if ( m_hAbortedLaunchees.Find( pOther ) == -1 ) + { + m_hAbortedLaunchees.AddToTail( pOther ); + } + SetThink( &CTriggerCatapult::LaunchThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); + return; + } + +#if defined( GAME_DLL ) + // Don't touch things the player is holding + if ( pOther->VPhysicsGetObject() && (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) ) + { + if ( m_hAbortedLaunchees.Find( pOther ) == -1 ) + { + m_hAbortedLaunchees.AddToTail( pOther ); + } + SetThink( &CTriggerCatapult::LaunchThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); + return; + } + else if ( pOther->IsPlayer() ) + { + // Always keep players in this list in case the were trapped under another player in the previous launch + if ( m_hAbortedLaunchees.Find( pOther ) == -1 ) + { + m_hAbortedLaunchees.AddToTail( pOther ); + } + SetThink( &CTriggerCatapult::LaunchThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); + } +#endif + + // Get the target + CBaseEntity *pLaunchTarget = m_hLaunchTarget; + + // See if we're attempting to hit a target + if ( pLaunchTarget ) + { + // See if we are using the threshold check + if ( m_bUseThresholdCheck ) + { + // Get the velocity of the physics objects / players touching the catapult + Vector vecVictim; + if ( pOther->IsPlayer() ) + { + vecVictim = pOther->GetAbsVelocity(); + } + else if( pOther->VPhysicsGetObject() ) + { + pOther->VPhysicsGetObject()->GetVelocity( &vecVictim, NULL ); + } + else + { +// DevMsg("Catapult fail!! Object is not a player and has no physics object! BUG THIS\n"); + vecVictim = vec3_origin; + } + + float flVictimSpeed = vecVictim.Length(); + + // get the speed needed to hit the target + Vector vecVelocity; + if( m_bUseExactVelocity ) + { + vecVelocity = CalculateLaunchVectorPreserve( vecVictim, pOther, pLaunchTarget ); + } + else + { + vecVelocity = CalculateLaunchVector( pOther, pLaunchTarget ); + } + float flLaunchSpeed = vecVelocity.Length(); + + // is the victim facing the target? + Vector vecDirection = ( pLaunchTarget->GetAbsOrigin() - pOther->GetAbsOrigin() ); + Vector necNormalizedVictim = vecVictim; + Vector vecNormalizedDirection = vecDirection; + + necNormalizedVictim.NormalizeInPlace(); + vecNormalizedDirection.NormalizeInPlace(); + + float flDot = DotProduct( necNormalizedVictim, vecNormalizedDirection ); + if ( flDot >= m_flEntryAngleTolerance ) + { + // Is the victim speed within tolerance to launch them? + if ( ( ( flLaunchSpeed - (flLaunchSpeed * m_flLowerThreshold ) ) < flVictimSpeed ) && ( ( flLaunchSpeed + (flLaunchSpeed * m_flUpperThreshold ) ) > flVictimSpeed ) ) + { + if( m_bOnlyVelocityCheck ) + { + OnLaunchedVictim( pOther ); + } + else + { + // Launch! + LaunchByTarget( pOther, pLaunchTarget ); +// DevMsg( 1, "Catapult \"%s\" is adjusting velocity of \"%s\" so it will hit the target. (Object Velocity: %.1f -- Object needed to be between %.1f and %.1f \n", STRING(GetEntityName()), pOther->GetClassname(), flVictimSpeed, flLaunchSpeed - (flLaunchSpeed * m_flLowerThreshold ), flLaunchSpeed + (flLaunchSpeed * m_flUpperThreshold ) ); + } + } + else + { +// DevMsg( 1, "Catapult \"%s\" ignoring object \"%s\" because its velocity is outside of the threshold. (Object Velocity: %.1f -- Object needed to be between %.1f and %.1f \n", STRING(GetEntityName()), pOther->GetClassname(), flVictimSpeed, flLaunchSpeed - (flLaunchSpeed * m_flLowerThreshold ), flLaunchSpeed + (flLaunchSpeed * m_flUpperThreshold ) ); + // since we attempted a fling set the refire delay +#if defined( GAME_DLL ) + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#else + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#endif + } + } + else + { + // we're facing the wrong way. set the refire delay. +#if defined( GAME_DLL ) + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#else + m_flRefireDelay[ nRefireIndex ] = gpGlobals->curtime + 0.5f; // HACK! +#endif + } + } + else + { + LaunchByTarget( pOther, pLaunchTarget ); + } + } + else + { +#if defined( CLIENT_DLL ) + if( m_hLaunchTarget.IsValid() ) + { + Warning( "Catapult launch target not networked to client! This will make prediction fail! Fix this in the map.\n" + "Catapult launch target not networked to client! This will make prediction fail! Fix this in the map.\n" + "Catapult launch target not networked to client! This will make prediction fail! Fix this in the map.\n" + "Catapult launch target not networked to client! This will make prediction fail! Fix this in the map.\n" + "Catapult launch target not networked to client! This will make prediction fail! Fix this in the map.\n" ); + } +#endif + + bool bShouldLaunch = true; + + if( m_bUseThresholdCheck ) + { + // Get the velocity of the physics objects / players touching the catapult + Vector vecVictim; + if ( pOther->IsPlayer() ) + { + vecVictim = pOther->GetAbsVelocity(); + } + else if( pOther->VPhysicsGetObject() ) + { + pOther->VPhysicsGetObject()->GetVelocity( &vecVictim, NULL ); + } + else + { +// DevMsg("Catapult fail!! Object is not a player and has no physics object! BUG THIS\n"); + vecVictim = vec3_origin; + } + + Vector vecForward; + AngleVectors( m_vecLaunchAngles, &vecForward, NULL, NULL ); + + float flDot = DotProduct( vecForward, vecVictim ); + float flLower = m_flPlayerVelocity - (m_flPlayerVelocity * m_flLowerThreshold); + float flUpper = m_flPlayerVelocity + (m_flPlayerVelocity * m_flUpperThreshold); + if( flDot < flLower || flDot > flUpper ) + { + bShouldLaunch = false; + } + } + + if( bShouldLaunch ) + { +#if defined( CLIENT_DLL ) + CEG_PROTECT_VIRTUAL_FUNCTION ( CTriggerCatapult_StartTouch ); +#endif + if( m_bOnlyVelocityCheck ) + { + OnLaunchedVictim( pOther ); + } + else + { + LaunchByDirection( pOther ); + } + } + } +}