cstrike15_src/game/shared/portal/portal_player_shared.cpp
2025-06-04 03:22:50 +02:00

5228 lines
173 KiB
C++
Raw Blame History

//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include <functional>
#include "portal_player_shared.h"
//#include "portal_playeranimstate.h"
#include "debugoverlay_shared.h"
#include "mesh.h"
#include "in_buttons.h"
#include "portal_base2d_shared.h"
#include "movevars_shared.h"
#include "util_shared.h"
#include "portal_util_shared.h"
#include "collisionutils.h"
#include "portal_mp_gamerules.h"
#ifdef CLIENT_DLL
#include "c_portal_player.h"
#include "prediction.h"
#define CRecipientFilter C_RecipientFilter
#include "iclientvehicle.h"
#include "c_basedoor.h"
#include "c_world.h"
#include "view.h"
#include "iviewrender.h"
#include "ivieweffects.h"
#define CRecipientFilter C_RecipientFilter
extern IViewRender *view;
extern IViewEffects *GetViewEffects();
#include "cdll_util.h"
#include "c_portal_base2d.h"
#include "c_weapon_paintgun.h"
#include "c_trigger_catapult.h"
#include "c_trigger_tractorbeam.h"
#include "c_basetempentity.h"
#include "igameevents.h"
#include "cam_thirdperson.h"
#define CTriggerCatapult C_TriggerCatapult
#else
#include "portal_player.h"
#include "ai_basenpc.h"
#include "portal_gamestats.h"
#include "util.h"
#include "eventqueue.h"
#include "physics_bone_follower.h"
#include "prop_testchamber_door.h"
#include "iservervehicle.h"
#include "trains.h"
#include "world.h"
#include "doors.h"
#include "ai_basenpc.h"
#include "env_zoom.h"
#include "ammodef.h"
#include "npc_security_camera.h"
extern int TrainSpeed(int iSpeed, int iMax);
#include "portal_base2d.h"
#include "paint_database.h"
#include "basetempentity.h"
#include "paint_swap_guns.h"
#include "weapon_paintgun.h"
#include "weapon_portalgun.h"
#include "trigger_catapult.h"
#include "trigger_tractorbeam.h"
#include "physicsshadowclone.h"
#include "explode.h"
#include "props.h"
#endif
#include "vphysics/player_controller.h"
#include "in_buttons.h"
#include "engine/IEngineSound.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "collisionutils.h"
#include "portal2/portal_grabcontroller_shared.h"
#include "portal2/player_pickup.h"
#include "weapon_paintgun_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define TLK_PLAYER_PICKED_UP_ITEM "TLK_PLAYER_PICKED_UP_ITEM"
const char *g_pszChellConcepts[] =
{
"CONCEPT_CHELL_IDLE",
"CONCEPT_CHELL_DEAD",
};
extern ConVar sv_footsteps;
extern ConVar sv_debug_player_use;
char const* const JUMP_HELPER_CONTEXT = "Jump Helper Powers";
char const* const AIR_LOOK_AHEAD_CONTEXT = "Air Look Ahead Powers";
char const* const STICK_PREDICTION_CONTEXT = "Stick Prediction Powers";
char const* const PAINT_SCREEN_EFFECT = "boomer_vomit_screeneffect";//"paint_screen_effect";
char const* const PAINT_DRIP_EFFECT = "boomer_vomit_survivor";//"paint_drip_effect";
const float BOUNCE_PAINT_INPUT_DAMPING = 0.1f;
const float BOUNCE_PAINT_INPUT_DAMP_TIME = 2.0;
const float MIN_SPEED_PAINT_AIR_INPUT_START_DAMPING = 0.05f;
const float SPEED_PAINT_AIR_INPUT_DAMP_TIME = 5.0f;
const float RELATIVE_ERROR = 1.0e-6;
const float DEAD_INPUT_COS = 0.70710678118654752440084436210485f;
const Vector ABS_UP = Vector(0.f,0.f,1.f);
// The depth of the OBB on the portal surface within which we reject contacts
// HACK: Half width of player box + 1 for now. We'll have to do a projection of
// the collision shape onto the portal normal when we have oriented bounding data
const float PORTAL_PLANE_IGNORE_EPSILON = 17.0f;
extern ConVar sv_speed_normal;
extern ConVar sv_speed_paint_max;
extern ConVar portal_tauntcam_dist;
ConVar portal_deathcam_dist( "portal_deathcam_dist", "128", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
ConVar sv_portal_coop_ping_hud_indicitator_duration( "sv_portal_coop_ping_hud_indicitator_duration", "5", FCVAR_REPLICATED );
char const* g_pszPredictedPowerIgnoreFilter[] =
{
"prop_weighted_cube"
};
const char *GetEggBotModel( bool bLowRes )
{
if ( bLowRes)
return "models/player/eggbot/eggbot_opt.mdl";
return "models/player/eggbot/eggbot.mdl";
}
const char *GetBallBotModel( bool bLowRes )
{
if ( bLowRes)
return "models/player/ballbot/ballbot_opt.mdl";
return "models/player/ballbot/ballbot.mdl";
}
const char *g_pszPlayerModel = "models/player/chell/player.mdl";
int PAINTED_SURFACE_PROPERTY_INDEX = -1;
ConVar sv_enableholdrotation( "sv_enableholdrotation", "0", FCVAR_REPLICATED, "When enabled, hold attack2 to rotate held objects" );
ConVar sv_player_use_cone_size( "sv_player_use_cone_size", "0.6", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar sv_use_trace_duration( "sv_use_trace_duration", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT );
//bounce convars
ConVar sv_bounce_paint_forward_velocity_bonus("sv_bounce_paint_forward_velocity_bonus", "0.375f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "What percentage of forward velocity to add onto a ground bounce");
ConVar bounce_paint_wall_jump_upward_speed("bounce_paint_wall_jump_upward_speed", "275", FCVAR_REPLICATED | FCVAR_CHEAT, "The upward velocity added when bouncing off a wall");
ConVar bounce_paint_min_speed("bounce_paint_min_speed", "500.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "For tweaking how high bounce paint launches the player.");
ConVar sv_wall_bounce_trade("sv_wall_bounce_trade", "0.73", FCVAR_REPLICATED | FCVAR_CHEAT, "How much outward velocity is traded for upward velocity on wall bounces");
ConVar jump_helper_enabled("jump_helper_enabled", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Use long jump helper code?");
ConVar jump_helper_look_ahead_time("jump_helper_look_ahead_time", "0.2f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Look ahead time for long jump helper. This assumes constant velocity and samples ahead in discrete chunks of time. Use paint_power_look_ahead_sample_density to adjust how many samples are taken over a given distance.");
ConVar paint_power_look_ahead_sample_density("paint_power_look_ahead_sample_density", "0.07f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Number of samples per game unit along the extent of look ahead vector.");
ConVar jump_helper_late_jump_max_time("jump_helper_late_jump_max_time", "0.2f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "How late a player can try to jump and still get a super jump she's no longer touching.");
ConVar jump_helper_debug_enabled("jump_helper_debug_enabled", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Toggle debug draw and and messages for jump helper.");
ConVar bounce_reflect_restitution("bounce_reflect_restitution", "1.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "The elasticity of a collision with a bounce paint surface. Values should be in the range [0, 1].");
ConVar trampoline_bounce_off_walls_while_on_ground("trampoline_bounce_off_walls_while_on_ground", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Trampoline bounce activates a wall jump if the player walks into a wall.");
ConVar jump_button_can_activate_trampoline_bounce("jump_button_can_activate_trampoline_bounce", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "The space bar can activate the initial bounce in trampoline bounce mode.");
ConVar bounce_reflect_wall_jumps_enabled("bounce_reflect_wall_jumps_enabled", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Enable/Disable wall jumps for reflection bounce.");
ConVar bounce_reflect_wall_jump_min_up_speed("bounce_reflect_wall_jump_min_up_speed", "10.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "The minimum upward speed the player will jump off the wall with when reflection bounce wall jumps are enabled.");
ConVar trampoline_bounce_min_impact_speed("trampoline_bounce_min_impact_speed", "4.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "The minimum speed into the surface to activate a trampoline bounce.");
ConVar bounce_ledge_fall_height_boost("bounce_ledge_fall_height_boost", "45", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Toggle whether bounce reflections after falling off a ledge without jumping add upward hop velocity on the first bounce.");
ConVar bounce_auto_trigger_min_speed("bounce_auto_trigger_min_speed", "500", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "At what speed the player will auto-bounce when running over bounce paint.");
ConVar look_dependent_auto_long_jump_enabled("look_dependent_auto_long_jump_enabled", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "The player must be looking roughly in the direction she's travelling to get activate an auto long jump.");
ConVar look_dependent_auto_long_jump_min_cos_angle("look_dependent_auto_long_jump_min_cos_angle", "0.7", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "The player must be looking roughly in the direction she's travelling to get activate an auto long jump.");
//speed convars
ConVar sv_speed_paint_acceleration("sv_speed_paint_acceleration", "500.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "How fast the player accelerates on speed paint.");
ConVar sv_speed_paint_ramp_acceleration("sv_speed_paint_ramp_acceleration", "1000.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "How fast the player accelerates on speed paint when on a ramp.");
ConVar sv_speed_paint_on_bounce_deceleration_delay("sv_speed_paint_on_bounce_deceleration_delay", "0.2f", FCVAR_REPLICATED | FCVAR_CHEAT, "How long before starting to decelerate if going from speed to bounce.");
ConVar sv_speed_paint_straf_accel_scale("sv_speed_paint_straf_accel_scale", "0.7f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Scale applied to acceleration when the player is strafing on speed paint.");
//stick convars
//ConVar stick_surface_transition_delay("stick_surface_transition_delay", ".5f", FCVAR_REPLICATED | FCVAR_CHEAT, "How long to wait after transitioning to a new stick surface before the player can transition again.");
#define player_can_unstick_by_pushing 1//ConVar player_can_unstick_by_pushing("player_can_unstick_by_pushing", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Allow/disallow players to walk off a sticky surface into the air with WASD.");
//stick camera
#define stick_cam_correct_pitch 1 //ConVar stick_cam_correct_pitch("stick_cam_correct_pitch", "1", FCVAR_REPLICATED);
#define sv_stick_input_cancel_threshold 1.f //ConVar sv_stick_input_cancel_threshold("sv_stick_input_cancel_threshold", "1.f", FCVAR_REPLICATED | FCVAR_CHEAT, "Threshold of mouse_y input to cancel correct pitch" );
#define stick_cam_pitch_rate 0.2f //ConVar stick_cam_pitch_rate("stick_cam_pitch_rate", "0.2f", FCVAR_REPLICATED | FCVAR_CHEAT );
#define stick_cam_roll_rate 0.125f //ConVar stick_cam_roll_rate("stick_cam_roll_rate", "0.125f", FCVAR_REPLICATED | FCVAR_CHEAT );
#define stick_cam_over_the_top_threshold 0.75f //ConVar stick_cam_over_the_top_threshold("stick_cam_over_the_top_threshold", "0.75", FCVAR_REPLICATED, "How far up/down the player must look to trigger pitch instead of roll correction" ); // Life won't meet you half way
#define stick_cam_pitch_vs_roll_good_angle_threshold 0.95f //ConVar stick_cam_pitch_vs_roll_good_angle_threshold("stick_cam_pitch_vs_roll_good_angle_threshold", "0.95f", FCVAR_REPLICATED, "When not overpitched in the target orientation, how close to straight up/down we have to be looking to trigger the pitch transition" );
#define stick_cam_min_rotation_rate 10.f //ConVar stick_cam_min_rotation_rate("stick_cam_min_rotation_rate", "10.f", FCVAR_REPLICATED, "The rotation rate with which to rotate the player's up vector" );
ConVar sv_contact_region_thickness( "sv_contact_region_thickness", "0.2f", FCVAR_REPLICATED | FCVAR_CHEAT, "The thickness of a contact region (how much the box expands)." );
ConVar sv_clip_contacts_to_portals( "sv_clip_contacts_to_portals", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Enable/Disable clipping contact regions to portal planes." );
ConVar sv_debug_draw_contacts( "sv_debug_draw_contacts", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "0: Dont draw anything. 1: Draw contacts. 2: Draw colored contacts" );
ConVar sv_post_teleportation_box_time( "sv_post_teleportation_box_time", ".0333333f", FCVAR_REPLICATED | FCVAR_CHEAT, "Time to use a slightly expanded box for contacts right after teleportation." );
// Paintable player ConVars
ConVar player_can_use_painted_power("player_can_use_painted_power", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Allow/disallow players to be painted.");
ConVar player_loses_painted_power_over_time("player_loses_painted_power_over_time", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "The player should lose the power after having it for player_paint_effects_duration.");
ConVar player_paint_effects_duration("player_paint_effects_duration", "4.0f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time player maintains her power after being painted.");
ConVar player_paint_effects_enabled("player_paint_effects_enabled", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Display the visual effects of being painted.");
ConVar player_paint_screen_effect_restart_delay("player_paint_screen_effect_restart_delay", "0.5f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Time to wait between particle system restarts when the player gets painted multiple times in rapid succession.");
//Playtesting convars
ConVar sv_press_jump_to_bounce("sv_press_jump_to_bounce", "3", FCVAR_REPLICATED, "0: Bounce on touch, 1: Bounce on press, 2: Bounce on hold");
ConVar sv_bounce_reflect_enabled("sv_bounce_reflect_enabled", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Enable/Disable reflection on bounce.");
//Wall jump convars
ConVar sv_wall_jump_help("sv_wall_jump_help", "1", FCVAR_REPLICATED, "Enable the wall jump helper to help keep players bouncing between two opposing walls");
ConVar sv_wall_jump_help_threshold("sv_wall_jump_help_threshold", "9.0f", FCVAR_REPLICATED, "Threshold at which the wall jump helper will bring the player's velocity in line with the surface normal");
ConVar sv_wall_jump_help_amount("sv_wall_jump_help_amount", "5.0f", FCVAR_REPLICATED, "Maximum correction amount per wall bounce");
ConVar sv_wall_jump_help_debug("sv_wall_jump_help_debug", "0", FCVAR_REPLICATED);
//Debug convars
ConVar show_player_paint_power_debug( "show_player_paint_power_debug", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
ConVar mp_should_gib_bots("mp_should_gib_bots", "1", FCVAR_REPLICATED | FCVAR_CHEAT);
ConVar sv_debug_bounce_reflection("sv_debug_bounce_reflection", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
ConVar sv_debug_bounce_reflection_time("sv_debug_bounce_reflection_time", "15.f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
ConVar paint_compute_contacts_simd("paint_compute_contacts_simd", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Compute the contacts with paint in fast SIMD (1) or with slower FPU (0)." );
ConVar prevent_crouch_jump("prevent_crouch_jump", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Enable/Disable crouch jump prevention.");
#if defined( PORTAL2_PUZZLEMAKER )
ConVar sv_use_bendy_model("sv_use_bendy_model", "1", FCVAR_REPLICATED, "Use the bendy stick-man as the player model" );
#endif // PORTAL2_PUZZLEMAKER
extern ConVar player_held_object_use_view_model;
extern float IntervalDistance( float x, float x0, float x1 );
//-----------------------------------------------------------------------------
// Consider the weapon's built-in accuracy, this character's proficiency with
// the weapon, and the status of the target. Use this information to determine
// how accurately to shoot at the target.
//-----------------------------------------------------------------------------
Vector CPortal_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
{
if ( pWeapon )
return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT );
return VECTOR_CONE_15DEGREES;
}
void CPortal_Player::GetStepSoundVelocities( float *velwalk, float *velrun )
{
// UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!!
if ( ( GetFlags() & FL_DUCKING ) || ( GetMoveType() == MOVETYPE_LADDER ) )
{
*velwalk = 10; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow
*velrun = 60;
}
else
{
*velwalk = 90;
*velrun = 220;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : step -
// fvol -
// force - force sound to play
//-----------------------------------------------------------------------------
void CPortal_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force )
{
#ifndef CLIENT_DLL
IncrementStepsTaken();
#endif
// Cache off step side
int const side = m_Local.m_nStepside;
// Play the base material step sound
BaseClass::PlayStepSound( vecOrigin, psurface, fvol, force );
if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() )
return;
#if defined( CLIENT_DLL )
// during prediction play footstep sounds only once
if ( prediction->InPrediction() && !prediction->IsFirstTimePredicted() )
return;
#endif
// Play the paint step sound if applicable
bool shouldPlayPaintStepSound = false;
if( engine->HasPaintmap() )
{
CBaseEntity const* pGroundEntity = GetGroundEntity();
for( unsigned i = 0; i < PAINT_POWER_TYPE_COUNT; ++i )
{
CachedPaintPowerChoiceResult const& result = m_PortalLocal.m_CachedPaintPowerChoiceResults[i];
CBaseEntity const* pSurfaceEntity = result.surfaceEntity.Get() != NULL ? EntityFromEntityHandle( result.surfaceEntity.Get() ) : NULL;
if( result.wasValid && pSurfaceEntity == pGroundEntity && result.surfaceNormal.z >= STEEP_SLOPE )
{
shouldPlayPaintStepSound = true;
break;
}
}
}
if( shouldPlayPaintStepSound )
{
surfacedata_t const* pPaintedSurface = physprops->GetSurfaceData( PAINTED_SURFACE_PROPERTY_INDEX );
if( pPaintedSurface )
{
unsigned short const paintStepSoundIndex = side != 0 ? pPaintedSurface->sounds.runStepLeft : pPaintedSurface->sounds.runStepRight;
CSoundParameters soundParams;
if( paintStepSoundIndex != 0 &&
GetParametersForSound( physprops->GetString( paintStepSoundIndex ), soundParams, NULL ) )
{
// Emit the paint step sound
EmitSound_t emitParams( soundParams );
emitParams.m_flVolume = fvol;
emitParams.m_pOrigin = &vecOrigin;
CRecipientFilter filter;
filter.UsePredictionRules();
filter.AddRecipientsByPAS( vecOrigin );
EmitSound( filter, entindex(), emitParams );
}
}
}
// If this was forced, then the coop step sound probably won't play automatically from
// an animation event. Make sure it plays in multiplayer.
if( force )
PlayCoopStepSound( vecOrigin, side, fvol );
}
void CPortal_Player::PlayCoopStepSound( const Vector& origin, int side, float volume )
{
if( GameRules()->IsMultiplayer() && !m_bIsBendy )
{
const char* soundName = NULL;
// Play the robotic footstep sounds over the surface footsteps for the surface material
if( GetTeamNumber() == TEAM_BLUE )
{
// Ballbot
soundName = "CoopBot.CoopBotBallFsRoboticsImpact";
}
else
{
// Eggbot
soundName = side != 0 ? "CoopBot.CoopBotEggFsRoboticsL" : "CoopBot.CoopBotEggFsRoboticsR";
}
CSoundParameters soundParams;
if( GetParametersForSound( soundName, soundParams, NULL ) )
{
EmitSound_t emitParams( soundParams );
emitParams.m_flVolume = volume;
emitParams.m_pOrigin = &origin;
CRecipientFilter filter;
filter.UsePredictionRules();
filter.AddRecipientsByPAS( origin );
EmitSound( filter, entindex(), emitParams );
}
}
}
Activity CPortal_Player::TranslateActivity( Activity baseAct, bool *pRequired /* = NULL */ )
{
Activity translated = baseAct;
if ( GetActiveWeapon() )
{
translated = GetActiveWeapon()->ActivityOverride( baseAct, pRequired );
}
else if (pRequired)
{
*pRequired = false;
}
return translated;
}
CWeaponPortalBase* CPortal_Player::GetActivePortalWeapon() const
{
CBaseCombatWeapon *pWeapon = GetActiveWeapon();
if ( pWeapon )
{
return dynamic_cast< CWeaponPortalBase* >( pWeapon );
}
else
{
return NULL;
}
}
const Vector& CPortal_Player::WorldSpaceCenter() const
{
m_vWorldSpaceCenterHolder = GetAbsOrigin();
//m_vWorldSpaceCenterHolder.z += 0.5f * ( (GetFlags() & FL_DUCKING) ? (VEC_DUCK_HULL_MAX.z) : (VEC_HULL_MAX.z) );
m_vWorldSpaceCenterHolder += 0.5f * m_flHullHeight * m_PortalLocal.m_StickNormal;
return m_vWorldSpaceCenterHolder;
}
bool CPortal_Player::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
Assert( ray.m_IsRay );
// Transform the ray into local space (w.r.t. the player hull)
VMatrix worldToLocal = SetupMatrixAxisToAxisRot( m_PortalLocal.m_StickNormal, Vector(0, 0, 1) );
worldToLocal = worldToLocal * SetupMatrixTranslation( -WorldSpaceCenter() );
Ray_t localRay;
const Vector start = worldToLocal * ray.m_Start;
const Vector end = start + worldToLocal.ApplyRotation( ray.m_Delta );
localRay.Init( start, end );
if( IntersectRayWithAACylinder( localRay, vec3_origin, 0.5f * GetHullWidth() * PLAYER_HULL_REDUCTION, GetHullHeight(), &tr ) )
{
// Transform the start, end, and normal of the trace back into world coordinates
// These were changed in the call to IntersectRayWithAACylinder() when it succeeded
const VMatrix localToWorld = worldToLocal.InverseTR();
tr.startpos = localToWorld * tr.startpos;
tr.endpos = localToWorld * tr.endpos;
tr.plane.normal = localToWorld.ApplyRotation( tr.plane.normal );
tr.hitbox = 0;
CStudioHdr *pStudioHdr = GetModelPtr( );
if (!pStudioHdr)
return false;
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( m_nHitboxSet );
if ( !set || !set->numhitboxes )
return false;
mstudiobbox_t *pbox = set->pHitbox( tr.hitbox );
mstudiobone_t *pBone = pStudioHdr->pBone(pbox->bone);
tr.surface.name = "**studio**";
tr.surface.flags = SURF_HITBOX;
tr.surface.surfaceProps = pBone->GetSurfaceProp();
}
return true;
}
float CPortal_Player::GetImplicitVerticalStepSpeed() const
{
return m_flImplicitVerticalStepSpeed;
}
void CPortal_Player::SetImplicitVerticalStepSpeed( float speed )
{
Assert( !IS_NAN( speed ) );
m_flImplicitVerticalStepSpeed = speed;
}
void CPortal_Player::ForceDuckThisFrame( void )
{
if( (GetFlags() & FL_DUCKING) == 0 )
{
EASY_DIFFPRINT( this, "CPortal_Player::ForceDuckThisFrame()" );
SetGroundEntity( NULL );
SetGroundChangeTime( gpGlobals->curtime + 0.5f );
m_Local.m_bDucked = true;
m_Local.m_bInDuckJump = true;
/*if( m_Local.m_nDuckJumpTimeMsecs == 0 )
{
m_Local.m_nDuckJumpTimeMsecs = GAMEMOVEMENT_JUMP_TIME;
}*/
ForceButtons( IN_DUCK );
ForceButtons( IN_JUMP );
AddFlag( FL_DUCKING );
SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX );
SetViewOffset( VEC_DUCK_VIEW );
//#if defined( GAME_DLL )
SetVCollisionState( GetAbsOrigin() + Vector( 0.0f, 0.0f, 18.0f ), GetAbsVelocity(), VPHYS_CROUCH ); //+18 on z to maintain centered state
if( m_pPhysicsController )
{
m_pPhysicsController->SetObject( m_pShadowCrouch );
m_pPhysicsController->Jump();
}
SetTouchedPhysics( true );
//#endif
}
}
const CPortalPlayerLocalData& CPortal_Player::GetPortalPlayerLocalData() const
{
return m_PortalLocal;
}
#if 0
//==========================
// ANIMATION CODE
//==========================
// Below this many degrees, slow down turning rate linearly
#define FADE_TURN_DEGREES 45.0f
// After this, need to start turning feet
#define MAX_TORSO_ANGLE 90.0f
// Below this amount, don't play a turning animation/perform IK
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f
static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." );
extern ConVar sv_backspeed;
extern ConVar mp_feetyawrate;
extern ConVar mp_facefronttime;
extern ConVar mp_ik;
CPlayerAnimState::CPlayerAnimState( CPortal_Player *outer )
: m_pOuter( outer )
{
m_flGaitYaw = 0.0f;
m_flGoalFeetYaw = 0.0f;
m_flCurrentFeetYaw = 0.0f;
m_flCurrentTorsoYaw = 0.0f;
m_flLastYaw = 0.0f;
m_flLastTurnTime = 0.0f;
m_flTurnCorrectionTime = 0.0f;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPlayerAnimState::Update()
{
m_angRender = GetOuter()->GetLocalAngles();
ComputePoseParam_BodyYaw();
ComputePoseParam_BodyPitch( GetOuter()->GetModelPtr() );
ComputePoseParam_BodyLookYaw();
ComputePlaybackRate();
#ifdef CLIENT_DLL
GetOuter()->UpdateLookAt();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPlayerAnimState::ComputePlaybackRate()
{
// Determine ideal playback rate
Vector vel;
GetOuterAbsVelocity( vel );
float speed = vel.Length2D();
bool isMoving = ( speed > 0.5f ) ? true : false;
float maxspeed = GetOuter()->GetSequenceGroundSpeed( GetOuter()->GetSequence() );
if ( isMoving && ( maxspeed > 0.0f ) )
{
float flFactor = 1.0f;
// Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below
GetOuter()->SetPlaybackRate( ( speed * flFactor ) / maxspeed );
// BUG BUG:
// This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed
}
else
{
GetOuter()->SetPlaybackRate( 1.0f );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : CBasePlayer
//-----------------------------------------------------------------------------
CPortal_Player *CPlayerAnimState::GetOuter()
{
return m_pOuter;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : dt -
//-----------------------------------------------------------------------------
void CPlayerAnimState::EstimateYaw( void )
{
float dt = gpGlobals->frametime;
if ( !dt )
{
return;
}
Vector est_velocity;
QAngle angles;
GetOuterAbsVelocity( est_velocity );
angles = GetOuter()->GetLocalAngles();
if ( est_velocity[1] == 0 && est_velocity[0] == 0 )
{
float flYawDiff = angles[YAW] - m_flGaitYaw;
flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360;
if (flYawDiff > 180)
flYawDiff -= 360;
if (flYawDiff < -180)
flYawDiff += 360;
if (dt < 0.25)
flYawDiff *= dt * 4;
else
flYawDiff *= dt;
m_flGaitYaw += flYawDiff;
m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360;
}
else
{
m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI);
if (m_flGaitYaw > 180)
m_flGaitYaw = 180;
else if (m_flGaitYaw < -180)
m_flGaitYaw = -180;
}
}
//-----------------------------------------------------------------------------
// Purpose: Override for backpeddling
// Input : dt -
//-----------------------------------------------------------------------------
void CPlayerAnimState::ComputePoseParam_BodyYaw( void )
{
int iYaw = GetOuter()->LookupPoseParameter( "move_yaw" );
if ( iYaw < 0 )
return;
// view direction relative to movement
float flYaw;
EstimateYaw();
QAngle angles = GetOuter()->GetLocalAngles();
float ang = angles[ YAW ];
if ( ang > 180.0f )
{
ang -= 360.0f;
}
else if ( ang < -180.0f )
{
ang += 360.0f;
}
// calc side to side turning
flYaw = ang - m_flGaitYaw;
// Invert for mapping into 8way blend
flYaw = -flYaw;
flYaw = flYaw - (int)(flYaw / 360) * 360;
if (flYaw < -180)
{
flYaw = flYaw + 360;
}
else if (flYaw > 180)
{
flYaw = flYaw - 360;
}
GetOuter()->SetPoseParameter( iYaw, flYaw );
#ifndef CLIENT_DLL
//Adrian: Make the model's angle match the legs so the hitboxes match on both sides.
GetOuter()->SetLocalAngles( QAngle( GetOuter()->GetAnimEyeAngles().x, m_flCurrentFeetYaw, 0 ) );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr )
{
// Get pitch from v_angle
float flPitch = GetOuter()->GetLocalAngles()[ PITCH ];
if ( flPitch > 180.0f )
{
flPitch -= 360.0f;
}
flPitch = clamp( flPitch, -90, 90 );
QAngle absangles = GetOuter()->GetAbsAngles();
absangles.x = 0.0f;
m_angRender = absangles;
// See if we have a blender for pitch
GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", -flPitch );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : goal -
// maxrate -
// dt -
// current -
// Output : int
//-----------------------------------------------------------------------------
int CPlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current )
{
int direction = TURN_NONE;
float anglediff = goal - current;
float anglediffabs = fabs( anglediff );
anglediff = AngleNormalize( anglediff );
float scale = 1.0f;
if ( anglediffabs <= FADE_TURN_DEGREES )
{
scale = anglediffabs / FADE_TURN_DEGREES;
// Always do at least a bit of the turn ( 1% )
scale = clamp( scale, 0.01f, 1.0f );
}
float maxmove = maxrate * dt * scale;
if ( fabs( anglediff ) < maxmove )
{
current = goal;
}
else
{
if ( anglediff > 0 )
{
current += maxmove;
direction = TURN_LEFT;
}
else
{
current -= maxmove;
direction = TURN_RIGHT;
}
}
current = AngleNormalize( current );
return direction;
}
void CPlayerAnimState::ComputePoseParam_BodyLookYaw( void )
{
QAngle absangles = GetOuter()->GetAbsAngles();
absangles.y = AngleNormalize( absangles.y );
m_angRender = absangles;
// See if we even have a blender for pitch
int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" );
if ( upper_body_yaw < 0 )
{
return;
}
// Assume upper and lower bodies are aligned and that we're not turning
float flGoalTorsoYaw = 0.0f;
int turning = TURN_NONE;
float turnrate = 360.0f;
Vector vel;
GetOuterAbsVelocity( vel );
bool isMoving = ( vel.Length() > 1.0f ) ? true : false;
if ( !isMoving )
{
// Just stopped moving, try and clamp feet
if ( m_flLastTurnTime <= 0.0f )
{
m_flLastTurnTime = gpGlobals->curtime;
m_flLastYaw = GetOuter()->GetAnimEyeAngles().y;
// Snap feet to be perfectly aligned with torso/eyes
m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y;
m_flCurrentFeetYaw = m_flGoalFeetYaw;
m_nTurningInPlace = TURN_NONE;
}
// If rotating in place, update stasis timer
if ( m_flLastYaw != GetOuter()->GetAnimEyeAngles().y )
{
m_flLastTurnTime = gpGlobals->curtime;
m_flLastYaw = GetOuter()->GetAnimEyeAngles().y;
}
if ( m_flGoalFeetYaw != m_flCurrentFeetYaw )
{
m_flLastTurnTime = gpGlobals->curtime;
}
turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw );
QAngle eyeAngles = GetOuter()->GetAnimEyeAngles();
QAngle vAngle = GetOuter()->GetLocalAngles();
// See how far off current feetyaw is from true yaw
float yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;
yawdelta = AngleNormalize( yawdelta );
bool rotated_too_far = false;
float yawmagnitude = fabs( yawdelta );
// If too far, then need to turn in place
if ( yawmagnitude > 45 )
{
rotated_too_far = true;
}
// Standing still for a while, rotate feet around to face forward
// Or rotated too far
// FIXME: Play an in place turning animation
if ( rotated_too_far ||
( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) )
{
m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y;
m_flLastTurnTime = gpGlobals->curtime;
/* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw;
if ( yd > 0 )
{
m_nTurningInPlace = TURN_RIGHT;
}
else if ( yd < 0 )
{
m_nTurningInPlace = TURN_LEFT;
}
else
{
m_nTurningInPlace = TURN_NONE;
}
turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw );
yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;*/
}
// Snap upper body into position since the delta is already smoothed for the feet
flGoalTorsoYaw = yawdelta;
m_flCurrentTorsoYaw = flGoalTorsoYaw;
}
else
{
m_flLastTurnTime = 0.0f;
m_nTurningInPlace = TURN_NONE;
m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y;
flGoalTorsoYaw = 0.0f;
m_flCurrentTorsoYaw = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;
}
if ( turning == TURN_NONE )
{
m_nTurningInPlace = turning;
}
if ( m_nTurningInPlace != TURN_NONE )
{
// If we're close to finishing the turn, then turn off the turning animation
if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION )
{
m_nTurningInPlace = TURN_NONE;
}
}
// Rotate entire body into position
absangles = GetOuter()->GetAbsAngles();
absangles.y = m_flCurrentFeetYaw;
m_angRender = absangles;
GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) );
/*
// FIXME: Adrian, what is this?
int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" );
if ( body_yaw >= 0 )
{
GetOuter()->SetPoseParameter( body_yaw, 30 );
}
*/
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : activity -
// Output : Activity
//-----------------------------------------------------------------------------
Activity CPlayerAnimState::BodyYawTranslateActivity( Activity activity )
{
// Not even standing still, sigh
if ( activity != ACT_IDLE )
return activity;
// Not turning
switch ( m_nTurningInPlace )
{
default:
case TURN_NONE:
return activity;
/*
case TURN_RIGHT:
return ACT_TURNRIGHT45;
case TURN_LEFT:
return ACT_TURNLEFT45;
*/
case TURN_RIGHT:
case TURN_LEFT:
return mp_ik.GetBool() ? ACT_TURN : activity;
}
Assert( 0 );
return activity;
}
const QAngle& CPlayerAnimState::GetRenderAngles()
{
return m_angRender;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CPlayerAnimState::Teleport( Vector *pOldOrigin, QAngle *pOldAngles )
{
QAngle absangles = GetOuter()->GetAbsAngles();
absangles.x = 0.0f;
m_angRender = absangles;
m_flCurrentFeetYaw = m_flGoalFeetYaw = m_flLastYaw = m_angRender.y;
m_flLastTurnTime = 0.0f;
m_nTurningInPlace = TURN_NONE;
}
void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel )
{
#if defined( CLIENT_DLL )
GetOuter()->EstimateAbsVelocity( vel );
#else
vel = GetOuter()->GetAbsVelocity();
#endif
}
#endif // #if 0
bool CPortal_Player::IsUsingVMGrab( void )
{
return m_bUsingVMGrabState;
}
bool CPortal_Player::WantsVMGrab( void )
{
if ( player_held_object_use_view_model.GetInt() >= 0 )
{
return player_held_object_use_view_model.GetBool();
}
return m_bUseVMGrab;
}
void CPortal_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis )
{
m_bHeldObjectOnOppositeSideOfPortal = false;
#if !defined CLIENT_DLL
if ( PhysIsInCallback() )
{
variant_t value;
g_EventQueue.AddEvent( this, "ForceDropPhysObjects", value, 0.01f, pOnlyIfHoldingThis, this );
return;
}
#endif
m_bForcingDrop = true;
// Drop any objects being handheld.
ClearUseEntity();
m_bForcingDrop = false;
}
//-----------------------------------------------------------------------------
// 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 );
#if !defined CLIENT_DLL
FireConcept( TLK_PLAYER_PICKED_UP_ITEM );
#endif
}
bool CPortal_Player::IsInvalidHandoff( CBaseEntity *pObject )
{
CBasePlayer *pPlayer = GetPlayerHoldingEntity( pObject );
if ( !pPlayer || pPlayer == this )
{
// No one is holding it now or we are holding it, it's all good
return false;
}
else
{
// Make sure it has a clear shot to the holding player
trace_t tr;
UTIL_TraceLine( pObject->GetAbsOrigin(), pPlayer->EyePosition(), MASK_SOLID, pObject, COLLISION_GROUP_NONE, &tr );
if ( !tr.startsolid && ( tr.fraction >= 1.0f || tr.m_pEnt == pPlayer ) )
{
// Make sure it has a clear shot to the grabbing player
trace_t tr;
UTIL_TraceLine( pObject->GetAbsOrigin(), EyePosition(), MASK_SOLID, pObject, COLLISION_GROUP_NONE, &tr );
if ( !tr.startsolid && ( tr.fraction >= 1.0f || tr.m_pEnt == this ) )
{
return false;
}
else
{
// It's in solid or colliding with something other than the grabbing player
return true;
}
}
else
{
// It's in solid or colliding with something other than the holding player
return true;
}
}
}
// Calls FindUseEntity every tick for a period of time
// I'm REALLY sorry about this. This will clean up quite a bit
// once grab controllers are on the client.
void CPortal_Player::PollForUseEntity( bool bBasicUse, CBaseEntity **ppUseEnt, CPortal_Base2D **ppUseThroughPortal )
{
bool bTryGrab = false;
CBaseEntity *pSendEntity = NULL;
#ifdef CLIENT_DLL
pSendEntity = m_hUseEntToSend.Get();
#endif
if ( !m_bIsHoldingSomething && !pSendEntity && gpGlobals->curtime > m_flAutoGrabLockOutTime + 1.0f )
{
// We're not preventing new grabs... check the auto grab state
if ( bBasicUse || ( GameRules() && GameRules()->IsMultiplayer() && GetAbsVelocity().AsVector2D().Length() > 275.0f ) )
{
// They either just pressed used or are flying fast in multiplayer... mark this time to start auto grabbing
m_flUseKeyStartTime = gpGlobals->curtime;
}
// If they're currently autograbbing, try it out this frame
bTryGrab = ( gpGlobals->curtime < m_flUseKeyStartTime + sv_use_trace_duration.GetFloat() );
}
if ( bTryGrab )
{
// Trace for a use entity
CPortal_Base2D *pThroughPortal = NULL;
*ppUseEnt = FindUseEntity( &pThroughPortal );
*ppUseThroughPortal = pThroughPortal;
if ( *ppUseEnt )
{
bool bIsPhysics = ( (*ppUseEnt)->GetMoveType() == MOVETYPE_VPHYSICS );
if ( bIsPhysics || bBasicUse )
{
if ( bIsPhysics )
{
// Prevent redropping/regrabbing when mashing +use
m_flAutoGrabLockOutTime = gpGlobals->curtime;
}
// You need to be looking in it's general direction to auto grab it
Vector vObjectDir = (*ppUseEnt)->GetAbsOrigin() - EyePosition();
VectorNormalize( vObjectDir );
Vector vEyeForward;
EyeVectors( &vEyeForward );
if ( bBasicUse || vObjectDir.Dot( vEyeForward ) > 0.2f )
{
m_flUseKeyStartTime = -sv_use_trace_duration.GetFloat() - 1.0f;
}
else
{
// You're probably going to smack in to it, don't actually auto grab it
*ppUseEnt = NULL;
}
}
else
{
// Don't autograb non-physics
*ppUseEnt = NULL;
}
}
}
}
CBaseEntity *CPortal_Player::FindUseEntity( CPortal_Base2D **pThroughPortal )
{
Vector forward, up;
EyeVectors( &forward, NULL, &up );
Vector vNetworkPosOffset = vec3_origin;
#ifdef CLIENT_DLL
vNetworkPosOffset = GetNetworkOrigin() - GetAbsOrigin();
#endif
trace_t tr;
// Search for objects in a sphere (tests for entities that are not solid, yet still useable)
Vector searchCenter = EyePosition() + vNetworkPosOffset;
// NOTE: Some debris objects are useable too, so hit those as well
// A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too.
// BUG 61818: Allowing pickup through playerclips because we'd like to be abled to drop through them.
int useableContents = MASK_SOLID | CONTENTS_DEBRIS /*| CONTENTS_PLAYERCLIP*/;
UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr );
// try the hit entity if there is one, or the ground entity if there isn't.
CBaseEntity *pNearest = NULL;
CBaseEntity *pObject = tr.m_pEnt;
#if 1
int count = 0;
// UNDONE: Might be faster to just fold this range into the sphere query
const int NUM_TANGENTS = 10;
while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS)
{
// trace a box at successive angles down
// 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15
// then up (useful in portal when flying past a use target quickly)
// -20 deg, -30 deg, -45 deg
const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f,
-0.3639702342f, -0.57735026919f, -1 };
Vector down = forward - tangents[count]*up;
VectorNormalize(down);
UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr );
pObject = tr.m_pEnt;
count++;
}
#endif
float nearestDot = CONE_90_DEGREES;
if ( IsUseableEntity(pObject, 0) )
{
Vector delta = tr.endpos - tr.startpos;
float centerZ = CollisionProp()->WorldSpaceCenter().z;
delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z );
float dist = delta.Length();
if ( dist < PLAYER_USE_RADIUS && !IsInvalidHandoff( pObject ) )
{
if ( sv_debug_player_use.GetBool() )
{
debugoverlay->AddLineOverlay( searchCenter, tr.endpos, 0, 255, 0, true, 30 );
//FIXME: Supposed to be a cross
Vector boxSize( 4,4,4 );
debugoverlay->AddBoxOverlay( tr.endpos, -boxSize, boxSize, vec3_angle, 255, 0, 0, 255, 30 );
}
return pObject;
}
}
CBaseEntity *pFoundByTrace = pObject;
// check ground entity first
// if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
// otherwise, search out in a 90 degree cone (hemisphere)
if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) )
{
pNearest = GetGroundEntity();
nearestDot = CONE_45_DEGREES;
}
for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
{
if ( !pObject )
continue;
if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) )
continue;
// see if it's more roughly in front of the player than previous guess
Vector point;
pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point );
Vector dir = point - searchCenter;
VectorNormalize(dir);
float dot = DotProduct( dir, forward );
// Need to be looking at the object more or less
if ( dot < sv_player_use_cone_size.GetFloat() )
continue;
if ( IsInvalidHandoff( pObject ) )
continue;
if ( dot > nearestDot )
{
// Since this has purely been a radius search to this point, we now
// make sure the object isn't behind glass or a grate.
trace_t trCheckOccluded;
UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded );
if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject )
{
pNearest = pObject;
nearestDot = dot;
}
}
}
if ( sv_debug_player_use.GetBool() )
{
if ( !pNearest )
{
debugoverlay->AddLineOverlay( searchCenter, tr.endpos, 0, 255, 0, true, 30 );
Vector boxSize( 4,4,4 );
debugoverlay->AddBoxOverlay( tr.endpos, -boxSize, boxSize, vec3_angle, 255, 0, 0, 255, 30 );
}
else if ( pNearest == pFoundByTrace )
{
debugoverlay->AddLineOverlay( searchCenter, tr.endpos, 0, 255, 0, true, 30 );
Vector boxSize( 4,4,4 );
debugoverlay->AddBoxOverlay( tr.endpos, -boxSize, boxSize, vec3_angle, 255, 0, 0, 255, 30 );
}
else
{
Vector boxSize( 8,8,8 );
debugoverlay->AddBoxOverlay( pNearest->WorldSpaceCenter(), -boxSize, boxSize, vec3_angle, 255, 0, 0, 255, 30 );
}
}
if ( pNearest == NULL )
{
Vector forward;
EyeVectors( &forward, NULL, NULL );
Vector start = EyePosition();
Ray_t rayPortalTest;
rayPortalTest.Init( start, start + forward * PLAYER_USE_RADIUS );
float fMustBeCloserThan = 1.0f;
SetHeldObjectPortal( UTIL_Portal_FirstAlongRay( rayPortalTest, fMustBeCloserThan ) );
if ( GetHeldObjectPortal() )
{
pNearest = FindUseEntityThroughPortal();
if ( pNearest )
{
if ( pThroughPortal )
{
*pThroughPortal = GetHeldObjectPortal();
}
}
}
}
return pNearest;
}
CBaseEntity* CPortal_Player::FindUseEntityThroughPortal( void )
{
Vector forward, up;
EyeVectors( &forward, NULL, &up );
CPortal_Base2D *pPortal = GetHeldObjectPortal();
trace_t tr;
// Search for objects in a sphere (tests for entities that are not solid, yet still useable)
Vector searchCenter = EyePosition();
Vector vTransformedForward, vTransformedUp, vTransformedSearchCenter;
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
UTIL_Portal_PointTransform( matThisToLinked, searchCenter, vTransformedSearchCenter );
UTIL_Portal_VectorTransform( matThisToLinked, forward, vTransformedForward );
UTIL_Portal_VectorTransform( matThisToLinked, up, vTransformedUp );
// NOTE: Some debris objects are useable too, so hit those as well
// A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too.
int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP;
//UTIL_TraceLine( vTransformedSearchCenter, vTransformedSearchCenter + vTransformedForward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr );
Ray_t rayLinked;
rayLinked.Init( searchCenter, searchCenter + forward * 1024 );
UTIL_PortalLinked_TraceRay( pPortal, rayLinked, useableContents, this, COLLISION_GROUP_NONE, &tr );
// try the hit entity if there is one, or the ground entity if there isn't.
CBaseEntity *pNearest = NULL;
CBaseEntity *pObject = tr.m_pEnt;
int count = 0;
// UNDONE: Might be faster to just fold this range into the sphere query
const int NUM_TANGENTS = 7;
while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS)
{
// trace a box at successive angles down
// 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15
const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f };
Vector down = vTransformedForward - tangents[count]*vTransformedUp;
VectorNormalize(down);
UTIL_TraceHull( vTransformedSearchCenter, vTransformedSearchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr );
pObject = tr.m_pEnt;
count++;
}
float nearestDot = CONE_90_DEGREES;
if ( IsUseableEntity(pObject, 0) )
{
Vector delta = tr.endpos - tr.startpos;
float centerZ = CollisionProp()->WorldSpaceCenter().z;
delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z );
float dist = delta.Length();
if ( dist < PLAYER_USE_RADIUS && !IsInvalidHandoff( pObject ) )
{
return pObject;
}
}
// check ground entity first
// if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
// otherwise, search out in a 90 degree cone (hemisphere)
if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) )
{
pNearest = GetGroundEntity();
nearestDot = CONE_45_DEGREES;
}
for ( CEntitySphereQuery sphere( vTransformedSearchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
{
if ( !pObject )
continue;
if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) )
continue;
// see if it's more roughly in front of the player than previous guess
Vector point;
pObject->CollisionProp()->CalcNearestPoint( vTransformedSearchCenter, &point );
Vector dir = point - vTransformedSearchCenter;
VectorNormalize(dir);
float dot = DotProduct( dir, vTransformedForward );
// Need to be looking at the object more or less
if ( dot < 0.8 )
continue;
if ( IsInvalidHandoff( pObject ) )
continue;
if ( dot > nearestDot )
{
// Since this has purely been a radius search to this point, we now
// make sure the object isn't behind glass or a grate.
trace_t trCheckOccluded;
UTIL_TraceLine( vTransformedSearchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded );
if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject )
{
pNearest = pObject;
nearestDot = dot;
}
}
}
return pNearest;
}
//////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////
#if defined CLIENT_DLL
bool CPortal_Player::IsPotatosOn( void )
{
return m_bPotatos;
}
#endif
//////////////////////////////////////////////////////////////////////////
// PAINT SECTION
//////////////////////////////////////////////////////////////////////////
bool CPortal_Player::WantsToSwapGuns( void )
{
return m_bWantsToSwapGuns;
}
const Vector& CPortal_Player::GetPrevGroundNormal() const
{
return m_vPrevGroundNormal;
}
void CPortal_Player::SetPrevGroundNormal( const Vector& vPrevNormal )
{
m_vPrevGroundNormal = vPrevNormal;
}
float CPortal_Player::PredictedAirTimeEnd( void )
{
if ( m_PortalLocal.m_hTractorBeam.Get() )
{
// Flying in a TBeam!
return 4.0f;
}
float fSeconds = 0.0f;
if ( GetGroundEntity() == NULL )
{
#ifdef CLIENT_DLL
Vector vPos = GetNetworkOrigin();
#else
Vector vPos = GetAbsOrigin();
#endif
trace_t tr;
tr.fraction = 1.0f;
const float fStep = 0.4f;
for ( float i = 0.0f; i < 4.0f && tr.fraction >= 1.0f; i += fStep )
{
Vector vEndPos = vPos + ( GetAbsVelocity() * fStep - Vector( 0.0f, 0.0f, sv_gravity.GetFloat() * i ) );
UTIL_TraceEntity( this, vPos, vEndPos, MASK_PLAYERSOLID, &tr );
fSeconds += tr.fraction * fStep;
vPos = vEndPos;
}
return fSeconds;
}
else
{
return 0.0f;
}
}
float CPortal_Player::PredictedBounce( void )
{
float fSeconds = 0.0f;
if ( m_PortalLocal.m_hTractorBeam.Get() || GetAirTime() < 1.0f )
{
// We haven't been falling long enough
m_PlayerAnimState->m_fPrevBouncePredict = 4.0f;
return m_PlayerAnimState->m_fPrevBouncePredict;
}
if ( m_PlayerAnimState->m_fPrevBouncePredict > 1.0f && gpGlobals->curtime < m_PlayerAnimState->m_fNextBouncePredictTime )
{
// We predicted too recently and aren't near a bounce, reuse the last value we calculated
return m_PlayerAnimState->m_fPrevBouncePredict;
}
// Repredict only 5 times per second
m_PlayerAnimState->m_fNextBouncePredictTime = gpGlobals->curtime + 0.2f;
#ifdef CLIENT_DLL
Vector vPos = GetNetworkOrigin();
#else
Vector vPos = GetAbsOrigin();
#endif
trace_t tr;
tr.fraction = 1.0f;
const float fStep = 0.4f;
for ( float i = 0.0f; i < 4.0f && tr.fraction >= 1.0f; i += fStep )
{
Vector vEndPos = vPos + ( GetAbsVelocity() * fStep - Vector( 0.0f, 0.0f, sv_gravity.GetFloat() * i ) );
UTIL_TraceEntity( this, vPos, vEndPos, MASK_PLAYERSOLID, &tr );
fSeconds += tr.fraction * fStep;
vPos = vEndPos;
}
fSeconds -= tr.fraction * fStep;
bool bTouched = false;
float fLeastFraction = tr.fraction;
Ray_t ray;
ray.Init( tr.startpos, vPos, GetHullMins(), GetHullMaxs() );
for ( int i = 0; i < ITriggerCatapultAutoList::AutoList().Count(); ++i )
{
CTriggerCatapult *pCatapult = static_cast< CTriggerCatapult* >( ITriggerCatapultAutoList::AutoList()[ i ] );
trace_t trTemp;
enginetrace->ClipRayToEntity( ray, MASK_SHOT, pCatapult, &trTemp );
if ( trTemp.startsolid )
{
fLeastFraction = 0.0f;
tr = trTemp;
bTouched = true;
break;
}
else if ( trTemp.fraction < 1.0f || trTemp.m_pEnt == pCatapult )
{
if ( fLeastFraction >= trTemp.fraction )
{
fLeastFraction = trTemp.fraction;
tr = trTemp;
bTouched = true;
}
}
}
fSeconds += tr.fraction * fStep;
if ( bTouched )
{
m_PlayerAnimState->m_fPrevBouncePredict = fSeconds;
return m_PlayerAnimState->m_fPrevBouncePredict;
}
m_PlayerAnimState->m_fPrevBouncePredict = 4.0f;
return m_PlayerAnimState->m_fPrevBouncePredict;
}
void CPortal_Player::SharedSpawn()
{
BaseClass::SharedSpawn();
SetCollisionBounds( GetHullMins(), GetHullMaxs() );
SetMaxSpeed( sv_speed_normal.GetFloat() );
if( PAINTED_SURFACE_PROPERTY_INDEX == -1 )
PAINTED_SURFACE_PROPERTY_INDEX = physprops->GetSurfaceIndex( "painted_surface" );
}
const char *CPortal_Player::GetPlayerModelName( void )
{
const char *pszCurrentModelName = g_pszPlayerModel;
#if defined( PORTAL2_PUZZLEMAKER )
// Reconstruct the community map number from its component pieces
extern ConVar cm_current_community_map;
const uint64 unCommunityMapId = (uint64) atol(cm_current_community_map.GetString());
char szFixedMapName[MAX_PATH];
#ifdef CLIENT_DLL
const char *pszMapName = engine->GetLevelNameShort();
#else
const char *pszMapName = gpGlobals->mapname.ToCStr();
#endif
V_strncpy( szFixedMapName, pszMapName, ARRAYSIZE( szFixedMapName ) );
V_FixSlashes( szFixedMapName );
bool bPuzzleMakerMap = !V_strnicmp( szFixedMapName, "puzzlemaker\\", V_strlen("puzzlemaker\\") );
#endif // PORTAL2_PUZZLEMAKER
if ( GameRules()->IsMultiplayer() )
{
#if !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
int iBot = ( GetTeamNumber() == TEAM_BLUE ) ? P2BOT_ATLAS : P2BOT_PBODY;
m_bIsBendy = false;
// See if we have a misc item equipped that modifies our skin
CEconItemView *pItem = m_Inventory.GetItemInLoadout( iBot, LOADOUT_POSITION_SKIN );
if ( pItem && pItem->IsValid() )
{
if ( !Q_strcmp( pItem->GetStaticData()->GetItemClass(), "skin" ) )
return pItem->GetPlayerDisplayModel(iBot);
}
#endif
if ( GetTeamNumber() == TEAM_BLUE )
{
pszCurrentModelName = GetBallBotModel();
}
else
{
pszCurrentModelName = GetEggBotModel();
}
}
#if defined( PORTAL2_PUZZLEMAKER )
else if ( ( unCommunityMapId != 0 || bPuzzleMakerMap ) && sv_use_bendy_model.GetBool() )
{
pszCurrentModelName = "models/info_character/info_character_player.mdl";
m_bIsBendy = true;
}
#endif
return pszCurrentModelName;
}
void CPortal_Player::UpdatePaintedPower()
{
#ifndef CLIENT_DLL
if( player_loses_painted_power_over_time.GetBool() &&
m_PortalLocal.m_PaintedPowerTimer.IsElapsed() )
{
CleansePaint();
}
#else // elif defined( CLIENT_DLL )
// If there's still time remaining
if( !m_PortalLocal.m_PaintedPowerTimer.IsElapsed() )
{
// Update paint screen effect if this player is in any split screen view
FOR_EACH_VALID_SPLITSCREEN_PLAYER( nSlot )
{
if( C_BasePlayer::GetLocalPlayer( nSlot ) == this && !m_PaintScreenSpaceEffect.IsValid() )
{
MDLCACHE_CRITICAL_SECTION();
m_PaintScreenSpaceEffect = ParticleProp()->Create( PAINT_SCREEN_EFFECT, PATTACH_CUSTOMORIGIN );
if( m_PaintScreenSpaceEffect.IsValid() )
{
// Make sure the particle system isn't automatically drawn with the viewmodel
m_PaintScreenSpaceEffect->m_pDef->SetDrawThroughLeafSystem( false );
m_PaintScreenSpaceEffect->m_pDef->m_nMaxParticles = 512;
// Set the control points
ParticleProp()->AddControlPoint( m_PaintScreenSpaceEffect, 1, this, PATTACH_CUSTOMORIGIN );
m_PaintScreenSpaceEffect->SetControlPoint( 0, GetAbsOrigin() );
m_PaintScreenSpaceEffect->SetControlPoint( 1, GetAbsOrigin() );
m_PaintScreenSpaceEffect->SetControlPointEntity( 0, this );
m_PaintScreenSpaceEffect->SetControlPointEntity( 1, this );
}
// Only do this once
break;
}
}
// commenting out the 3rd person drop effect
/*
// Update paint drip effect
if( m_PaintDripEffect.IsValid() )
{
m_PaintDripEffect->SetNeedsBBoxUpdate( true );
m_PaintDripEffect->SetSortOrigin( GetAbsOrigin() );
}
else
{
MDLCACHE_CRITICAL_SECTION();
m_PaintDripEffect = ParticleProp()->Create( PAINT_DRIP_EFFECT, PATTACH_ABSORIGIN_FOLLOW );
if( m_PaintDripEffect.IsValid() )
{
ParticleProp()->AddControlPoint( m_PaintDripEffect, 1, this, PATTACH_ABSORIGIN_FOLLOW );
m_PaintDripEffect->SetControlPoint( 0, GetAbsOrigin() );
m_PaintDripEffect->SetControlPoint( 1, GetAbsOrigin() );
m_PaintDripEffect->SetControlPointEntity( 0, this );
m_PaintDripEffect->SetControlPointEntity( 1, this );
}
}
*/
}
// The painted power wore off
else
{
InvalidatePaintEffects();
}
#endif // ifndef CLIENT_DLL
}
void CPortal_Player::UpdateAirInputScaleFadeIn()
{
if( !IsInactivePower( GetPaintPower( BOUNCE_POWER ) ) )
{
const bool shouldReturnFullInput = IsDeactivatingPower( GetPaintPower( BOUNCE_POWER ) ) && GetGroundEntity() != NULL;
m_PortalLocal.m_flAirInputScale = shouldReturnFullInput ? 1.0f : clamp( m_PortalLocal.m_flAirInputScale + gpGlobals->frametime / BOUNCE_PAINT_INPUT_DAMP_TIME, 0.0f, 1.0f );
}
else if( !IsInactivePower( GetPaintPower( SPEED_POWER ) ) )
{
//const bool doneDeactivating = IsDeactivatingPower( GetPaintPower( SPEED_POWER ) ) && MaxSpeed() == sv_speed_normal.GetFloat() && GetGroundEntity() != NULL;
//m_PortalLocal.m_flAirInputScale = doneDeactivating ? 1.0f : clamp( m_PortalLocal.m_flAirInputScale + gpGlobals->frametime / SPEED_PAINT_AIR_INPUT_DAMP_TIME, 0.0f, 1.0f );
// HACK: For the playtest, if speed and only speed is not in the inactive state,
// the player has limited air control.
m_PortalLocal.m_flAirInputScale = GetGroundEntity() ? 1.0f : MIN_SPEED_PAINT_AIR_INPUT_START_DAMPING;
}
else
{
m_PortalLocal.m_flAirInputScale = 1.0f;
}
//float inputScale = m_PortalLocal.m_flAirInputScale;
//DevMsg( "Input Scale: %f\n", inputScale );
}
void CPortal_Player::UpdateInAirState()
{
const InAirState state = m_PortalLocal.m_InAirState;
// The player jumped
if( JumpedThisFrame() )
m_PortalLocal.m_InAirState = IN_AIR_JUMPED;
// The player bounced
if( BouncedThisFrame() )
m_PortalLocal.m_InAirState = IN_AIR_BOUNCED;
// The player is on the ground
if( GetGroundEntity() )
m_PortalLocal.m_InAirState = ON_GROUND;
// If the player was on the ground but suddenly isn't and didn't jump or bounce
if( state == ON_GROUND && GetGroundEntity() == NULL && !JumpedThisFrame() )
{
//EASY_DIFFPRINT( this, "CPortal_Player::UpdateInAirState() player fell" );
// The player fell
m_PortalLocal.m_InAirState = IN_AIR_FELL;
}
// Reset whether the player jumped or bounced this frame
SetJumpedThisFrame( false );
SetBouncedThisFrame( false );
}
void CPortal_Player::CachePaintPowerChoiceResults( const PaintPowerChoiceResultArray& choiceInfo )
{
int const count = MIN( PAINT_POWER_TYPE_COUNT, choiceInfo.Count() );
if( count == 0 )
{
for( int i = 0; i < PAINT_POWER_TYPE_COUNT; ++i )
{
m_PortalLocal.m_CachedPaintPowerChoiceResults[i].Initialize();
}
}
else
{
for( int i = 0; i < count; ++i )
{
bool const wasValid = choiceInfo[i].pPaintPower != NULL;
m_PortalLocal.m_CachedPaintPowerChoiceResults[i].wasValid = wasValid;
m_PortalLocal.m_CachedPaintPowerChoiceResults[i].wasIgnored = choiceInfo[i].bWasIgnored;
if( wasValid )
{
m_PortalLocal.m_CachedPaintPowerChoiceResults[i].surfaceEntity = choiceInfo[i].pPaintPower->m_HandleToOther;
m_PortalLocal.m_CachedPaintPowerChoiceResults[i].surfaceNormal = choiceInfo[i].pPaintPower->m_SurfaceNormal;
}
}
}
}
bool CPortal_Player::LateSuperJumpIsValid() const
{
return jump_helper_enabled.GetBool() &&
IsBouncePower( m_CachedJumpPower ) &&
gpGlobals->curtime - m_flCachedJumpPowerTime < jump_helper_late_jump_max_time.GetFloat();
}
StickCameraState CPortal_Player::GetStickCameraState() const
{
return m_PortalLocal.m_nStickCameraState;
}
Vector CPortal_Player::Weapon_ShootPosition()
{
return EyePosition();
}
const Vector& CPortal_Player::GetInputVector() const
{
return m_vInputVector;
}
void CPortal_Player::SetInputVector( const Vector& vInput )
{
m_vInputVector = vInput;
}
PaintPowerType CPortal_Player::GetPaintPowerAtPoint( const Vector& worldContactPt ) const
{
return m_PortalLocal.m_PaintedPowerType;
}
void CPortal_Player::Paint( PaintPowerType type, const Vector& worldContactPt )
{
if( player_can_use_painted_power.GetBool() || player_paint_effects_enabled.GetBool() )
{
#ifdef CLIENT_DLL
// Invalidate if the timer ran out or this is a different paint type
if( m_PortalLocal.m_PaintedPowerTimer.IsElapsed() || type != m_PortalLocal.m_PaintedPowerType )
{
InvalidatePaintEffects();
}
// If this is the same paint type, the timer hasn't elapsed, and the restart cooldown timer has, restart
else if( m_PaintScreenEffectCooldownTimer.IsElapsed() )
{
// Restart paint screen space effect
if( m_PaintScreenSpaceEffect.IsValid() )
{
m_PaintScreenSpaceEffect->Restart( RESTART_RESET_AND_MAKE_SURE_EMITS_HAPPEN );
}
// commenting out the 3rd person drop effect
/*
// Restart paint drip effect
if( m_PaintDripEffect.IsValid() )
{
m_PaintDripEffect->Restart( RESTART_RESET_AND_MAKE_SURE_EMITS_HAPPEN );
}
*/
m_PaintScreenEffectCooldownTimer.Start( player_paint_screen_effect_restart_delay.GetFloat() );
}
#endif
if( player_can_use_painted_power.GetBool() )
BaseClass::Paint( type, worldContactPt );
m_PortalLocal.m_PaintedPowerType = type;
m_PortalLocal.m_PaintedPowerTimer.Start( player_paint_effects_duration.GetFloat() );
}
}
void CPortal_Player::CleansePaint()
{
BaseClass::CleansePaint();
m_PortalLocal.m_PaintedPowerType = NO_POWER;
m_PortalLocal.m_PaintedPowerTimer.Invalidate();
#ifdef CLIENT_DLL
m_PaintScreenEffectCooldownTimer.Invalidate();
#endif
}
// Paint power debug
void CPortal_Player::DrawJumpHelperDebug( PaintPowerConstIter begin, PaintPowerConstIter end, float duration, bool noDepthTest, const PaintPowerInfo_t* pSelected ) const
{
const Color bounceColor(255, 255, 0);
const Color otherColor( 255, 255, 255 );
const Color selectedColor( 255, 0, 255 );
for( PaintPowerConstIter i = begin; i != end; ++i )
{
const PaintPowerInfo_t& powerInfo = *i;
DrawPaintPowerContactInfo( powerInfo, (i == pSelected) ? selectedColor : ( IsBouncePower( powerInfo ) ? bounceColor : otherColor ), duration, noDepthTest );
}
}
inline bool IsBetterPaintPowerVelocityInputScore( float flCurrentBestInputCos,
float flCurrentBestVelocityCos,
float flQueryInputCos,
float flQueryVelocityCos )
{
return (flCurrentBestInputCos - flQueryInputCos > EQUAL_EPSILON && fabs(flQueryInputCos) > DEAD_INPUT_COS) || // Pushing most towards this one and pushing toward it significantly
(flCurrentBestInputCos >= 0 && flCurrentBestVelocityCos - flQueryInputCos > EQUAL_EPSILON ); // Not pushing toward anything yet, but moving most toward this one
}
void CPortal_Player::ChooseBestPaintPowersInRange( PaintPowerChoiceResultArray& bestPowers,
PaintPowerConstIter begin,
PaintPowerConstIter end,
const PaintPowerChoiceCriteria_t& choiceCriteria ) const
{
// Note: Valid input and velocity cosines should all be in the range [-1, 1), but we don't really want anything close to 1.
// Theoretically, 1.0f would be an appropriate default value. However, in the case where the player jumps from a standstill
// (no WASD input), she will not get a power on the bottom of a projected wall because we don't know about the contact until
// after she starts falling, making the velocity cosine precisely 1.0f. Cases like this are the only reason I'm making the
// default value 2.0f here. Feel free to change it if this is no longer the case. The input cosine default remains unchanged
// because it should always reflect the actual input intentions. If its value is undesirable, velocity takes over.
// -Ted
bestPowers.EnsureCount( PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER );
PaintPowerChoiceResult_t bestIgnoredPowers[PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER];
for( unsigned i = 0; i < PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER; ++i )
{
bestPowers[i].Initialize();
bestIgnoredPowers[i].Initialize();
}
const Vector& vStickNormal = m_PortalLocal.m_StickNormal;
// Iterate through all the surfaces to see if there's a more appropriate power to use
for( PaintPowerConstIter iter = begin; iter != end; ++iter )
{
const PaintPowerInfo_t& powerInfo = *iter;
const unsigned powerIndex = powerInfo.m_PaintPowerType;
AssertMsg( powerIndex < PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER, "Invalid paint power may cause out-of-bounds array access!" );
// How much the player is pushing toward this surface and how different the normal is, respectively
const float flInputCos = DotProduct( choiceCriteria.vNormInputDir, powerInfo.m_SurfaceNormal );
const float flVelocityCos = DotProduct( choiceCriteria.vNormVelocity, powerInfo.m_SurfaceNormal );
if( show_player_paint_power_debug.GetBool() )
{
DevMsg( "Paint Power #%d\n", iter - begin );
PrintPowerInfoDebugMsg( powerInfo );
DevMsg( "\n" );
DevMsg( "Input Cosine: %f\n", flInputCos );
DevMsg( "Velocity Cosine: %f\n", flVelocityCos );
DevMsg("------------------------------\n");
}
// If this is the best choice so far
if( powerIndex < PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER &&
IsBetterPaintPowerVelocityInputScore( bestPowers[powerIndex].flInputCos,
bestPowers[powerIndex].flVelocityCos,
flInputCos,
flVelocityCos ) )
{
bool ignorePower = false;
// Special handling if you're in a portal environment
if( choiceCriteria.bInPortal )
{
const Vector& portalNormal = m_hPortalEnvironment.Get()->m_plane_Origin.normal;
const Vector& powerNormal = powerInfo.m_SurfaceNormal;
const float normCosAngle = DotProduct( portalNormal, powerNormal );
const float inputCosAngle = DotProduct( portalNormal, choiceCriteria.vNormInputDir);
const bool nearPortal = UTIL_PointIsNearPortal( powerInfo.m_ContactPoint, m_hPortalEnvironment, PORTAL_PLANE_IGNORE_EPSILON, 0.5f * GetHullWidth() - 5.0f );
const bool aboutParallel = CloseEnough( normCosAngle, 1.0f );
const bool pushingTowardPortal = inputCosAngle + RELATIVE_ERROR < 0;
// If it's no power on a portal surface, it doesn't matter
ignorePower |= nearPortal && IsNoPower( powerInfo ) && aboutParallel;
// If it's bounce or speed, the contact point is near the portal, and the normals are parallel or player is moving toward the portal.
ignorePower |= nearPortal &&
( IsBouncePower( powerInfo ) || IsSpeedPower( powerInfo ) ) &&
( aboutParallel || pushingTowardPortal );
}
// If the the player already has a power which is neither stick nor is on the ground that she's moving (but not pushing) toward and this
// is no power, just keep the power she has already.
//ignorePower |= IsNoPower( powerInfo ) && pBestPower != 0 &&
// (!IsStickPower( *pBestPower ) || pBestPower->m_SurfaceNormal.z >= STEEP_SLOPE) &&
// fBestInputCos >= 0.0f && fBestVelocityCos < 0.0f;
// Don't let speed paint on surfaces the player isn't standing on affect her, unless she's trying to leave stick paint and go
// to a speed surface.
const bool walkingOffStick = vStickNormal.z < STEEP_SLOPE && powerInfo.m_SurfaceNormal.z > STEEP_SLOPE;
ignorePower |= !walkingOffStick && IsSpeedPower( powerInfo ) && DotProduct( vStickNormal, powerInfo.m_SurfaceNormal ) < STEEP_SLOPE;
// For trampoline bounce, ignore surfaces the player isn't trying to use, so if the player pushes into a wall
// she can't use, it doesn't take priority over the ground she's falling onto. This doesn't change anything
// in any other bounce mode, since the power will never activate if the player isn't trying to use it.
ignorePower |= !walkingOffStick && IsBouncePower( powerInfo ) && !IsTryingToSuperJump( &powerInfo );
// Update the current best power
PaintPowerChoiceResult_t& result = ignorePower ? bestIgnoredPowers[powerIndex] : bestPowers[powerIndex];
// Make sure that this is actually better than the power to overwrite
// Note: This is to ensure the ignored powers are consistent but have no effect on the actual best powers
if( !ignorePower || IsBetterPaintPowerVelocityInputScore( result.flInputCos, result.flVelocityCos, flInputCos, flVelocityCos ) )
{
result.pPaintPower = iter;
result.flInputCos = flInputCos;
result.flVelocityCos = flVelocityCos;
result.bWasIgnored = ignorePower;
}
}
}
// If any of the best powers were never set, replace it with the best ignored power of the same type
for( unsigned i = 0; i < PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER; ++i )
{
if( bestPowers[i].pPaintPower == NULL )
bestPowers[i] = bestIgnoredPowers[i];
}
}
//-----------------------------------------------------------------------------
// Paint Power User Implementation
//-----------------------------------------------------------------------------
void CPortal_Player::ChooseActivePaintPowers( PaintPowerInfoVector& activePowers )
{
// Get all world contacts
DeterminePaintContacts();
// Cached stick power and new power
PaintPowerChoiceResultArray bestTouchedPowers;
PaintPowerConstRange touchedPowerRange = GetSurfacePaintPowerInfo();
// Criteria to choose the best power
PaintPowerChoiceCriteria_t choiceCriteria;
choiceCriteria.bInPortal = m_hPortalEnvironment != 0;
// If the player is touching anything
if( HasAnySurfacePaintPowerInfo() || LateSuperJumpIsValid() )
{
// Figure out colors/powers
MapSurfacesToPowers();
// Sort the surfaces by priority
PrioritySortSurfacePaintPowerInfo( &DescendingPaintPriorityCompare );
// Compute the absolute velocity with the change due to gravity
const Vector& vStickNormal = m_PortalLocal.m_StickNormal;
const float gravityScale = (GetGravity() != 0) ? GetGravity() : 1.0f;
const float gravityMagnitude = -(gravityScale * sv_gravity.GetFloat() * gpGlobals->frametime);
const Vector dvStickGravity = gravityMagnitude * vStickNormal;
choiceCriteria.vNormVelocity = (GetAbsVelocity() + dvStickGravity).Normalized();
// The normalized input direction
choiceCriteria.vNormInputDir = GetInputVector().Normalized();
const char *dllString;
#ifdef CLIENT_DLL
dllString = "Client";
#else
dllString = "Server";
#endif
if( show_player_paint_power_debug.GetBool() )
{
DevMsg( "DLL: %s\n", dllString );
DevMsg( "Norm Input: (%f, %f, %f)\n", XYZ( choiceCriteria.vNormInputDir ) );
DevMsg( "Abs Velocity: (%f, %f, %f)\n", XYZ( GetAbsVelocity() ) );
//DevMsg( "Stick Gravity: (%f, %f, %f)\n", XYZ( dvStickGravity ) );
DevMsg( "Norm Velocity: (%f, %f, %f)\n", XYZ( choiceCriteria.vNormVelocity ) );
DevMsg("------------------------------\n");
}
// Get all the paint power info of the surfaces the player is touching
ChooseBestPaintPowersInRange( bestTouchedPowers, touchedPowerRange.first, touchedPowerRange.second, choiceCriteria );
if( show_player_paint_power_debug.GetBool() )
{
DevMsg( "Context: Touched\n" );
DevMsg( "Paint Power Count: %d\n", touchedPowerRange.second - touchedPowerRange.first );
DevMsg("------------------------------\n");
}
// Trying to jump and jump helper is enabled
const bool bJumpHelperEnabled = jump_helper_enabled.GetBool();
const bool bIsPressingJumpKey = IsPressingJumpKey();
const PaintPowerInfo_t* pBestLookAheadSuperJumpPower = 0;
PaintPowerConstRange jumpHelperLookAheadPowerRange = GetSurfacePaintPowerInfo( JUMP_HELPER_CONTEXT );
if( bJumpHelperEnabled )
{
// Check the look ahead list for the super jump power
PaintPowerChoiceResultArray bestJumpHelperLookAheadPowers;
ChooseBestPaintPowersInRange( bestJumpHelperLookAheadPowers, jumpHelperLookAheadPowerRange.first, jumpHelperLookAheadPowerRange.second, choiceCriteria );
const PaintPowerInfo_t* pBestTouchedSuperJumpPower = bestTouchedPowers[BOUNCE_POWER].pPaintPower;
pBestLookAheadSuperJumpPower = bestJumpHelperLookAheadPowers[BOUNCE_POWER].pPaintPower;
if( bIsPressingJumpKey )
{
if( show_player_paint_power_debug.GetBool() )
{
DevMsg( "Context: %s\n", JUMP_HELPER_CONTEXT );
DevMsg( "Paint Power Count: %d\n", touchedPowerRange.second - touchedPowerRange.first );
DevMsg("------------------------------\n");
}
// Didn't get a touched super jump
if( !pBestTouchedSuperJumpPower )
{
// Got a predicted super jump
if( pBestLookAheadSuperJumpPower && IsBouncePower( *pBestLookAheadSuperJumpPower ) )
{
if( GetGroundEntity() )
{
// Early jump from the ground
bestTouchedPowers[BOUNCE_POWER].pPaintPower = pBestLookAheadSuperJumpPower;
bestTouchedPowers[BOUNCE_POWER].bWasIgnored = false;
}
else
{
// Early jump from the air
m_flPredictedJumpTime = gpGlobals->curtime;
}
}
// Got a late super jump
else if( LateSuperJumpIsValid() )
{
// Use the late super jump
bestTouchedPowers[BOUNCE_POWER].pPaintPower = &m_CachedJumpPower;
bestTouchedPowers[BOUNCE_POWER].bWasIgnored = false;
m_flCachedJumpPowerTime = -FLT_MAX;
}
}
}
}
// Jump helper debug draw
if( jump_helper_debug_enabled.GetBool() )
{
const float duration = bIsPressingJumpKey ? 5.0f : 0.0f;
DrawJumpHelperDebug( jumpHelperLookAheadPowerRange.first, jumpHelperLookAheadPowerRange.second, duration, false, bestTouchedPowers[BOUNCE_POWER].pPaintPower );
// Late hit was available
if( LateSuperJumpIsValid() )
{
if( bestTouchedPowers[BOUNCE_POWER].pPaintPower == &m_CachedJumpPower )
DrawPaintPowerContactInfo( m_CachedJumpPower, Color( 255, 0, 255 ), duration, false );
else if( IsBouncePower( m_CachedJumpPower ) )
DrawPaintPowerContactInfo( m_CachedJumpPower, Color( 255, 0, 0 ), duration, false );
}
// If we got a new bounce power, draw it in case it was a surface we actually touched.
if( bestTouchedPowers[BOUNCE_POWER].pPaintPower )
DrawPaintPowerContactInfo( *bestTouchedPowers[BOUNCE_POWER].pPaintPower, Color( 255, 0, 255 ), duration, false );
if( bestTouchedPowers[BOUNCE_POWER].pPaintPower )
{
IHandleEntity* pHandleEnt = bestTouchedPowers[BOUNCE_POWER].pPaintPower->m_HandleToOther.Get();
CBaseEntity* pBaseEnt = pHandleEnt ? EntityFromEntityHandle( pHandleEnt ) : 0;
char const* entName;
entName = pBaseEnt ? pBaseEnt->GetClassname() : "";
if( bestTouchedPowers[BOUNCE_POWER].pPaintPower == pBestLookAheadSuperJumpPower )
DevMsg( "Early jump off of %s!\n", entName );
else if( bestTouchedPowers[BOUNCE_POWER].pPaintPower == &m_CachedJumpPower )
DevMsg( "Late jump off of %s!\n", entName );
else if( IsTryingToSuperJump( bestTouchedPowers[BOUNCE_POWER].pPaintPower ) )
DevMsg( "Punctual jump off of %s!\n", entName );
}
}
}
// Play transition sounds of walking onto/off of paint
PlayPaintSounds( bestTouchedPowers );
// Set the new powers if we found one
for( int i = 0; i < bestTouchedPowers.Count(); ++i )
{
const PaintPowerInfo_t* pNewPower = bestTouchedPowers[i].pPaintPower;
if( pNewPower )
{
//#ifndef CLIENT_DLL
//// Only send the event if the powers are actually different
//if( AreDifferentPowers( *pNewPower, GetPaintPower( pNewPower->m_PaintPowerType ) ) )
//{
// IGameEvent *event = gameeventmanager->CreateEvent( "touched_paint" );
// if ( event )
// {
// event->SetInt("userid", GetUserID() );
// event->SetInt( "painttype", pNewPower->m_PaintPowerType );
// PaintSurfaceType surfaceType;
// if( pNewPower->m_SurfaceNormal.z > 0.5f )
// {
// surfaceType = FLOOR_SURFACE;
// }
// else if( pNewPower->m_SurfaceNormal.z < -0.5f )
// {
// surfaceType = CEILING_SURFACE;
// }
// else
// {
// surfaceType = WALL_SURFACE;
// }
// event->SetInt( "surfacedir", surfaceType );
// gameeventmanager->FireEvent( event );
// }
//}
//#endif //!CLIENT_DLL
if( !bestTouchedPowers[i].bWasIgnored )
activePowers.AddToTail( *pNewPower );
}
}
// Cache off the choice results
CachePaintPowerChoiceResults( bestTouchedPowers );
// If the player is paintable and has a painted power, try to use it
if( player_can_use_painted_power.GetBool() && m_PortalLocal.m_PaintedPowerType != NO_POWER )
{
// Copy all the surface data, and replace the powers with the painted power
PaintPowerInfoVector paintedPowerQuery;
paintedPowerQuery.EnsureCapacity( GetCountFromRange( touchedPowerRange ) );
for( PaintPowerConstIter i = touchedPowerRange.first; i != touchedPowerRange.second; ++i )
{
if( i->m_PaintPowerType != INVALID_PAINT_POWER )
{
PaintPowerInfo_t paintedPower = *i;
paintedPower.m_PaintPowerType = m_PortalLocal.m_PaintedPowerType;
paintedPowerQuery.AddToTail( paintedPower );
}
}
// Choose the best surface to use the painted power on
PaintPowerChoiceResultArray bestPaintedPowers;
PaintPowerChoiceCriteria_t paintedChoiceCriteria = choiceCriteria;
ChooseBestPaintPowersInRange( bestPaintedPowers,
GetConstBegin( paintedPowerQuery ),
GetConstEnd( paintedPowerQuery ),
paintedChoiceCriteria );
// If they weren't all ignored, give the player her painted power
if( bestPaintedPowers[m_PortalLocal.m_PaintedPowerType].pPaintPower &&
!bestPaintedPowers[m_PortalLocal.m_PaintedPowerType].bWasIgnored )
{
activePowers.AddToTail( *bestPaintedPowers[m_PortalLocal.m_PaintedPowerType].pPaintPower );
}
}
if( show_player_paint_power_debug.GetBool() )
{
DevMsg( "Active Powers:\n" );
for( int i = 0; i < activePowers.Count(); ++i )
{
DevMsg( "%s Power\n", PowerTypeToString( activePowers[i] ) );
}
DevMsg("==============================\n");
}
}
ConVar sv_paint_trigger_sound_delay( "sv_paint_trigger_sound_delay", "0.1f", FCVAR_REPLICATED );
void CPortal_Player::PlayPaintSounds( const PaintPowerChoiceResultArray& touchedPowers )
{
const char* const soundList[] =
{
"Player.EnterBouncePaint",
"Player.ExitBouncePaint",
"Player.EnterStickPaint",
"Player.ExitStickPaint",
"Player.EnterSpeedPaint",
"Player.ExitSpeedPaint"
};
CRecipientFilter filter;
filter.AddRecipient( this );
bool isTouchingPower[PAINT_POWER_TYPE_COUNT - 1] = { false };
isTouchingPower[BOUNCE_POWER] = BOUNCE_POWER < touchedPowers.Count() &&
touchedPowers[BOUNCE_POWER].pPaintPower != NULL;
isTouchingPower[SPEED_POWER] = SPEED_POWER < touchedPowers.Count() &&
touchedPowers[SPEED_POWER].pPaintPower != NULL &&
!touchedPowers[SPEED_POWER].bWasIgnored;
// Go through all usable powers and check if we just stepped onto or off of it
for( int i = 0; i < PAINT_POWER_TYPE_COUNT - 1; ++i )
{
// If we're on this power
if( isTouchingPower[i] )
{
// Just stepping onto this power
if( m_flTimeSinceLastTouchedPower[i] > sv_paint_trigger_sound_delay.GetFloat() )
{
EmitSound( filter, this->entindex(), soundList[i*2] );
}
// Reset timer
m_flTimeSinceLastTouchedPower[i] = 0.f;
}
// Not on this power
else
{
// Stepping off of this power. Use a delay so we don't deactivate when we very briefly lose a power
if( m_flTimeSinceLastTouchedPower[i] < sv_paint_trigger_sound_delay.GetFloat() &&
m_flTimeSinceLastTouchedPower[i] + gpGlobals->frametime >= sv_paint_trigger_sound_delay.GetFloat() )
{
EmitSound( filter, this->entindex(), soundList[i*2+1] );
}
// Increase timer
m_flTimeSinceLastTouchedPower[i] += gpGlobals->frametime;
}
}
}
bool CPortal_Player::IsUsingPostTeleportationBox() const
{
return m_flUsePostTeleportationBoxTime > 0.0f;
}
const Vector& CPortal_Player::GetHullMins() const
{
return IsObserver() ? VEC_OBS_HULL_MIN : ( GetFlags() & FL_DUCKING ? GetDuckHullMins() : GetStandHullMins() );
}
const Vector& CPortal_Player::GetHullMaxs() const
{
return IsObserver() ? VEC_OBS_HULL_MAX : ( GetFlags() & FL_DUCKING ? GetDuckHullMaxs() : GetStandHullMaxs() );
}
const Vector CPortal_Player::GetPlayerMins() const
{
return GetHullMins();
}
const Vector CPortal_Player::GetPlayerMaxs() const
{
return GetHullMaxs();
}
const Vector& CPortal_Player::GetStandHullMins() const
{
return m_PortalLocal.m_StandHullMin;
}
const Vector& CPortal_Player::GetStandHullMaxs() const
{
return m_PortalLocal.m_StandHullMax;
}
const Vector& CPortal_Player::GetDuckHullMins() const
{
return m_PortalLocal.m_DuckHullMin;
}
const Vector& CPortal_Player::GetDuckHullMaxs() const
{
return m_PortalLocal.m_DuckHullMax;
}
void CPortal_Player::UpdateCollisionBounds()
{
SetCollisionBounds( GetHullMins(), GetHullMaxs() );
}
inline float ComputeHullHeight( const Vector& localUp, const Vector& hullMins, const Vector& hullMaxs )
{
return fabs( DotProduct( localUp, hullMaxs - hullMins ) );
}
float CPortal_Player::GetHullHeight() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullHeight( localUp, GetHullMins(), GetHullMaxs() );
}
float ComputeHullWidth( const Vector& localUp, const Vector& hullMins, const Vector& hullMaxs )
{
Vector diffInPlane = hullMaxs - hullMins;
diffInPlane -= DotProduct( localUp, diffInPlane ) * localUp;
return fabs( diffInPlane[ diffInPlane.LargestComponent() ] );
}
float CPortal_Player::GetHullWidth() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullWidth( localUp, GetHullMaxs(), GetHullMins() );
}
float CPortal_Player::GetStandHullHeight() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullHeight( localUp, GetStandHullMins(), GetStandHullMaxs() );
}
float CPortal_Player::GetStandHullWidth() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullWidth( localUp, GetStandHullMaxs(), GetStandHullMins() );
}
float CPortal_Player::GetDuckHullHeight() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullHeight( localUp, GetDuckHullMins(), GetDuckHullMaxs() );
}
float CPortal_Player::GetDuckHullWidth() const
{
const Vector& localUp = m_PortalLocal.m_bAttemptHullResize ? static_cast<Vector>( m_PortalLocal.m_OldStickNormal ) : static_cast<Vector>( m_PortalLocal.m_StickNormal );
return ComputeHullWidth( localUp, GetDuckHullMaxs(), GetDuckHullMins() );
}
void CPortal_Player::AddSurfacePaintPowerInfo( const BrushContact& contact, char const* context )
{
AddSurfacePaintPowerInfo( PaintPowerInfo_t( contact.normal,
contact.point,
contact.pBrushEntity,
NO_POWER,
contact.isOnThinSurface ),
context );
}
void CPortal_Player::AddSurfacePaintPowerInfo( const trace_t& trace, char const* context )
{
if( trace.m_pEnt )
{
if( trace.m_pEnt->IsBSPModel() )
{
const float DIM_OFFSET = sv_contact_region_thickness.GetFloat();
const Vector AABB_OFFSET( DIM_OFFSET, DIM_OFFSET, DIM_OFFSET );
// Compute the contacts with a slightly expanded swept AABB
const Vector traceDelta = trace.endpos - trace.startpos;
const Vector contactBoxMin = GetAbsOrigin() + traceDelta + WorldAlignMins() - AABB_OFFSET,
contactBoxMax = GetAbsOrigin() + traceDelta + WorldAlignMaxs() + AABB_OFFSET;
const cplane_t *pClip = (m_hPortalEnvironment != 0 && sv_clip_contacts_to_portals.GetBool()) ? &m_hPortalEnvironment.Get()->m_plane_Origin : NULL;
const int iClipCount = pClip ? 1 : 0;
ContactVector contacts;
ComputeAABBContactsWithBrushEntity( contacts, pClip, iClipCount, WorldSpaceCenter(), contactBoxMin, contactBoxMax, trace.m_pEnt );
// Fill in the surface power info vector
const int contactCount = contacts.Count();
for( int i = 0; i < contactCount; ++i )
{
AddSurfacePaintPowerInfo( contacts[i], context );
}
}
else if( trace.plane.normal != vec3_origin )
{
AddSurfacePaintPowerInfo( PaintPowerInfo_t( trace.plane.normal,
UTIL_ProjectPointOntoPlane( trace.endpos, trace.plane ),
trace.m_pEnt ),
context );
}
}
}
void CPortal_Player::DeterminePaintContacts()
{
if( GetMoveType() == MOVETYPE_NOCLIP )
{
return;
}
const float DIM_OFFSET = sv_contact_region_thickness.GetFloat();
const Vector AABB_OFFSET( DIM_OFFSET, DIM_OFFSET, DIM_OFFSET );
// Compute the contacts with a slightly expanded swept AABB
Vector contactBoxMin = GetAbsOrigin() + WorldAlignMins() - AABB_OFFSET,
contactBoxMax = GetAbsOrigin() + WorldAlignMaxs() + AABB_OFFSET;
const Vector traceBoxMin = contactBoxMin, traceBoxMax = contactBoxMax;
// Compute the displacement and use it to sweep the box
const Vector displacement = gpGlobals->frametime * m_PortalLocal.m_vPreUpdateVelocity;
//ExpandAABB( contactBoxMin, contactBoxMax, displacement );
// HACK: Sweep the box ahead an extra frame while in the air to catch the ground early for
// trampoline/reflect bounce, so that the velocity doesn't get zeroed out before we can use it.
float traceLookAheadFactor = 1.0f;
if( ( sv_press_jump_to_bounce.GetInt() == TRAMPOLINE_BOUNCE || sv_bounce_reflect_enabled.GetBool() ) &&
GetGroundEntity() == NULL )
{
traceLookAheadFactor = 2.0f;
ExpandAABB( contactBoxMin, contactBoxMax, 2.0f * displacement );
}
//NDebugOverlay::Box( Vector(0, 0, 0), contactBoxMin, contactBoxMax, 0, 0, 255, 1, 0 );
//NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 1, 0 );
// Find contacts with the world
#ifdef CLIENT_DLL
CWorld* pWorld = GetClientWorldEntity();
#else
CWorld* pWorld = GetWorldEntity();
#endif
cplane_t *pClip = (m_hPortalEnvironment != 0 && sv_clip_contacts_to_portals.GetBool()) ? &m_hPortalEnvironment.Get()->m_plane_Origin : NULL;
int iClipCount = pClip ? 1 : 0;
ContactVector contacts;
ComputeAABBContactsWithBrushEntity( contacts, pClip, iClipCount, WorldSpaceCenter(), contactBoxMin, contactBoxMax, pWorld );
// Fill in the surface power info vector
int contactCount = contacts.Count();
for( int i = 0; i < contactCount; ++i )
{
AddSurfacePaintPowerInfo( contacts[i] );
}
// Find contacts with non-world entities
{
const Vector& center = WorldSpaceCenter();
trace_t trace;
TracePlayerBoxAgainstCollidables( trace, this, center, center + traceLookAheadFactor * displacement, traceBoxMin - center, traceBoxMax - center );
if( trace.DidHitNonWorldEntity() )
{
AddSurfacePaintPowerInfo( trace );
}
}
// Look ahead for powers the player may want to use
if( jump_helper_enabled.GetBool() || jump_helper_debug_enabled.GetBool() )
{
PredictPaintContacts( contactBoxMin, contactBoxMax, traceBoxMin, traceBoxMax, jump_helper_look_ahead_time.GetFloat(), JUMP_HELPER_CONTEXT );
}
}
void CPortal_Player::PredictPaintContacts( const Vector& contactBoxMin,
const Vector& contactBoxMax,
const Vector& traceBoxMin,
const Vector& traceBoxMax,
float lookAheadTime,
char const* context )
{
char const** filterBegin = g_pszPredictedPowerIgnoreFilter;
char const** filterEnd = filterBegin + ARRAYSIZE( g_pszPredictedPowerIgnoreFilter );
const Vector& worldSpaceCenter = WorldSpaceCenter();
const Vector lookAheadVector = lookAheadTime * m_PortalLocal.m_vPreUpdateVelocity;
Vector lookAheadDirection = lookAheadVector;
const float totalDistance = lookAheadDirection.NormalizeInPlace();
const int numSamples = static_cast<int>( fpmax( totalDistance * paint_power_look_ahead_sample_density.GetFloat(), 1.0f ) );
const float stepSize = totalDistance / numSamples;
ContactVector contacts;
#ifdef CLIENT_DLL
CWorld* pWorld = GetClientWorldEntity();
#else
CWorld* pWorld = GetWorldEntity();
#endif
cplane_t *pClip = (m_hPortalEnvironment != 0 && sv_clip_contacts_to_portals.GetBool()) ? &m_hPortalEnvironment.Get()->m_plane_Origin : NULL;
int iClipCount = pClip ? 1 : 0;
Vector prevCenter = worldSpaceCenter;
float distance = stepSize;
for( int sample = 0; sample < numSamples; distance += stepSize, ++sample )
{
// Find precise contacts with the world
const Vector offset = distance * lookAheadDirection;
const Vector center = worldSpaceCenter + offset;
ComputeAABBContactsWithBrushEntity( contacts, pClip, iClipCount, center, contactBoxMin + offset, contactBoxMax + offset, pWorld );
int contactCount = contacts.Count();
for( int i = 0; i < contactCount; ++i )
{
AddSurfacePaintPowerInfo( contacts[i], context );
}
contacts.RemoveAll();
// Trace for non-world entities
trace_t trace;
TracePlayerBoxAgainstCollidables( trace, this, prevCenter, center, traceBoxMin - worldSpaceCenter, traceBoxMax - worldSpaceCenter );
if( trace.DidHitNonWorldEntity() &&
std::find_if( filterBegin, filterEnd, StringCompare_t( trace.m_pEnt->GetClassname() ) ) == filterEnd )
{
//DevMsg( "Collided with %s\n", trace.m_pEnt->GetClassname() );
AddSurfacePaintPowerInfo( trace, context );
}
prevCenter = center;
}
}
PaintPowerState CPortal_Player::ActivateSpeedPower( PaintPowerInfo_t& speedInfo )
{
// Speed is initially the walking speed or the current tangential speed, whichever is greater
const Vector& localVelocity = GetLocalVelocity();
const Vector tangentialVelocity = localVelocity - DotProduct( localVelocity, speedInfo.m_SurfaceNormal ) * speedInfo.m_SurfaceNormal;
SetMaxSpeed( clamp( tangentialVelocity.Length(), sv_speed_normal.GetFloat(), sv_speed_paint_max.GetFloat() ) );
m_flSpeedDecelerationTime = 0.0f;
return ACTIVE_PAINT_POWER;
}
PaintPowerState CPortal_Player::UseSpeedPower( PaintPowerInfo_t& speedInfo )
{
// Compute normalized forward direction in tangent plane
const Vector& vInputVec = GetInputVector();
const Vector vWishDirection = vInputVec.Normalized();
const Vector vTangentRight = CrossProduct( vWishDirection, speedInfo.m_SurfaceNormal );
const Vector vNormTangentForward = CrossProduct( speedInfo.m_SurfaceNormal, vTangentRight ).Normalized();
const bool bStrafing = fabs( DotProduct( vWishDirection, Left() ) ) > 0.9f;
// Figure out if the player is moving up a ramp
const float flWorldUpAngle = vNormTangentForward.z;
const bool bMovingUpRamp = flWorldUpAngle > 0 && flWorldUpAngle < 1.0f - RELATIVE_ERROR;
const bool bInAir = GetGroundEntity() == NULL;
// Compute the change in speed this frame
float flAcceleration = bInAir ? 0.0f : ( bMovingUpRamp ? sv_speed_paint_ramp_acceleration.GetFloat() : sv_speed_paint_acceleration.GetFloat() );
flAcceleration *= bStrafing ? sv_speed_paint_straf_accel_scale.GetFloat() : 1.f;
float fldv = gpGlobals->frametime * flAcceleration;
// If the player is drastically changing direction, knock the speed back down.
// If the player is in the air his speed doesn't change
// Otherwise, scale the change in speed by how consistent it is with the current direction
Vector vLocalDirection = GetLocalVelocity();
const float flWishDirSpeed = DotProduct( vLocalDirection, vWishDirection );
const float flSpeed = vLocalDirection.NormalizeInPlace();
const float flWishCos = DotProduct( vWishDirection, vLocalDirection );
const float flSpeedFactor = vInputVec.IsZero() ? -1.0f : flWishCos;
fldv = (flWishCos < 0.0f) ? ( flWishCos * fabs( flSpeed - sv_speed_normal.GetFloat() ) ) : (flSpeedFactor * fldv);
float flDefaultNewMaxSpeed = clamp( MaxSpeed() + fldv,
sv_speed_normal.GetFloat(),
sv_speed_paint_max.GetFloat() );
// Compute the new max speed
float flNewMaxSpeed = SpeedPaintAcceleration( flDefaultNewMaxSpeed,
flSpeed,
flWishCos,
flWishDirSpeed );
// Use the new max speed or the lower bound if the player has stopped moving completely
SetMaxSpeed( GetLocalVelocity().IsZero() ? sv_speed_normal.GetFloat() : flNewMaxSpeed );
return ACTIVE_PAINT_POWER;
}
PaintPowerState CPortal_Player::DeactivateSpeedPower( PaintPowerInfo_t& speedInfo )
{
const float walkSpeed = sv_speed_normal.GetFloat();
const bool onGround = GetGroundEntity() != NULL;
float newMaxSpeed = 0.f;
// Deactivate in the air (and touching something besides speed paint)
if( !onGround && !IsEmptyRange( GetSurfacePaintPowerInfo() ) )
{
const Vector& velocity = GetAbsVelocity();
const Vector tangentialVelocity = velocity - DotProduct( velocity, speedInfo.m_SurfaceNormal ) * speedInfo.m_SurfaceNormal;
newMaxSpeed = tangentialVelocity.Length();
}
// On the ground and not on speed paint or in the air and not touching anything
else
{
if( IsActivePower( GetPaintPower( BOUNCE_POWER ) ) && m_flSpeedDecelerationTime < sv_speed_paint_on_bounce_deceleration_delay.GetFloat() )
{
m_flSpeedDecelerationTime += gpGlobals->frametime;
newMaxSpeed = MaxSpeed();
}
else
{
// Compute new max speed
float flDefaultNewMaxSpeed = MaxSpeed() - gpGlobals->frametime * sv_speed_paint_acceleration.GetFloat();
Vector vLocalDirection = GetLocalVelocity();
const Vector vWishDirection = GetInputVector().Normalized();
const float flWishDirSpeed = DotProduct( vLocalDirection, vWishDirection );
const float flSpeed = vLocalDirection.NormalizeInPlace();
const float flWishCos = DotProduct( vWishDirection, vLocalDirection );
newMaxSpeed = SpeedPaintAcceleration( flDefaultNewMaxSpeed,
flSpeed,
flWishCos,
flWishDirSpeed );
}
}
// Set the max speed. Done deactivating if it's normal walking speed
newMaxSpeed = clamp( newMaxSpeed, walkSpeed, sv_speed_paint_max.GetFloat() );
SetMaxSpeed( newMaxSpeed );
return newMaxSpeed == walkSpeed && onGround ? INACTIVE_PAINT_POWER : DEACTIVATING_PAINT_POWER;
}
float CPortal_Player::SpeedPaintAcceleration( float flDefaultMaxSpeed,
float flSpeed,
float flWishCos,
float flWishDirSpeed ) const
{
// Compute the new max speed and clamp it
float flNewMaxSpeed = 0.f;
// If they're not pushing any movement buttons, then cap their maxspeed at their current speed
if( GetInputVector().IsZero() )
{
flNewMaxSpeed = clamp( flNewMaxSpeed, sv_speed_normal.GetFloat(), flSpeed );
}
// If they're trying to move mostly orthogonal to their velocity then penalize their maxspeed
else if( flWishCos > 0.f && flWishCos < 0.3f )
{
// Cap their speed to a point between the speed they're moving in the direction they're pressing and
// their total speed.
flNewMaxSpeed = MAX( sv_speed_normal.GetFloat(), flWishDirSpeed + 0.3f * (flSpeed - flWishDirSpeed) );
}
else
{
flNewMaxSpeed = flDefaultMaxSpeed;
}
return flNewMaxSpeed;
}
bool CPortal_Player::IsPressingJumpKey() const
{
return ( m_afButtonForced & IN_JUMP ) ? m_bJumpWasPressedWhenForced : ( m_afButtonPressed & IN_JUMP ) != 0;
}
bool CPortal_Player::IsHoldingJumpKey() const
{
return ( m_afButtonForced & IN_JUMP ) ? m_bJumpWasPressedWhenForced : ( m_nButtons & IN_JUMP ) != 0;
}
CEG_NOINLINE bool CPortal_Player::IsTryingToSuperJump( const PaintPowerInfo_t* pInfo ) const
{
if( !pInfo )
return false;
const int superJumpMode = sv_press_jump_to_bounce.GetInt();
CEG_GCV_PRE();
static const int CEG_SPEED_POWER = CEG_GET_CONSTANT_VALUE( PaintSpeedPower );
CEG_GCV_POST();
// For the trampoline bounce mode, the player can bounce if the velocity is significantly toward the surface.
// Note that this condition requires the player be in the air. That is because we must always detect this case
// for ground bounces before she gets a ground entity and her z velocity is zeroed out. Requiring this
// necessary condition prevents misfires while walking on steep slopes, which occur because step code handles
// movement along z, so the velocity is always in the xy-plane, making the velocity appear to point more toward
// the surface than it actually does.
// Long jumps are automatic at sufficient speed, optionally if the player is looking roughly in the direction of movement.
// Optionally, the player can also super jump if the jump key is pressed or won't jump off a wall while simply walking into it.
const Vector& velocity = m_PortalLocal.m_vPreUpdateVelocity;
const float normalVelocity = DotProduct( pInfo->m_SurfaceNormal, velocity );
const bool isWallJumpFromGround = AlmostEqual( DotProduct( pInfo->m_SurfaceNormal, m_PortalLocal.m_StickNormal ), 0.0f );
const bool isInAir = GetGroundEntity() == NULL;
const bool canTrampolineBounceOffWall = trampoline_bounce_off_walls_while_on_ground.GetBool() || isInAir;
const bool spaceBarActivatedTrampolineJump = IsPressingJumpKey() && jump_button_can_activate_trampoline_bounce.GetBool();
const bool canAutoLongJump = MaxSpeed() > bounce_auto_trigger_min_speed.GetFloat() &&
!IsActivatingPower( GetPaintPower( CEG_SPEED_POWER ) ) &&
!isInAir &&
( !look_dependent_auto_long_jump_enabled.GetBool() || DotProduct( Forward(), velocity.Normalized() ) >= look_dependent_auto_long_jump_min_cos_angle.GetFloat() );
const bool canTrampolineBounce = ( superJumpMode == TRAMPOLINE_BOUNCE ) &&
( ( normalVelocity < -trampoline_bounce_min_impact_speed.GetFloat() && ( isInAir || pInfo->m_SurfaceNormal.z <= 0.0f ) ) ||
( spaceBarActivatedTrampolineJump ) ||
( canAutoLongJump ) ) &&
( !isWallJumpFromGround || canTrampolineBounceOffWall || spaceBarActivatedTrampolineJump );
// The conditions for the jump mode are satisfied
const bool jumpConditionsSatisfied = ( superJumpMode == JUMP_ON_TOUCH ) ||
( superJumpMode == PRESS_JUMP_TO_BOUNCE && IsPressingJumpKey() ) ||
( superJumpMode == HOLD_JUMP_TO_BOUNCE && IsHoldingJumpKey() ) ||
( canTrampolineBounce );
// Should perform a late super jump
const bool jumpHelperShouldLateJump = jumpConditionsSatisfied && LateSuperJumpIsValid();
// If they're legitimately jumping, or performing a late jump but not crouching.
return (jumpConditionsSatisfied || jumpHelperShouldLateJump);
}
void CPortal_Player::SetJumpedThisFrame( bool jumped )
{
if( jumped != m_PortalLocal.m_bJumpedThisFrame )
{
EASY_DIFFPRINT( this, "CPortal_Player::SetJumpedThisFrame() %s", jumped ? "true" : "false" );
}
m_PortalLocal.m_bJumpedThisFrame = jumped;
}
bool CPortal_Player::JumpedThisFrame() const
{
return m_PortalLocal.m_bJumpedThisFrame;
}
void CPortal_Player::SetBouncedThisFrame( bool bounced )
{
m_PortalLocal.m_bBouncedThisFrame = bounced;
}
bool CPortal_Player::BouncedThisFrame() const
{
return m_PortalLocal.m_bBouncedThisFrame;
}
InAirState CPortal_Player::GetInAirState() const
{
return m_PortalLocal.m_InAirState;
}
// Extracted and noinlined to give us a standalone callsite to protect
CEG_NOINLINE void UpdatePaintZ( Vector &velocity, float inGravity )
{
// Find the height the player will go at the current velocity
// 0 = v0 + g * t
// t = -v0 / g - time for gravity to zero velocity, assuming g < 0
// h = v * t + 0.5 * g * t^2 - height reached
const float gravityScale = inGravity != 0 ? inGravity : 1.0f;
const float gravity = -gravityScale * sv_gravity.GetFloat();
const float time = -velocity.z / gravity;
const float baseHeight = velocity.z * time + 0.5f * gravity * time * time;
// Compute how fast to go to reach the same height plus the height boost
// h = 0.5 * g * t^2 - distance traveled with linear accel (target height)
// t = sqrt(2.0 * h / g) - how long to fall h units
// v = g * t - velocity at the end (just invert it to jump up that high)
// v = g * sqrt(2.0 * h / g)
// v^2 = g * g * 2.0 * h / g
// v = sqrt(g * 2.0 * h)
const float targetHeight = baseHeight + bounce_ledge_fall_height_boost.GetFloat();
velocity.z = sqrt( 2 * -gravity * targetHeight ); // Negate gravity so the value is valid
}
#ifdef CLIENT_DLL
CEG_PROTECT_FUNCTION( UpdatePaintZ );
#endif
bool CPortal_Player::CheckToUseBouncePower( PaintPowerInfo_t& info )
{
// Crouching opts out of using bounce powers
const bool bIsCrouching = m_Local.m_bDucking || GetFlags() & FL_DUCKING || m_PortalLocal.m_bPreventedCrouchJumpThisFrame;
const bool bShouldJump = IsTryingToSuperJump( &info );
#ifndef CLIENT_DLL
// They're going to suppress an auto-bounce
if( bIsCrouching && bShouldJump && gpGlobals->curtime - m_flLastSuppressedBounceTime > 1.f )
{
m_flLastSuppressedBounceTime = gpGlobals->curtime;
IGameEvent *event = gameeventmanager->CreateEvent("player_suppressed_bounce");
if( event )
{
event->SetInt("userid", GetUserID() );
gameeventmanager->FireEvent( event );
}
}
#endif
// Trying to use super jump
if( bShouldJump && !bIsCrouching )
{
#ifndef CLIENT_DLL
//IGameEvent *event = gameeventmanager->CreateEvent( "player_paint_jumped" );
//if ( event )
//{
// event->SetInt("userid", GetUserID() );
// gameeventmanager->FireEvent( event );
//}
if( info.m_SurfaceNormal.z > 0.9f )
{
// Reset the bounce count if this is a different surface than last time
const float planeDistance = info.m_ContactPoint.z;
if( fabs( planeDistance - m_LastGroundBouncePlaneDistance ) > sv_stepsize.GetFloat() )
m_nBounceCount = 0;
m_LastGroundBouncePlaneDistance = planeDistance;
++m_nBounceCount;
IGameEvent *event = gameeventmanager->CreateEvent("bounce_count");
if( event )
{
event->SetInt("userid", GetUserID() );
event->SetInt("bouncecount", m_nBounceCount );
gameeventmanager->FireEvent( event );
}
}
EmitSound( "Player.JumpPowerUse" );
#endif //!CLIENT_DLL
m_PortalLocal.m_flAirInputScale = BOUNCE_PAINT_INPUT_DAMPING;
// In the air now.
SetBouncedThisFrame( true );
SetGroundEntity( NULL );
// Compute velocity in the normal direction
Vector velocity = m_PortalLocal.m_vPreUpdateVelocity;
const float normalSpeed = DotProduct( velocity, info.m_SurfaceNormal );
if ( sv_debug_bounce_reflection.GetBool() )
{
NDebugOverlay::VertArrow( info.m_ContactPoint - 50.f * velocity.Normalized(), info.m_ContactPoint, 2, 255, 0, 0, 64, true, sv_debug_bounce_reflection_time.GetFloat() );
}
// If the reflected normal velocity will be larger than the minimum bounce speed
if ( ( sv_bounce_reflect_enabled.GetBool() || sv_press_jump_to_bounce.GetInt() == TRAMPOLINE_BOUNCE ) &&
bounce_reflect_restitution.GetFloat() * fabs( normalSpeed ) >= bounce_paint_min_speed.GetFloat() )
{
// Reflect the velocity
velocity = ComputeBouncePostVelocityReflection( velocity, info.m_SurfaceNormal, m_PortalLocal.m_StickNormal );
}
else
{
// Otherwise, perform a jump
velocity = ComputeBouncePostVelocityNoReflect( velocity, info.m_SurfaceNormal, m_PortalLocal.m_StickNormal );
}
if ( sv_debug_bounce_reflection.GetBool() )
{
NDebugOverlay::VertArrow( info.m_ContactPoint, info.m_ContactPoint + 50.f * info.m_SurfaceNormal, 2, 255, 255, 0, 64, true, sv_debug_bounce_reflection_time.GetFloat() );
NDebugOverlay::VertArrow( info.m_ContactPoint, info.m_ContactPoint + 50.f * m_PortalLocal.m_StickNormal, 2, 0, 0, 255, 64, true, sv_debug_bounce_reflection_time.GetFloat() );
NDebugOverlay::VertArrow( info.m_ContactPoint, info.m_ContactPoint + 50.f * velocity.Normalized(), 2, 0, 255, 0, 64, true, sv_debug_bounce_reflection_time.GetFloat() );
}
const bool bounceSurfaceIsGround = AlmostEqual( info.m_SurfaceNormal.z, 1.0f );
// If the player jumped from the ground
if( GetInAirState() == ON_GROUND && bounceSurfaceIsGround )
{
// Add in some bonus lateral velocity for a jump off the ground
Vector vLateralVelocity = velocity;
vLateralVelocity.z = 0;
Vector vMaxLateralVel = vLateralVelocity.Normalized() * (sv_speed_normal.GetFloat() + sv_speed_normal.GetFloat() * sv_bounce_paint_forward_velocity_bonus.GetFloat());
// If our velocity is lower than is allowed with the lateral bonus
if( velocity.Length2DSqr() < vMaxLateralVel.Length2DSqr() )
{
Vector vBonusLateral = vLateralVelocity * sv_bounce_paint_forward_velocity_bonus.GetFloat();
// Clip extra velocity that would make us go too fast
if( (vBonusLateral + velocity).Length2DSqr() > vMaxLateralVel.Length2DSqr() )
{
vBonusLateral.x = vMaxLateralVel.x - velocity.x;
vBonusLateral.y = vMaxLateralVel.y - velocity.y;
}
velocity += vBonusLateral;
}
}
// Add some height if the player fell off a ledge onto the ground
if( GetInAirState() == IN_AIR_FELL && bounceSurfaceIsGround )
{
UpdatePaintZ( velocity, GetGravity() );
}
SetAbsVelocity( velocity );
if ( info.m_SurfaceNormal.z > 0.75f )
{
OnBounced();
}
return true;
}
return false;
}
PaintPowerState CPortal_Player::ActivateBouncePower( PaintPowerInfo_t& info )
{
// Not dead
bool bUsedPower = false;
if ( !pl.deadflag )
{
bUsedPower = CheckToUseBouncePower( info );
}
return bUsedPower ? DEACTIVATING_PAINT_POWER : ACTIVE_PAINT_POWER;
}
PaintPowerState CPortal_Player::UseBouncePower( PaintPowerInfo_t& info )
{
bool bUsedPower = false;
if( !pl.deadflag )
{
bUsedPower = CheckToUseBouncePower( info );
}
return bUsedPower ? DEACTIVATING_PAINT_POWER : ACTIVE_PAINT_POWER;
}
PaintPowerState CPortal_Player::DeactivateBouncePower( PaintPowerInfo_t& info )
{
// TODO: Fix late jumps. This needs to happen on the first frame of deactivation
//if( jump_helper_enabled.GetBool() )
//{
// // Cache off this jump in case of a late hit
// m_flCachedJumpPowerTime = gpGlobals->curtime;
// m_CachedJumpPower = info;
//}
// Bring input scale back to 1
return IsEmptyRange( GetSurfacePaintPowerInfo() ) ? INACTIVE_PAINT_POWER : DEACTIVATING_PAINT_POWER;
}
void CPortal_Player::OnBounced( float fTimeOffset )
{
m_PortalLocal.m_fBouncedTime = gpGlobals->curtime + fTimeOffset;
}
void CPortal_Player::RecomputeBoundsForOrientation()
{
// Recompute local min, max based on stick normal.
// Note: The stick normal is in world space, but since the player AABB doesn't
// transform with the player (the problem this solves), it's OK to use it
// as though it's in local space or world space.
const Vector& currentStickNormal = m_PortalLocal.m_StickNormal;
VMatrix rotation;
MatrixBuildRotation( rotation, Vector(0, 0, 1), currentStickNormal );
// Rotate the local space hull offsets
Vector xformedStandMin = rotation.ApplyRotation( VEC_HULL_MIN );
Vector xformedStandMax = rotation.ApplyRotation( VEC_HULL_MAX );
Vector xformedDuckMin = rotation.ApplyRotation( VEC_DUCK_HULL_MIN );
Vector xformedDuckMax = rotation.ApplyRotation( VEC_DUCK_HULL_MAX );
// Make sure the local origin is at the "bottom" of the hull by offsetting the mins and maxes by the
// distance along the stick normal from the local origin to the mins
const Vector standOffset = DotProduct( xformedStandMin, currentStickNormal ) * currentStickNormal;
const Vector duckOffset = DotProduct( xformedDuckMin, currentStickNormal ) * currentStickNormal;
xformedStandMin -= standOffset;
xformedStandMax -= standOffset;
xformedDuckMin -= duckOffset;
xformedDuckMax -= duckOffset;
// Recompute min, max in each dimension
const Vector newStandHullMin = xformedStandMin.Min( xformedStandMax );
const Vector newStandHullMax = xformedStandMin.Max( xformedStandMax );
const Vector newDuckHullMin = xformedDuckMin.Min( xformedDuckMax );
const Vector newDuckHullMax = xformedDuckMin.Max( xformedDuckMax );
// Attempt to update the collision bounds
m_PortalLocal.m_bAttemptHullResize = true;
TryToChangeCollisionBounds( newStandHullMin, newStandHullMax, newDuckHullMin, newDuckHullMax );
}
void CPortal_Player::TryToChangeCollisionBounds( const Vector& newStandHullMin,
const Vector& newStandHullMax,
const Vector& newDuckHullMin,
const Vector& newDuckHullMax )
{
// If the bounds haven't already been changed
if( m_PortalLocal.m_bAttemptHullResize )
{
// Trace to find the new abs origin, placing it at the feet
#ifdef CLIENT_DLL
CTraceFilterSimple traceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT );
#else
CTraceFilterSimple baseFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT );
CTraceFilterTranslateClones traceFilter( &baseFilter );
#endif
const Vector& currentStickNormal = m_PortalLocal.m_StickNormal;
const float halfHeight = 0.5f * ComputeHullHeight( m_PortalLocal.m_OldStickNormal, GetHullMins(), GetHullMaxs() );
const Vector oldCenter = GetAbsOrigin() + halfHeight * m_PortalLocal.m_OldStickNormal;
Ray_t ray;
ray.Init( oldCenter, oldCenter - halfHeight * currentStickNormal );
trace_t trace;
UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace, true );
const Vector newAbsOrigin = trace.endpos /*+ EQUAL_EPSILON * currentStickNormal*/;
// Trace to see if the new box fits where it needs to
const bool isDucking = (GetFlags() & FL_DUCKING) != 0;
const Vector& traceHullMin = isDucking ? newDuckHullMin : newStandHullMin;
const Vector& traceHullMax = isDucking ? newDuckHullMax : newStandHullMax;
ray.Init( newAbsOrigin, newAbsOrigin, traceHullMin, traceHullMax );
UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace, true );
// If the trace is fine
if( !trace.DidHit() )
{
// Give ourselves a punch offset, since our origin just snapped a noticeable amount
SetEyeOffset( GetAbsOrigin(), newAbsOrigin );
#ifdef GAME_DLL
// Apply the changes to the abs origin
Teleport( &newAbsOrigin, NULL, NULL );
#endif
// Apply the changes to the hull
m_PortalLocal.m_StandHullMin = newStandHullMin;
m_PortalLocal.m_StandHullMax = newStandHullMax;
m_PortalLocal.m_DuckHullMin = newDuckHullMin;
m_PortalLocal.m_DuckHullMax = newDuckHullMax;
// Apply the changes to collision bounds
SetCollisionBounds( GetHullMins(), GetHullMaxs() );
#ifdef GAME_DLL
InitVCollision( newAbsOrigin, GetAbsVelocity() );
#endif
// Don't attempt to resize again
m_PortalLocal.m_bAttemptHullResize = false;
}
// If the trace hit something
else
{
// Cache off the new hull and try again later
m_PortalLocal.m_CachedStandHullMinAttempt = newStandHullMin;
m_PortalLocal.m_CachedStandHullMaxAttempt = newStandHullMax;
m_PortalLocal.m_CachedDuckHullMinAttempt = newDuckHullMin;
m_PortalLocal.m_CachedDuckHullMaxAttempt = newDuckHullMax;
m_PortalLocal.m_bAttemptHullResize = true;
}
}
}
CBaseCombatWeapon* CPortal_Player::Weapon_OwnsThisType( const char *pszWeapon, int iSubType ) const
{
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
CBaseCombatWeapon* pWeapon = GetWeapon(i);
if ( pWeapon && FClassnameIs( pWeapon, pszWeapon ) )
{
// Make sure it matches the subtype
return pWeapon;
}
}
return NULL;
}
void CPortal_Player::SelectItem( const char *pstr, int iSubType )
{
if (!pstr)
return;
CBaseCombatWeapon *pItem = Weapon_OwnsThisType( pstr, iSubType );
if (!pItem)
return;
if( GetObserverMode() != OBS_MODE_NONE )
return;// Observers can't select things.
if ( !Weapon_ShouldSelectItem( pItem ) )
{
//always set subtype when select weapon to fix weird red color not being set
GetActiveWeapon()->SetSubType( iSubType );
return;
}
// FIX, this needs to queue them up and delay
// Make sure the current weapon can be holstered
if ( GetActiveWeapon() )
{
if ( !GetActiveWeapon()->CanHolster() )
return;
ResetAutoaim( );
}
Weapon_Switch( pItem );
}
void CPortal_Player::Touch( CBaseEntity *pOther )
{
#ifndef CLIENT_DLL
// Check for doors closing on the player
if ( GameRules()->IsMultiplayer() )
{
CBoneFollower *pBoneFollower = dynamic_cast< CBoneFollower* >( pOther );
if ( pBoneFollower )
{
CPropTestChamberDoor *pDoor = dynamic_cast< CPropTestChamberDoor* >( pBoneFollower->GetOwnerEntity() );
if ( pDoor && pDoor->IsClosed() )
{
// Make sure the door is closing on them, and they're not just touching the outside
Ray_t ray;
ray.Init( GetAbsOrigin(), GetAbsOrigin(), GetHullMins(), GetHullMaxs() );
trace_t tr;
bool bHit = IntersectRayWithOBB( ray, pDoor->GetAbsOrigin(), pDoor->GetAbsAngles(), Vector( -4.0f, -64.0f, 0.0f ), Vector( 4.0f, 64.0f, 128.0f ), 0.0f, &tr );
if ( bHit )
{
// In multiplayer we CRUSH them, cause it's funny
CTakeDamageInfo dmgInfo( GetWorldEntity(), GetWorldEntity(), vec3_origin, vec3_origin, 1000, DMG_CRUSH );
dmgInfo.SetDamageForce( Vector( 0, 0, -1 ) );
dmgInfo.SetDamagePosition( GetAbsOrigin() );
TakeDamage( dmgInfo );
}
}
}
}
#endif
BaseClass::Touch( pOther );
}
float CPortal_Player::GetReorientationProgress() const
{
Vector vUp = m_PortalLocal.m_Up;
Vector vStickUp = m_PortalLocal.m_vLocalUp;
vUp.NormalizeInPlace();
vStickUp.NormalizeInPlace();
float fDot = DotProduct( vUp , vStickUp );
fDot = clamp( fDot, 0.f, 1.f );
return fDot;
}
bool CPortal_Player::IsDoneReorienting() const
{
const QAngle& qPunch = m_PortalLocal.m_qQuaternionPunch;
// To be considered done reorienting we have to not be pitching,
// not be rotating our up vector, be fully reoriented, and not have any
// quaternion punch going on
return m_PortalLocal.m_bDoneCorrectPitch &&
m_PortalLocal.m_bDoneStickInterp &&
CloseEnough( GetReorientationProgress(), 1.f ) &&
qPunch.LengthSqr() == 0.f;
}
float AngleBetween(Vector up, Vector forward)
{
float flDot = DotProduct(up,forward);
float flAngle = RAD2DEG(acos(flDot));
return flAngle;
}
void CPortal_Player::PostTeleportationCameraFixup( const CPortal_Base2D *pEnteredPortal )
{
CPortal_Base2D *pExitPortal = pEnteredPortal->m_hLinkedPortal;
if( !pExitPortal )
return;
// Get transformed up
Vector vTransformedUp, vTransformedForward, vForward, vUp;
AngleVectors( pl.v_angle, &vForward, NULL, &vUp );
VectorRotate( m_PortalLocal.m_Up, pEnteredPortal->m_matrixThisToLinked.As3x4(), vTransformedUp );
const Vector vForwardCrossAbsUp = CrossProduct( vForward, ABS_UP );
const Vector vForwardCrossViewUp = CrossProduct( vForward, vUp );
// Dont bother if the vectors are the same (wall<->wall or ceiling<->floor portal)
if( CloseEnough( vTransformedUp, m_PortalLocal.m_Up ) == false )
{
bool bLookingInBadDirection = false;
const float flRightCrossNewRight = DotProduct( vForwardCrossAbsUp, vForwardCrossViewUp );
// Check if our up vector is more upside down than rightside up
if( flRightCrossNewRight < 0.f )
{
bLookingInBadDirection = true;
}
m_PortalLocal.m_Up = vTransformedUp;
// Let SnapCamera mess with our up vector
SnapCamera( STICK_CAMERA_PORTAL, bLookingInBadDirection );
}
}
//#define DEBUG_STICK_CAM
void CPortal_Player::Reorient( QAngle& viewAngles )
{
DecayEyeOffset();
DecayQuaternionPunch();
QAngle oldViewAngles = viewAngles;
// Get vectors out of our angles
Vector vForward, vUp, vRight;
AngleVectors( oldViewAngles, &vForward, &vRight, NULL );
vUp = m_PortalLocal.m_Up;
RotateUpVector( vForward, vUp ); // Reorient the player
m_PortalLocal.m_Up = vUp;
// use up vector as our up when sticking on to new surface
{
QAngle newViewAngles;
VectorAngles( vForward, vUp, newViewAngles );
QAngle viewOffset( 0, 0, 0 );
UTIL_NormalizedAngleDiff( oldViewAngles, newViewAngles, &viewOffset );
// round angles to 0 if they are too small
for ( int i=0; i<3; ++i )
{
if ( fabs( viewOffset[i] ) < 0.001f )
{
viewOffset[i] = 0.f;
}
}
viewAngles = oldViewAngles + viewOffset;
#ifdef DEBUG_STICK_CAM
#ifdef GAME_DLL
engine->Con_NPrintf( 1, "%f, %f, %f", XYZ(vUp) );
engine->Con_NPrintf( 2, "%f, %f, %f", XYZ( Vector(m_PortalLocal.m_vEyeOffset) ) );
engine->Con_NPrintf( 3, "%f, %f, %f", XYZ( GetLocalOrigin() ) );
QAngle quatAngles = m_PortalLocal.m_qQuaternionPunch;
engine->Con_NPrintf( 4, "%f, %f, %f", quatAngles.x, quatAngles.y, quatAngles.z );
#else
engine->Con_NPrintf( 6, "%f, %f, %f", XYZ(vUp) );
engine->Con_NPrintf( 7, "%f, %f, %f", XYZ( Vector(m_PortalLocal.m_vEyeOffset) ) );
engine->Con_NPrintf( 8, "%f, %f, %f", XYZ( GetLocalOrigin() ) );
QAngle quatAngles = m_PortalLocal.m_qQuaternionPunch;
engine->Con_NPrintf( 9, "%f, %f, %f", quatAngles.x, quatAngles.y, quatAngles.z );
#endif
#endif
}
// Set our camera state if we just finished the "easy" transitions
if( m_PortalLocal.m_bDoneStickInterp )
{
if( m_PortalLocal.m_nStickCameraState == STICK_CAMERA_PORTAL )
{
m_PortalLocal.m_nStickCameraState = STICK_CAMERA_UPRIGHT;
}
}
}
void CPortal_Player::SetQuaternionPunch( const Quaternion& qPunch )
{
QAngle qTempAngle;
QuaternionAngles( qPunch, qTempAngle );
qTempAngle.x = anglemod( qTempAngle.x );
qTempAngle.y = anglemod( qTempAngle.y );
qTempAngle.z = anglemod( qTempAngle.z );
m_PortalLocal.m_qQuaternionPunch = qTempAngle;
}
void CPortal_Player::DecayQuaternionPunch()
{
// Convert angles to quaternion
Quaternion qOut;
AngleQuaternion( m_PortalLocal.m_qQuaternionPunch, qOut );
// Quaternion to axis angles
Vector vAxis;
float flAngle;
QuaternionAxisAngle( qOut, vAxis, flAngle );
// Decay the angle
flAngle *= ExponentialDecay( stick_cam_pitch_rate, gpGlobals->frametime );
// Snap to 0 if small enough
flAngle = fabs(flAngle) < 0.15f ? 0.f : flAngle;
// Convert back. Axis angle to quaternion
AxisAngleQuaternion( vAxis, flAngle, qOut );
// Convert quaternion to angles
QAngle anglesOut;
QuaternionAngles( qOut, anglesOut );
anglesOut.x = anglemod( anglesOut.x );
anglesOut.y = anglemod( anglesOut.y );
anglesOut.z = anglemod( anglesOut.z );
// Store it
m_PortalLocal.m_qQuaternionPunch = anglesOut;
}
void CPortal_Player::SnapCamera( StickCameraState nCameraState, bool bOverPitched )
{
// Default to set the player's up view
StickCameraCorrectionMethod correctionMethod = DO_NOTHING;
// Get our vectors
Vector vForward, vUp, vRight, vNewForward;
Vector vQuaternionRollAxis = m_PortalLocal.m_OldStickNormal;
AngleVectors( pl.v_angle, &vForward, &vRight, &vUp );
bool bRollCorrect = false, bPitchCorrect = false;
// Setup data we'll need to test for specific cases
const float flForwardDotLocalUp = DotProduct( vForward, m_PortalLocal.m_vLocalUp );
Vector vForwardInXY = vForward - ( m_PortalLocal.m_vLocalUp * flForwardDotLocalUp );
vForwardInXY.NormalizeInPlace();
const float flUpDotAbsUp = DotProduct( m_PortalLocal.m_Up, ABS_UP );
// const float flUpDot = fabs( pEnterPortal->m_matrixThisToLinked.m[2][2] ); //How much does zUp still look like zUp after going through this portal
// bool bSignificantAngle = (flUpDot < COS_PI_OVER_SIX && (sv_portal_new_player_trace.GetBool() || (flUpDot >= EQUAL_EPSILON)));
// Portalling
if( nCameraState == STICK_CAMERA_PORTAL )
{
// Portalling: Floor->Floor and Ceiling->Ceiling
// Over pitched and looking within a threshold where we want to pitch rather than roll.
if( bOverPitched &&
fabs(flForwardDotLocalUp) > stick_cam_over_the_top_threshold &&
AlmostEqual( fabs(flUpDotAbsUp), 1.f ) )
{
correctionMethod = QUATERNION_CORRECT;
bPitchCorrect = true;
bRollCorrect = false;
#ifdef DEBUG_STICK_CAM
DevMsg("Portal: Floor->Floor or Ceiling->Ceiling.");
#endif
}
// Portalling: Wall->Floor and Wall->Ceiling
// Over pitched and looking within a threshold where we want to pitch rather than roll or
// they're not overpitched but are still within a threshold where we want to slightly roll them (which will appear
// to be a yaw relative to their perspective) rather than actuall roll.
else if( ( bOverPitched &&
fabs(flForwardDotLocalUp) > stick_cam_over_the_top_threshold ) ||
( fabs(flForwardDotLocalUp) > stick_cam_pitch_vs_roll_good_angle_threshold ) )
{
correctionMethod = QUATERNION_CORRECT;
bRollCorrect = true;
bPitchCorrect = bOverPitched;
vQuaternionRollAxis = vUp;
#ifdef DEBUG_STICK_CAM
DevMsg("Portal: Wall->Floor or Wall->Ceiling.");
if( bOverPitched )
DevMsg("\tPitching");
#endif
}
else // Normal portalling where we want to roll
{
correctionMethod = SNAP_UP;
#ifdef DEBUG_STICK_CAM
DevMsg("Portal: Snap up");
#endif
}
}
// Instantly snap the player's view into a good state, and use a quaternion view punch to offset their
// view to where it was before the snap then decay the quaternion.
if( correctionMethod == QUATERNION_CORRECT )
{
const Vector vForwardTarget = DotProduct( vForward, ABS_UP ) > 0 ? ABS_UP : -ABS_UP;
const float flForwardDotRollAxis = DotProduct( vForward, vQuaternionRollAxis );
// Project forward vector onto the plane we just came off of
Vector vForwardInPlane = vForward - ( vQuaternionRollAxis * flForwardDotRollAxis );
vForwardInPlane.NormalizeInPlace();
const float flRightDotTarget = DotProduct( vRight, vForwardTarget );
const float flRightAngle = 90.0f - RAD2DEG( acos( flRightDotTarget ) );
// Determine how rolled we are
const float flRollAngle = -flRightAngle;
// Create matrix to undo roll
VMatrix rotMatrix;
MatrixBuildRotationAboutAxis( rotMatrix, vQuaternionRollAxis, flRollAngle );
// Create quaternion to preserve roll for punch
Quaternion qRoll(0,0,0,1); // Identity
if( bRollCorrect )
{
AxisAngleQuaternion( vQuaternionRollAxis, -flRollAngle, qRoll );
// Roll our forward vector
vForward = rotMatrix * vForward;
}
// Determine how much pitch we need to do
const float flRolledForwardDotUp = DotProduct( vForward, vForwardTarget );
float flPitchAngle = RAD2DEG( acos( flRolledForwardDotUp ) ); // This angle will get us straight up
// Over-pitch if we're looking in a "bad direction" (ie. over-pitched in target orientation)
if( bOverPitched )
{
flPitchAngle = flPitchAngle > 0 ? flPitchAngle + 1 : flPitchAngle - 1;
}
else
{
flPitchAngle = flPitchAngle > 0 ? flPitchAngle - 1 : flPitchAngle + 1;
}
// Build our pitch matrix
Vector vRotAxis = CrossProduct( vForward, vForwardTarget );
vRotAxis.NormalizeInPlace();
MatrixBuildRotationAboutAxis( rotMatrix, vRotAxis, flPitchAngle );
// Build quaternion to preserve this pitch for punch
Quaternion qPitch(0,0,0,1); // Identity
if( bPitchCorrect )
{
AxisAngleQuaternion( vRotAxis, -flPitchAngle, qPitch );
// Pitch our forward vector
vForward = rotMatrix * vForward;
}
// Accumulate rotations
Quaternion qPunch;
QuaternionNormalize( qRoll );
QuaternionNormalize( qPitch );
QuaternionMult( qRoll, qPitch, qPunch );
// Punch ourselves in the face from the 4th dimension
SetQuaternionPunch( qPunch );
// Get our new angles
QAngle vNewAngles;
VectorAngles( vForward, vUp, vNewAngles );
pl.v_angle = vNewAngles;
#ifdef GAME_DLL
// Only snap player's eyes in a singleplayer game
/*if( g_pGameRules->IsMultiplayer() == false )
{
pl.fixangle = FIXANGLE_ABSOLUTE;
}*/
#else
if( IsLocalPlayer() && (GET_ACTIVE_SPLITSCREEN_SLOT() == GetSplitScreenPlayerSlot()) )
{
engine->SetViewAngles( pl.v_angle );
if( prediction->InPrediction() )
{
prediction->SetViewAngles( pl.v_angle );
}
}
#endif
m_PortalLocal.m_Up = m_PortalLocal.m_StickNormal;
m_PortalLocal.m_bDoneCorrectPitch = true;
}
// Snap up vector to be our view up then have it rotate from there to our local up
else if( correctionMethod == SNAP_UP )
{
Vector vNewUp;
AngleVectors( pl.v_angle, NULL, NULL, &vNewUp );
// Check if our up is more abs down than abs up. If so, don't let it get too far behind our forward
if( DotProduct(vNewUp, m_PortalLocal.m_vLocalUp ) < -0.0f )
{
// Make up be coplanar with stick normal and orthogonal to forward
vNewUp -= vForwardInXY * DotProduct( vForwardInXY, vNewUp );
vNewUp.NormalizeInPlace();
// Determine rotation axis
const Vector vForwardCrossUp = CrossProduct( vForwardInXY, m_PortalLocal.m_vLocalUp );
m_PortalLocal.m_vStickRotationAxis = DotProduct( vForwardCrossUp, vNewUp ) > 0.f ? -vForwardInXY.Normalized() : vForwardInXY.Normalized();
}
else
{
m_PortalLocal.m_vStickRotationAxis = CrossProduct( vUp, m_PortalLocal.m_vLocalUp ).Normalized();
}
m_PortalLocal.m_Up = vNewUp;
m_PortalLocal.m_bDoneStickInterp = false;
}
// Rotate our up vector from where it currently is to our local up
else if( correctionMethod == ROTATE_UP )
{
m_PortalLocal.m_vStickRotationAxis = CrossProduct( m_PortalLocal.m_Up, m_PortalLocal.m_vLocalUp ).Normalized();
m_PortalLocal.m_bDoneStickInterp = false;
}
m_PortalLocal.m_nStickCameraState = nCameraState;
#ifdef DEBUG_STICK_CAM
DevMsg("\n");
#endif
}
void CPortal_Player::RotateUpVector( Vector& vForward, Vector& vUp )
{
float flRotationAmt = 0.f;
const Vector vStartUp = vUp.Normalized();
const Vector vStartForward = vForward.Normalized();
const Vector vForwardInRotationPlane = vForward - ( m_PortalLocal.m_vStickRotationAxis * DotProduct(vForward,m_PortalLocal.m_vStickRotationAxis) );
const Vector vPreRotationRight = CrossProduct( vForwardInRotationPlane, vUp );
const float vRotationRate = stick_cam_roll_rate;
if( m_PortalLocal.m_bDoneStickInterp == false )
{
Vector vEndUp = m_PortalLocal.m_vLocalUp;
vEndUp.NormalizeInPlace();
// Find the angle between our current up and our destination up
float flDot = DotProduct( vStartUp, vEndUp );
flDot = clamp( flDot, -1, 1 );
float flAngleBetween = RAD2DEG( acos( flDot ) );
// If there's still significant difference between our current up and our destination up
if( fabs( flAngleBetween ) > 0.1f )
{
float flDecayTo = 0.f;
flDecayTo = flDecayTo > vRotationRate ? flDecayTo : vRotationRate;
// Exponentially decay towards destination
float flDecayAngle = flAngleBetween * ExponentialDecay( flDecayTo, gpGlobals->frametime );
flRotationAmt = flAngleBetween - flDecayAngle;
// Have constant rotation once the rotation rate becomes very small
if( gpGlobals->frametime != 0.f &&
( flRotationAmt / gpGlobals->frametime < stick_cam_min_rotation_rate ) )
{
flRotationAmt = stick_cam_min_rotation_rate * gpGlobals->frametime;
}
// Slow down rotation if forward vector and up vector get very close to each other
if( m_PortalLocal.m_nStickCameraState == STICK_CAMERA_UPRIGHT )
{
float flAngle = DotProduct( vForward, vUp );
if( flAngle > 0.9f )
{
float flDamp = RemapValClamped( flAngle, 0.9f, 1.f, 1.f, 5.f );
flDamp *= flDamp;
flRotationAmt /= flDamp;
}
}
// Possibly flip the rotation axis if it's not on the side we expect it to be on
if( DotProduct( m_PortalLocal.m_vStickRotationAxis, CrossProduct( vUp, m_PortalLocal.m_vLocalUp ) ) < 0.f )
{
Vector v = m_PortalLocal.m_vStickRotationAxis;
m_PortalLocal.m_vStickRotationAxis = -v;
}
// Build rotation matrix
VMatrix rotMatrix;
MatrixBuildRotationAboutAxis( rotMatrix, m_PortalLocal.m_vStickRotationAxis, flRotationAmt );
// Rotate up
vUp = rotMatrix * vUp;
vUp.NormalizeInPlace();
if( m_PortalLocal.m_bDoneCorrectPitch == false )
{
vForward = rotMatrix * vForward;
vForward.NormalizeInPlace();
}
}
else
{
// Just snap
vUp = vEndUp;
m_PortalLocal.m_bDoneStickInterp = true;
}
}
// Should we bother trying to correct pitch?
if ( stick_cam_correct_pitch && m_PortalLocal.m_bDoneCorrectPitch == false )
{
// Check if we want to not correct the player's pitch because we think they're trying to correct it themselves
const CUserCmd *cmd = m_pCurrentCommand;
// If they're mousing around, don't correct pitch
if( cmd && abs( cmd->mousedy ) > sv_stick_input_cancel_threshold && GetReorientationProgress() > 0.9f )
{
m_PortalLocal.m_bDoneCorrectPitch = true;
return;
}
// Build the matrix
VMatrix rotMatrix;
MatrixBuildRotationAboutAxis( rotMatrix, m_PortalLocal.m_vStickRotationAxis, flRotationAmt);
// Rotate forward along with up
}// if !m_bDoneCorrectPitch
//#ifdef DEBUG_STICK_CAM
//#ifdef GAME_DLL
// if( m_nButtons & IN_GRENADE2 )
// {
// Vector vRight = CrossProduct( vForward, vUp );
// vRight.NormalizeInPlace();
// // NDebugOverlay::Sphere( EyePosition() + 20.f * vUp, 2.f, 255, 255, 255, true, 0.f );
// // NDebugOverlay::Sphere( EyePosition() + 20.f * m_PortalLocal.m_vLocalUp, 2.f, 0, 205, 50, true, 0.f );
// NDebugOverlay::Line( GetAbsOrigin() , GetAbsOrigin() + (vForward*50) , 205, 50, 0, true, 100 );
// NDebugOverlay::Line( GetAbsOrigin() , GetAbsOrigin() + (m_PortalLocal.m_vLocalUp*50) , 0, 205, 50, true, 100 );
// NDebugOverlay::Line( GetAbsOrigin() , GetAbsOrigin() + (vRight*50),0,0,255,true,100);
// NDebugOverlay::Line( GetAbsOrigin() , GetAbsOrigin() + (vUp*50),255,255,255,true,100);
// }
//#endif
//#endif
}
void CPortal_Player::PreventCrouchJump( CUserCmd* ucmd )
{
if( prevent_crouch_jump.GetBool() && GetInAirState() == IN_AIR_JUMPED )
{
m_PortalLocal.m_bPreventedCrouchJumpThisFrame = (ucmd->buttons & IN_DUCK) != 0;
ucmd->buttons &= ~IN_DUCK;
}
}
void CPortal_Player::BridgeRemovedFromUnder( void )
{
m_PlayerAnimState->BridgeRemovedFromUnder();
}
const Vector ComputeJumpVelocity( const Vector& normal,
const Vector& localUp,
float desiredNormalSpeed )
{
Vector outBounceVelocity = vec3_origin;
const float flNormDot = DotProduct( localUp, normal );
const float flTrade = sv_wall_bounce_trade.GetFloat(); // We trade some outward velocity for upward velocity
// Add upward velocity if surface normal is facing up, relative to the player.
float flBounceScale = 0.f;
if( sv_press_jump_to_bounce.GetInt() != TRAMPOLINE_BOUNCE || bounce_reflect_wall_jumps_enabled.GetBool() )
{
if( flNormDot > -0.1f )
{
// Extra upward wall bounce velocity
flBounceScale = 1.f - flNormDot;
outBounceVelocity += localUp * bounce_paint_wall_jump_upward_speed.GetFloat() * flBounceScale;
}
// Downward facing wall. Add velocity in the XY plane.
else
{
// Vector pointing out of the surface in the XY plane
Vector vOut = ( normal - (localUp * flNormDot) ).Normalized();
// Extra lateral velocity off the wall
flBounceScale = DotProduct( vOut, normal );
outBounceVelocity += vOut * bounce_paint_wall_jump_upward_speed.GetFloat() * flBounceScale;
}
}
// Calculate how much bounce velocity is left to spend after the lateral
float fWallBounceScale = flTrade + ( (1.f - flBounceScale) * (1.0 - flTrade) );
// Velocity off of the surface
outBounceVelocity += normal * desiredNormalSpeed * fWallBounceScale;
return outBounceVelocity;
}
const Vector ComputeWallJumpHelperVelocity( const Vector& finalBounceVelocity,
const Vector& normal )
{
Vector velocity = finalBounceVelocity;
if( sv_wall_jump_help.GetBool() )
{
Vector vecOutVelocity = velocity;
vecOutVelocity.z = 0.0f;
VectorNormalize( vecOutVelocity );
float flOutAngle = RAD2DEG( acos( clamp( DotProduct( normal, vecOutVelocity ), -1.0f, 1.0f ) ) );
float flCorrection = 0.0f;
//Do not correct if the out angle is the same as the normal
if( flOutAngle == 0.0f )
{
flCorrection = 0.0f;
return velocity;
}
float flSign = vecOutVelocity.x * normal.y - vecOutVelocity.y * normal.x;
//The out angle is small enough to snap to the normal
if( flOutAngle <= sv_wall_jump_help_amount.GetFloat() )
{
flCorrection = flOutAngle;
}
//The out angle needs to be corrected to the normal dir
else if( flOutAngle <= sv_wall_jump_help_threshold.GetFloat() )
{
flCorrection = sv_wall_jump_help_amount.GetFloat();
}
//Rotate the out velocity towards the normal
VMatrix matRotate;
if( flSign > 0.0f )
{
MatrixBuildRotateZ( matRotate, flCorrection );
}
else
{
MatrixBuildRotateZ( matRotate, -flCorrection );
}
Vector rotatedVelocity;
VectorRotate( velocity, matRotate.As3x4(), rotatedVelocity );
velocity = rotatedVelocity;
//#ifndef CLIENT_DLL
//if( sv_wall_jump_help_debug.GetBool() )
//{
// Vector vecInVelocity = velocity;
// vecInVelocity.z = 0.0f;
// VectorNormalize( vecInVelocity );
// float flInAngle = 180.0f - RAD2DEG( acos( DotProduct( normal, vecInVelocity ) ) );
// DevMsg( "InAngle: %f, OutAngle: %f, Correction: %f\n", flInAngle, flOutAngle, flCorrection );
//}
//#endif //!CLIENT_DLL
}
return velocity;
}
const Vector ComputeBouncePostVelocityReflection( const Vector& preVelocity,
const Vector& normal,
const Vector& localUp )
{
// Cancel out velocity in the normal direction
const float normalSpeed = DotProduct( preVelocity, normal );
const Vector normalVelocity = normalSpeed * normal;
Vector velocity = preVelocity;
velocity -= normalVelocity;
// The base bounce velocity is the remaining velocity required to reflect the
// velocity when the player hit the surface.
Vector bounceVelocity = -bounce_reflect_restitution.GetFloat() * normalVelocity;
// Optionally trade some outward velocity for upward velocity on wall jumps
const bool shouldWallJump = bounce_reflect_wall_jumps_enabled.GetBool() && CloseEnough( normal.z, 0.0f );
if( shouldWallJump )
{
bounceVelocity = ComputeJumpVelocity( normal, localUp, fabs( normalSpeed ) );
// Don't let the velocity tangent to the surface cancel out the wall jump
const float minWallJumpUpSpeed = bounce_reflect_wall_jump_min_up_speed.GetFloat();
if( bounceVelocity.z + velocity.z < minWallJumpUpSpeed )
{
bounceVelocity.z += minWallJumpUpSpeed - velocity.z;
}
}
velocity += bounceVelocity;
// If wall jumps are allowed, use the wall jump helper to bring
// the post impact velocity closer to the normal direction
if( shouldWallJump )
velocity = ComputeWallJumpHelperVelocity( velocity, normal );
return velocity;
}
const Vector ComputeBouncePostVelocityNoReflect( const Vector& preVelocity,
const Vector& normal,
const Vector& localUp )
{
Vector vBounceVel(0,0,0);
Vector velocity = preVelocity;
velocity -= normal * DotProduct( velocity, normal );
// Cancel out downward velocity (allows for going up parallel walls)
velocity -= localUp * DotProduct( velocity, localUp );
// Store this for later
Vector velNorm = velocity;
velNorm.NormalizeInPlace();
// Add upward velocity if surface normal is facing perpendicular to up, relative to the player
vBounceVel = ComputeJumpVelocity( normal, localUp, bounce_paint_min_speed.GetFloat() );
// Don't let our velocity fight the new bounce velocity
velocity -= velocity.Normalized() * DotProduct( velocity, vBounceVel.Normalized() );
velocity += vBounceVel;
// Use the wall jump helper to bring the post jump velocity closer to the normal direction
velocity = ComputeWallJumpHelperVelocity( velocity, normal );
return velocity;
}
void ExpandAABB( Vector& boxMin, Vector& boxMax, const Vector& sweepVector )
{
for( unsigned i = 0; i < 3; ++i )
{
boxMin[i] += sweepVector[i] < 0 ? sweepVector[i] : 0;
boxMax[i] += sweepVector[i] > 0 ? sweepVector[i] : 0;
}
}
void BrushContact::Initialize( const Vector& contactPt,
const Vector& planeNormal,
CBaseEntity* pEntity,
bool onThinSurface )
{
point = contactPt;
normal = planeNormal;
pBrushEntity = pEntity;
isOnThinSurface = onThinSurface;
}
void BrushContact::Initialize( const fltx4& contactPt,
const fltx4& planeNormal,
CBaseEntity* pEntity,
bool onThinSurface )
{
StoreUnaligned3SIMD( &point.x, contactPt );
StoreUnaligned3SIMD( &normal.x, planeNormal );
pBrushEntity = pEntity;
isOnThinSurface = onThinSurface;
}
typedef CUtlVector<Vector4D> PlaneVector;
void AddBboxToPlaneList( Vector4D *pListInsertPosition, const Vector &vMins, const Vector &vMaxs )
{
pListInsertPosition[0] = Vector4D(1, 0, 0, vMaxs.x);
pListInsertPosition[1] = Vector4D(-1, 0, 0, -vMins.x);
pListInsertPosition[2] = Vector4D(0, 1, 0, vMaxs.y);
pListInsertPosition[3] = Vector4D(0, -1, 0, -vMins.y);
pListInsertPosition[4] = Vector4D(0, 0, 1, vMaxs.z);
pListInsertPosition[5] = Vector4D(0, 0, -1, -vMins.z);
}
void AddBboxToPlaneList_SIMD( fltx4 *pListInsertPosition, const fltx4 &vMins, const fltx4 &vMaxs )
{
pListInsertPosition[0] = SetWFromXSIMD( g_SIMD_Identity[0], vMaxs ); // Vector4D(1, 0, 0, vMaxs.x);
pListInsertPosition[1] = -SetWFromXSIMD( g_SIMD_Identity[0], vMins ); // Vector4D(-1, 0, 0, -vMins.x);
pListInsertPosition[2] = SetWFromYSIMD( g_SIMD_Identity[1], vMaxs ); // Vector4D(0, 1, 0, vMaxs.y);
pListInsertPosition[3] = -SetWFromYSIMD( g_SIMD_Identity[1], vMins ); // Vector4D(0, -1, 0, -vMins.y);
pListInsertPosition[4] = SetWFromZSIMD( g_SIMD_Identity[2], vMaxs ); // Vector4D(0, 0, 1, vMaxs.z);
pListInsertPosition[5] = -SetWFromZSIMD( g_SIMD_Identity[2], vMins ); // Vector4D(0, 0, -1, -vMins.z);
}
inline const Vector ToVector( const float* vec )
{
return Vector( vec[0], vec[1], vec[2] );
}
void DebugDrawMesh( const CMesh& mesh )
{
for ( int i = 0; i < mesh.TriangleCount(); ++i )
{
int i0 = mesh.m_pIndices[i*3 + 0];
int i1 = mesh.m_pIndices[i*3 + 1];
int i2 = mesh.m_pIndices[i*3 + 2];
NDebugOverlay::Triangle( ToVector( mesh.GetVertex(i0) ),
ToVector( mesh.GetVertex(i1) ),
ToVector( mesh.GetVertex(i2) ),
255, 0, 0, 128, true, 0 );
}
}
const Vector ComputeCentroid( const CMesh& volume )
{
Vector centroid( 0, 0, 0 );
for ( int i = 0; i < volume.m_nVertexCount; ++i )
{
//NDebugOverlay::Sphere( ToVector( volume.GetVertex(i) ), 5.0f, 0, 255, 255, false, 0 );
centroid += ToVector( volume.GetVertex(i) );
}
centroid *= (1.0f / volume.m_nVertexCount);
return centroid;
}
const fltx4 ComputeCentroid_SIMD( const CMesh& volume )
{
fltx4 centroid = Four_Zeros;
int nCount = volume.m_nVertexCount;
fltx4 f4NumberOfVertices = ReplicateX4( (float)nCount );
int nIndex = 0;
while ( nCount >= 4 )
{
fltx4 p0 = *(const fltx4 *)volume.GetVertex( nIndex );
fltx4 p1 = *(const fltx4 *)volume.GetVertex( nIndex + 1 );
fltx4 p2 = *(const fltx4 *)volume.GetVertex( nIndex + 2 );
fltx4 p3 = *(const fltx4 *)volume.GetVertex( nIndex + 3 );
// Try to reduce register dependency a bit
fltx4 first2 = AddSIMD( p0, p1 );
fltx4 second2 = AddSIMD( p2, p3 );
centroid = AddSIMD( centroid, first2 );
centroid = AddSIMD( centroid, second2 );
nCount -= 4;
nIndex += 4;
}
while ( nCount > 0 )
{
fltx4 p0 = *(const fltx4 *)volume.GetVertex( nIndex );
centroid = AddSIMD( centroid, p0 );
--nCount;
++nIndex;
}
centroid = MulSIMD( centroid, ReciprocalSIMD( f4NumberOfVertices ) );
return centroid;
}
// outsidePt is a point that should be on the positive side of the plane defined by the triangle
bool ComputeContactPlane( cplane_t& plane, int& contactTriangleIndex, const CMesh& contactRegion, const Vector& outsidePt )
{
Vector contactNormal( 0, 0, 1 );
float maxAreaSq = -FLT_MAX;
int p0VertexIndex = 0;
const int triangleCount = contactRegion.TriangleCount();
contactTriangleIndex = triangleCount;
for( int i = 0; i < triangleCount; ++i )
{
// Get the vertices of the triangle
const int triangleIndex = i * 3;
const int i0 = contactRegion.m_pIndices[triangleIndex];
const int i1 = contactRegion.m_pIndices[triangleIndex + 1];
const int i2 = contactRegion.m_pIndices[triangleIndex + 2];
const Vector p0 = ToVector( contactRegion.GetVertex(i0) );
const Vector p1 = ToVector( contactRegion.GetVertex(i1) );
const Vector p2 = ToVector( contactRegion.GetVertex(i2) );
// Compute the normal to the plane defined by the triangle
const Vector planeNormal = CrossProduct( p1 - p0, p2 - p0 );
// Choose the normal corresponding to the triangle with the largest area
// (since this is the intersection volume of a slightly expanded AABB, which
// is only penetrating the brush by the expansion amount). Also, make sure it
// points in the general direction of the point outside the plane.
const Vector outVector = outsidePt - p0;
const float areaSq = planeNormal.LengthSqr();
if( areaSq > maxAreaSq && DotProduct( planeNormal, outVector ) > 0 )
{
maxAreaSq = areaSq;
contactNormal = planeNormal;
p0VertexIndex = i0;
contactTriangleIndex = i;
}
//NDebugOverlay::Triangle( p0, p1, p2, 255, 0, 0, 128, true, 0 );
//NDebugOverlay::HorzArrow( p0, p0 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
//NDebugOverlay::HorzArrow( p1, p1 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
//NDebugOverlay::HorzArrow( p2, p2 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
}
// Normalize the normal
const bool isValid = maxAreaSq > 0;
contactNormal *= isValid ? 1.0f / sqrt( maxAreaSq ) : 1.0f;
// Fill out the plane
plane.normal = contactNormal;
plane.dist = DotProduct( contactNormal, ToVector( contactRegion.GetVertex(p0VertexIndex) ) );
return isValid;
}
int ComputeContactPlane_SIMD( fltx4& plane, const CMesh& contactRegion, const fltx4& outsidePt )
{
fltx4 contactNormal = g_SIMD_Identity[2]; // ( 0, 0, 1 );
fltx4 maxAreaSq = Four_Zeros;
const int nTriangleCount = contactRegion.TriangleCount();
int contactTriangleIndex = -1;
fltx4 pReferenceVertex = Four_Zeros;
for( int i = 0; i < nTriangleCount; ++i )
{
// Get the vertices of the triangle
const int triangleIndex = i * 3;
const int i0 = contactRegion.m_pIndices[triangleIndex];
const int i1 = contactRegion.m_pIndices[triangleIndex + 1];
const int i2 = contactRegion.m_pIndices[triangleIndex + 2];
// The mesh has been created from fltx4 (aligned with a stride of 4).
const fltx4 p0 = *(const fltx4 *)( contactRegion.GetVertex(i0) );
const fltx4 p1 = *(const fltx4 *)( contactRegion.GetVertex(i1) );
const fltx4 p2 = *(const fltx4 *)( contactRegion.GetVertex(i2) );
// Compute the normal to the plane defined by the triangle
const fltx4 planeNormal = CrossProductSIMD( p1 - p0, p2 - p0 );
// Choose the normal corresponding to the triangle with the largest area
// (since this is the intersection volume of a slightly expanded AABB, which
// is only penetrating the brush by the expansion amount). Also, make sure it
// points in the general direction of the point outside the plane.
const fltx4 outVector = outsidePt - p0;
const fltx4 areaSq = Dot3SIMD( planeNormal, planeNormal );
const fltx4 dotProd = Dot3SIMD( planeNormal, outVector );
const fltx4 f4IsMax = (fltx4)CmpLeSIMD( areaSq, maxAreaSq ); // if ( areaSq <= maxAreaSq )
const fltx4 f4IsDotPos = (fltx4)CmpLeSIMD( dotProd, Four_Zeros ); // if ( DotProduct( planeNormal, outVector ) <= 0 )
//if ( areaSq > maxAreaSq && DotProduct( planeNormal, outVector ) > 0 ) - Except we do the opposite tests and look if the result is false
if ( IsAllEqual( OrSIMD( f4IsMax, f4IsDotPos), Four_Zeros ) )
{
maxAreaSq = areaSq;
contactNormal = planeNormal;
pReferenceVertex = p0;
contactTriangleIndex = i;
}
//NDebugOverlay::Triangle( p0, p1, p2, 255, 0, 0, 128, true, 0 );
//NDebugOverlay::HorzArrow( p0, p0 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
//NDebugOverlay::HorzArrow( p1, p1 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
//NDebugOverlay::HorzArrow( p2, p2 + 20.0f * planeNormal.Normalized(), 1, 255, 0, 255, 128, true, 0 );
}
if (contactTriangleIndex < 0)
{
return -1;
}
// At that point the area is greater than zero (i.e. valid)
// Normalize the normal
// const bool isValid = maxAreaSq > 0;
// contactNormal *= isValid ? 1.0f / sqrt( maxAreaSq ) : 1.0f;
contactNormal = MulSIMD( contactNormal, ReciprocalSqrtSIMD( maxAreaSq ) );
// Fill out the plane
// plane.normal = contactNormal;
// plane.dist = DotProduct( contactNormal, ToVector( contactRegion.GetVertex(p0VertexIndex) ) );
fltx4 f4Dist = Dot3SIMD( contactNormal, pReferenceVertex );
plane = SetWSIMD( contactNormal, f4Dist );
return contactTriangleIndex;
}
void ComputeAABBContactsWithBrushEntity( ContactVector& contacts, const Vector& boxOrigin, const Vector& boxMin, const Vector& boxMax, CBaseEntity* pBrushEntity, int contentsMask )
{
ComputeAABBContactsWithBrushEntity( contacts, NULL, 0, boxOrigin, boxMin, boxMax, pBrushEntity, contentsMask );
}
void ComputeAABBContactsWithBrushEntity_Old( ContactVector& contacts, const cplane_t *pClipPlanes, int iClipPlaneCount, const Vector& boxOrigin, const Vector& boxMin, const Vector& boxMax, CBaseEntity* pBrushEntity, int contentsMask)
{
//typedef CUtlVector<int> BrushIndexVector;
typedef CUtlVector<uint32> PlaneIndexVector;
//typedef CUtlVector<BrushSideInfo_t> BrushSideInfoVector;
//typedef BrushSideInfo_t* BrushSideInfoIterator;
// Get the collision model index of the brush entity
AssertMsg( pBrushEntity->IsBSPModel(), "Your brush entity is not a brush entity." );
ICollideable* pCollideable = enginetrace->GetCollideable( pBrushEntity );
const int cmodelIndex = pCollideable->GetCollisionModelIndex() - 1;
AssertMsg( !pBrushEntity->IsWorld() || cmodelIndex == 0, "World collision model index should be 0." );
const matrix3x4_t& entityToWorld = pBrushEntity->EntityToWorldTransform();
// The query box must be in local space for non-world brush entities
Vector queryBoxMin = boxMin;
Vector queryBoxMax = boxMax;
if( !pBrushEntity->IsWorld() )
{
ITransformAABB( entityToWorld, boxMin, boxMax, queryBoxMin, queryBoxMax );
}
// Get the indices of all the colliding brushes
//BrushIndexVector brushIndices;
CBrushQuery brushQuery;
enginetrace->GetBrushesInAABB( queryBoxMin, queryBoxMax, brushQuery, contentsMask, cmodelIndex );
// Find the contact regions
//BrushSideInfoVector brushSides;
BrushSideInfo_t *brushSides = (BrushSideInfo_t *)stackalloc( sizeof( BrushSideInfo_t ) * brushQuery.MaxBrushSides() );
//PlaneVector planes;
Vector4D *planes = (Vector4D *)stackalloc( sizeof( Vector4D ) * (brushQuery.MaxBrushSides() + 6 /*bbox*/ + iClipPlaneCount) );
for( int i = 0; i < brushQuery.Count(); ++i )
{
// Get the brush side info
int iBrushContents;
int iNumBrushSides = enginetrace->GetBrushInfo( brushQuery[i], iBrushContents, brushSides, brushQuery.MaxBrushSides() );
Assert( iNumBrushSides > 0 );
if( iNumBrushSides <= 0 )
continue;
//remove bevel planes
{
int iWriteIndex = 0;
for( int sideIndex = 0; sideIndex < iNumBrushSides; ++sideIndex )
{
if( brushSides[sideIndex].bevel != 0 )
continue;
brushSides[iWriteIndex] = brushSides[sideIndex];
++iWriteIndex;
}
iNumBrushSides = iWriteIndex;
}
// Transform the planes to world space
for( int sideIndex = 0; sideIndex < iNumBrushSides; ++sideIndex )
{
cplane_t temp;
MatrixTransformPlane( entityToWorld, brushSides[sideIndex].plane, temp );
planes[sideIndex] = Vector4D( temp.normal.x, temp.normal.y, temp.normal.z, temp.dist );
}
int iPlaneCount = iNumBrushSides;
// Add the box planes
AddBboxToPlaneList( &planes[iNumBrushSides], boxMin, boxMax );
iPlaneCount += 6;
// Add the clip planes
for( int j = 0; j < iClipPlaneCount; ++j )
{
const cplane_t& plane = pClipPlanes[j];
planes[iPlaneCount] = Vector4D( -plane.normal.x, -plane.normal.y, -plane.normal.z, -plane.dist );
++iPlaneCount;
}
Assert( iPlaneCount <= (brushQuery.MaxBrushSides() + 6 + iClipPlaneCount) );
// Compute the contact region
CMesh contactRegion;
PlaneIndexVector trianglePlaneIndices;
HullFromPlanes( &contactRegion, &trianglePlaneIndices, reinterpret_cast<const float*>( planes ), iPlaneCount, 4 );
//DebugDrawMesh( contactRegion );
// If the contact region exists
if( contactRegion.m_nVertexCount > 0 )
{
// Compute the contact point and normal
cplane_t plane;
int triangleIndex;
if( ComputeContactPlane( plane, triangleIndex, contactRegion, boxOrigin ) )
{
const Vector centroid = ComputeCentroid( contactRegion );
const float dist = DotProduct( centroid, plane.normal );
const float diff = dist - plane.dist;
const Vector contactPt = centroid - diff * plane.normal;
// Figure out if the contact is on a thin surface
const int planeIndex = trianglePlaneIndices[triangleIndex];
const bool isOnThinSurface = planeIndex < iNumBrushSides ? brushSides[planeIndex].thin != 0 : false;
BrushContact contact;
contact.Initialize( contactPt, plane.normal, pBrushEntity, isOnThinSurface );
contacts.AddToTail( contact );
if( sv_debug_draw_contacts.GetInt() == 1 )
{
Color color( contact.isOnThinSurface ? 0 : 255, 255, 255 );
NDebugOverlay::Sphere( contact.point, 5.0f, color.r(), color.g(), color.b(), true, 0 );
NDebugOverlay::Line( contact.point, contact.point + 20.0f * contact.normal, color.r(), color.g(), color.b(), true, 0 );
}
//DevMsg( "Contact: (%f, %f, %f)\n", XYZ( contact.point ) );
//DevMsg( "Normal: (%f, %f, %f)\n", XYZ( contact.normal ) );
}
}
}
}
void ComputeAABBContactsWithBrushEntity_SIMD( ContactVector& contacts, const cplane_t *pClipPlanes, int iClipPlaneCount, const Vector& boxOrigin, const Vector& boxMin, const Vector& boxMax, CBaseEntity* pBrushEntity, int contentsMask)
{
//typedef CUtlVector<int> BrushIndexVector;
typedef CUtlVector<uint16> PlaneIndexVector;
//typedef CUtlVector<BrushSideInfo_t> BrushSideInfoVector;
//typedef BrushSideInfo_t* BrushSideInfoIterator;
// Get the collision model index of the brush entity
AssertMsg( pBrushEntity->IsBSPModel(), "Your brush entity is not a brush entity." );
ICollideable* pCollideable = enginetrace->GetCollideable( pBrushEntity );
const int cmodelIndex = pCollideable->GetCollisionModelIndex() - 1;
AssertMsg( !pBrushEntity->IsWorld() || cmodelIndex == 0, "World collision model index should be 0." );
const matrix3x4_t& entityToWorld = pBrushEntity->EntityToWorldTransform();
// The query box must be in local space for non-world brush entities
Vector queryBoxMin = boxMin;
Vector queryBoxMax = boxMax;
if( !pBrushEntity->IsWorld() )
{
ITransformAABB( entityToWorld, boxMin, boxMax, queryBoxMin, queryBoxMax );
}
// Get the indices of all the colliding brushes
//BrushIndexVector brushIndices;
CBrushQuery brushQuery;
enginetrace->GetBrushesInAABB( queryBoxMin, queryBoxMax, brushQuery, contentsMask, cmodelIndex );
// Find the contact regions
//BrushSideInfoVector brushSides;
BrushSideInfo_t *brushSides = (BrushSideInfo_t *)stackalloc( sizeof( BrushSideInfo_t ) * brushQuery.MaxBrushSides() );
//PlaneVector planes;
const int NUMBER_OF_FLTX4 = brushQuery.MaxBrushSides() + 6 /*bbox*/ + iClipPlaneCount;
fltx4 *planes = (fltx4 *)stackalloc( sizeof( fltx4 ) * ( NUMBER_OF_FLTX4 + 1 ) ); // +1 for VMX alignment
planes = (fltx4*)ALIGN_VALUE( (int)planes, sizeof(fltx4) );
fltx4 f4BoxMin = LoadUnalignedSIMD( &boxMin.x );
fltx4 f4BoxMax = LoadUnalignedSIMD( &boxMax.x );
fltx4 f4BoxOrigin = LoadUnalignedSIMD( &boxOrigin.x );
for( int i = 0; i < brushQuery.Count(); ++i )
{
// Get the brush side info
int iBrushContents;
int iNumBrushSides = enginetrace->GetBrushInfo( brushQuery[i], iBrushContents, brushSides, brushQuery.MaxBrushSides() );
Assert( iNumBrushSides > 0 );
if( iNumBrushSides <= 0 )
continue;
//remove bevel planes
{
int iWriteIndex = 0;
for( int sideIndex = 0; sideIndex < iNumBrushSides; ++sideIndex )
{
if( brushSides[sideIndex].bevel != 0 )
continue;
brushSides[iWriteIndex] = brushSides[sideIndex];
++iWriteIndex;
}
iNumBrushSides = iWriteIndex;
}
// Transform the planes to world space
for( int sideIndex = 0; sideIndex < iNumBrushSides; ++sideIndex )
{
cplane_t temp;
MatrixTransformPlane( entityToWorld, brushSides[sideIndex].plane, temp ); // Could be optimized further here...
planes[sideIndex] = LoadUnalignedSIMD(&temp.normal); // Read XYZ and dist of the plane
}
int iPlaneCount = iNumBrushSides;
// Add the box planes
AddBboxToPlaneList_SIMD( &planes[iNumBrushSides], f4BoxMin, f4BoxMax );
iPlaneCount += 6;
// Add the clip planes
for( int j = 0; j < iClipPlaneCount; ++j )
{
const cplane_t& plane = pClipPlanes[j];
planes[iPlaneCount] = -LoadUnalignedSIMD(&plane.normal);
++iPlaneCount;
}
Assert( iPlaneCount <= NUMBER_OF_FLTX4 );
// Compute the contact region
CMesh contactRegion;
PlaneIndexVector trianglePlaneIndices;
HullFromPlanes_SIMD( &contactRegion, &trianglePlaneIndices, planes, iPlaneCount );
//DebugDrawMesh( contactRegion );
// If the contact region exists
if( contactRegion.m_nVertexCount > 0 )
{
// Compute the contact point and normal
fltx4 plane;
int nTriangleIndex = ComputeContactPlane_SIMD( plane, contactRegion, f4BoxOrigin );
#if _DEBUG
cplane_t slowPlane;
int slowTriangleIndex;
bool bSlowValid = ComputeContactPlane( slowPlane, slowTriangleIndex, contactRegion, boxOrigin );
Assert( (nTriangleIndex >= 0) == bSlowValid );
// For optimization reasons, we changed the API a bit.
if (nTriangleIndex >= 0)
{
Assert( nTriangleIndex == slowTriangleIndex );
// Because ComputeContactPlane_SIMD() does a square root reciprocal, there is some error involved that will slightly create divergence in the results.
Assert( fabs( SubFloat( plane, 0 ) - slowPlane.normal.x ) < 0.001f );
Assert( fabs( SubFloat( plane, 1 ) - slowPlane.normal.y ) < 0.001f );
Assert( fabs( SubFloat( plane, 2 ) - slowPlane.normal.z ) < 0.001f );
Assert( fabs( SubFloat( plane, 3 ) - slowPlane.dist ) < 0.001f );
}
else
{
Assert( slowTriangleIndex == contactRegion.TriangleCount() );
}
#endif
if ( nTriangleIndex >= 0 )
{
const fltx4 centroid = ComputeCentroid_SIMD( contactRegion );
const fltx4 dist = Dot3SIMD( centroid, plane );
const fltx4 diff = dist - SplatWSIMD( plane );
const fltx4 contactPt = MsubSIMD(diff, plane, centroid); // centroid - diff * plane.normal;
// Figure out if the contact is on a thin surface
const int planeIndex = trianglePlaneIndices[nTriangleIndex];
const bool isOnThinSurface = planeIndex < iNumBrushSides ? brushSides[planeIndex].thin != 0 : false;
BrushContact contact;
contact.Initialize( contactPt, plane, pBrushEntity, isOnThinSurface );
contacts.AddToTail( contact );
//DevMsg( "Contact: (%f, %f, %f)\n", XYZ( contact.point ) );
//DevMsg( "Normal: (%f, %f, %f)\n", XYZ( contact.normal ) );
}
}
}
// Do the test only once instead of for every contacts. This is debug code after all.
if( sv_debug_draw_contacts.GetInt() == 1 )
{
for ( int i = 0 ; i < contacts.Count() ; ++i )
{
const BrushContact & contact = contacts[i];
Color color( contact.isOnThinSurface ? 0 : 255, 255, 255 );
NDebugOverlay::Sphere( contact.point, 5.0f, color.r(), color.g(), color.b(), true, 0 );
NDebugOverlay::Line( contact.point, contact.point + 20.0f * contact.normal, color.r(), color.g(), color.b(), true, 0 );
}
}
}
void ComputeAABBContactsWithBrushEntity( ContactVector& contacts, const cplane_t *pClipPlanes, int iClipPlaneCount, const Vector& boxOrigin, const Vector& boxMin, const Vector& boxMax, CBaseEntity* pBrushEntity, int contentsMask)
{
if ( paint_compute_contacts_simd.GetBool() )
{
ComputeAABBContactsWithBrushEntity_SIMD( contacts, pClipPlanes, iClipPlaneCount, boxOrigin, boxMin, boxMax, pBrushEntity, contentsMask );
#if _DEBUG
ContactVector fpuContacts;
ComputeAABBContactsWithBrushEntity_Old( fpuContacts, pClipPlanes, iClipPlaneCount, boxOrigin, boxMin, boxMax, pBrushEntity, contentsMask );
// Now check that the official version and the SIMD version give the same results.
Assert( contacts.Count() == fpuContacts.Count() );
for (int i = 0 ; i < contacts.Count() ; ++i )
{
// Because there are some reciprocal called in the SIMD version, there is a bit of inaccuracy in the results.
Assert( contacts[i].point.DistTo(fpuContacts[i].point) < 0.01 );
Assert( contacts[i].normal.DistTo(fpuContacts[i].normal) < 0.01 );
Assert( contacts[i].pBrushEntity == fpuContacts[i].pBrushEntity );
Assert( contacts[i].isOnThinSurface == fpuContacts[i].isOnThinSurface );
}
#endif
}
else
{
ComputeAABBContactsWithBrushEntity_Old( contacts, pClipPlanes, iClipPlaneCount, boxOrigin, boxMin, boxMax, pBrushEntity, contentsMask );
}
}
void TracePlayerBoxAgainstCollidables( trace_t& trace,
const CPortal_Player* player,
const Vector& startPos,
const Vector& endPos,
const Vector& boxLocalMin,
const Vector& boxLocalMax )
{
#ifdef CLIENT_DLL
CTraceFilterSimpleClassnameList traceFilter( player, COLLISION_GROUP_PLAYER_MOVEMENT );
traceFilter.AddClassnameToIgnore("prop_weighted_cube");
#else
CTraceFilterSimpleClassnameList baseFilter( player, COLLISION_GROUP_PLAYER_MOVEMENT );
baseFilter.AddClassnameToIgnore( "prop_weighted_cube" );
baseFilter.AddClassnameToIgnore( "prop_physics_paintable" );
CTraceFilterTranslateClones traceFilter( &baseFilter );
#endif
Ray_t ray;
ray.Init( startPos, endPos, boxLocalMin, boxLocalMax );
UTIL_ClearTrace( trace );
UTIL_Portal_TraceRay_With( player->m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace, true );
}
//The weapon shoot position of the player
ConVar player_paint_shoot_pos_forward_scale( "player_paint_shoot_pos_forward_scale", "55.0f", FCVAR_REPLICATED );
ConVar player_paint_shoot_pos_right_scale( "player_paint_shoot_pos_right_scale", "12.0f", FCVAR_REPLICATED );
ConVar player_paint_shoot_pos_up_scale( "player_paint_shoot_pos_up_scale", "25.0f", FCVAR_REPLICATED );
#ifndef CLIENT_DLL
ConVar debug_player_paint_shoot_pos( "debug_player_paint_shoot_pos", "0" );
#endif
Vector CPortal_Player::GetPaintGunShootPosition()
{
Vector vecEyePosition = EyePosition();
Vector vecForward, vecRight, vecUp;
EyeVectors( &vecForward, &vecRight, &vecUp );
vecForward.NormalizeInPlace();
vecUp.NormalizeInPlace();
vecRight.NormalizeInPlace();
Vector vecMuzzlePos = vecEyePosition +
( player_paint_shoot_pos_forward_scale.GetFloat() * vecForward ) +
( player_paint_shoot_pos_right_scale.GetFloat() * vecRight ) -
( player_paint_shoot_pos_up_scale.GetFloat() * vecUp );
#ifndef CLIENT_DLL
if( debug_player_paint_shoot_pos.GetBool() )
{
NDebugOverlay::Sphere( vecMuzzlePos, 4.0f, 255, 255, 255, false, 0 );
}
#endif
return vecMuzzlePos;
}
void CPortal_Player::DecayEyeOffset()
{
const float flDecay = ExponentialDecay( 0.2f, gpGlobals->frametime );
m_PortalLocal.m_vEyeOffset *= flDecay; // Decay eye offset
}
void CPortal_Player::SetEyeOffset( const Vector& vOldOrigin, const Vector& vNewOrigin )
{
const Vector vOriginOffset = vOldOrigin - vNewOrigin;
m_PortalLocal.m_vEyeOffset = vOriginOffset;
}
#ifdef DEBUG_STICK_CAM
ConVar debug_client_cam("debug_client_cam", "0", FCVAR_REPLICATED);
#endif
Vector CPortal_Player::EyePosition()
{
const Vector vEyeOffset = m_PortalLocal.m_vEyeOffset;
Vector vUp = GetViewOffset();
Vector vEyePosition = GetAbsOrigin() + vEyeOffset + vUp;
#ifdef DEBUG_STICK_CAM
#ifdef GAME_DLL
debug_client_cam.SetValue(m_nButtons & IN_GRENADE2);
if( m_nButtons & IN_GRENADE1 )
#else
if( debug_client_cam.GetBool() )
#endif
{
// Yellow Line: Offset
//NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vEyeOffset, 255, 255, 0, true, 100 );
// White Cross: Origin
//NDebugOverlay::Cross( GetAbsOrigin() + vUp, 5, 255, 255, 255, true, 100 );
// Red Cross: Eye Position
// NDebugOverlay::Cross( vEyePosition , 5, 255, 0, 0, true, 100 );
// Teal Line: Eye offset
// NDebugOverlay::Line( vEyePosition, vEyePosition - vEyeOffset, 0, 255, 255, true, 100 );
// Green Line: View offset (orign to eye)
//NDebugOverlay::Line( GetAbsOrigin() + vEyeOffset, GetAbsOrigin() + vEyeOffset + vUp, 0, 255, 0, true, 100 );
// Vector vOriginToCenter = (GetHullMaxs() + GetHullMins()) * 0.5f;
// Vector vMoveCenter = GetAbsOrigin() + vOriginToCenter;
// NDebugOverlay::Cross( GetAbsOrigin(), 5, 255, 0, 255, true, 1000.f );
}
if( m_nButtons & IN_LOOKSPIN )
{
debugoverlay->ClearAllOverlays();
}
#endif
return vEyePosition;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
// FIXME: Bring this back for DLC2
#define paintgun_ammo_type 0 //extern ConVar paintgun_ammo_type;
void CPortal_Player::ItemPostFrame()
{
BaseClass::ItemPostFrame();
CBaseCombatWeapon* pActiveWeapon = GetActiveWeapon();
if( m_hUseEntity != NULL &&
paintgun_ammo_type != PAINT_AMMO_NONE &&
pActiveWeapon != NULL &&
FClassnameIs( pActiveWeapon, "weapon_paintgun" ) )
{
#ifdef CLIENT_DLL
// Predicting this weapon
if( pActiveWeapon->IsPredicted() )
#endif
{
pActiveWeapon->ItemPostFrame( );
}
}
#if defined( GAME_DLL )
if ( m_bPlayUseDenySound )
{
m_bPlayUseDenySound = false;
if( GetActivePortalWeapon() )
{
EmitSound( "PortalPlayer.UseDeny" );
}
else
{
EmitSound( "PortalPlayer.UseDenyNoGun" );
}
}
#endif
}
void CPortal_Player::PostThink( void )
{
BaseClass::PostThink();
// this was all moved from portal_player (GAME_DLL), but there are client checks in the code? don't run it just in case
#if defined( GAME_DLL )
// 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 );
UpdatePortalPlaneSounds();
UpdateWooshSounds();
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
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;
}
// Try to update our crosshair
m_bIsHoldingSomething = IsHoldingEntity( NULL );
IncrementDistanceTaken();
#endif //GAME_DLL
// Reset our jumping status
m_bJumpWasPressedWhenForced = false;
// Reset whether we prevented crouch jump
m_PortalLocal.m_bPreventedCrouchJumpThisFrame = false;
// Update in air state
UpdateInAirState();
#if defined( CLIENT_DLL )
engine->GetViewAngles( pl.v_angle );
#else
if( pl.fixangle == FIXANGLE_NONE )
#endif
{
Reorient( pl.v_angle );
}
#if defined( CLIENT_DLL )
engine->SetViewAngles( pl.v_angle );
#else
SwapThink();
#endif
}
void CPortal_Player::SetAirDuck( bool bDuckedInAir )
{
m_PortalLocal.m_bDuckedInAir = bDuckedInAir;
}
void CPortal_Player::UnDuck( void )
{
#ifndef CLIENT_DLL
FirePlayerProxyOutput( "OnUnDuck", variant_t(), this, this );
#endif
}
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA_NO_BASE( CPortalPlayerShared )
DEFINE_PRED_FIELD( m_nPlayerCond, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
#endif // CLIENT_DLL
CPortalPlayerShared::CPortalPlayerShared()
{
}
void CPortalPlayerShared::Init( CPortal_Player *pPlayer )
{
m_pOuter = pPlayer;
m_bLoadoutUnavailable = false;
}
//-----------------------------------------------------------------------------
// Purpose: Add a condition and duration
// duration of PERMANENT_CONDITION means infinite duration
//-----------------------------------------------------------------------------
void CPortalPlayerShared::AddCond( int nCond, float flDuration /* = PERMANENT_CONDITION */ )
{
Assert( nCond >= 0 && nCond < PORTAL_COND_LAST );
m_nPlayerCond |= (1<<nCond);
m_flCondExpireTimeLeft[nCond] = flDuration;
OnConditionAdded( nCond );
}
//-----------------------------------------------------------------------------
// Purpose: Forcibly remove a condition
//-----------------------------------------------------------------------------
void CPortalPlayerShared::RemoveCond( int nCond )
{
Assert( nCond >= 0 && nCond < PORTAL_COND_LAST );
m_nPlayerCond &= ~(1<<nCond);
m_flCondExpireTimeLeft[nCond] = 0;
OnConditionRemoved( nCond );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CPortalPlayerShared::InCond( int nCond )
{
Assert( nCond >= 0 && nCond < PORTAL_COND_LAST );
return ( ( m_nPlayerCond & (1<<nCond) ) != 0 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CPortalPlayerShared::GetConditionDuration( int nCond )
{
Assert( nCond >= 0 && nCond < PORTAL_COND_LAST );
if ( InCond( nCond ) )
{
return m_flCondExpireTimeLeft[nCond];
}
return 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPortalPlayerShared::DebugPrintConditions( void )
{
const char *szDll;
#ifndef CLIENT_DLL
szDll = "Server";
#else
szDll = "Client";
#endif
Msg( "( %s ) Conditions for player ( %d )\n", szDll, m_pOuter->entindex() );
int i;
int iNumFound = 0;
for ( i=0;i<PORTAL_COND_LAST;i++ )
{
if ( m_nPlayerCond & (1<<i) )
{
if ( m_flCondExpireTimeLeft[i] == PERMANENT_CONDITION )
{
Msg( "( %s ) Condition %d - ( permanent cond )\n", szDll, i );
}
else
{
Msg( "( %s ) Condition %d - ( %.1f left )\n", szDll, i, m_flCondExpireTimeLeft[i] );
}
iNumFound++;
}
}
if ( iNumFound == 0 )
{
Msg( "( %s ) No active conditions\n", szDll );
}
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPortalPlayerShared::OnPreDataChanged( void )
{
m_nOldConditions = m_nPlayerCond;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPortalPlayerShared::OnDataChanged( void )
{
// Update conditions from last network change
if ( m_nOldConditions != m_nPlayerCond )
{
UpdateConditions();
m_nOldConditions = m_nPlayerCond;
}
}
//-----------------------------------------------------------------------------
// Purpose: check the newly networked conditions for changes
//-----------------------------------------------------------------------------
void CPortalPlayerShared::UpdateConditions( void )
{
int nCondChanged = m_nPlayerCond ^ m_nOldConditions;
int nCondAdded = nCondChanged & m_nPlayerCond;
int nCondRemoved = nCondChanged & m_nOldConditions;
int i;
for ( i=0;i<PORTAL_COND_LAST;i++ )
{
if ( nCondAdded & (1<<i) )
{
OnConditionAdded( i );
}
else if ( nCondRemoved & (1<<i) )
{
OnConditionRemoved( i );
}
}
}
#endif // CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Remove any conditions affecting players
//-----------------------------------------------------------------------------
void CPortalPlayerShared::RemoveAllCond()
{
int i;
for ( i=0;i<PORTAL_COND_LAST;i++ )
{
if ( m_nPlayerCond & (1<<i) )
{
RemoveCond( i );
}
}
// Now remove all the rest
m_nPlayerCond = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Called on both client and server. Server when we add the bit,
// and client when it receives the new cond bits and finds one added
//-----------------------------------------------------------------------------
void CPortalPlayerShared::OnConditionAdded( int nCond )
{
float flDuration = 0.0f;
switch( nCond )
{
case PORTAL_COND_TAUNTING:
{
// The reason m_bTauntRemoteView isn't evaluated here is because it is evaluated at the start of a normal taunt only in StartTaunt()
// None of the other conditions go through the start taunt function when they are set
// a good thing to do would be to make all conditions that make IsTaunting() true, go through the same code path
}
break;
case PORTAL_COND_DEATH_GIB:
{
if ( m_pOuter )
{
flDuration = 3.5f; // change this to animation length
m_pOuter->m_fTauntCameraDistance = portal_deathcam_dist.GetFloat();
// if the player is not already taunting, set the remote view to false so when we die, we don't get residual data (because it's only evaluated at the start of a normal taunt on the server)
if ( !InCond( PORTAL_COND_TAUNTING ) )
{
m_pOuter->m_bTauntRemoteView = false;
}
}
}
break;
case PORTAL_COND_DEATH_CRUSH:
{
if ( m_pOuter )
{
m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_MP_DEATH_CRUSH );
flDuration = 3.0f; // change this to animation length
m_pOuter->m_fTauntCameraDistance = portal_deathcam_dist.GetFloat();
// if the player is not already taunting, set the remote view to false so when we die, we don't get residual data (because it's only evaluated at the start of a normal taunt on the server)
if ( !InCond( PORTAL_COND_TAUNTING ) )
{
m_pOuter->m_bTauntRemoteView = false;
}
}
}
break;
case PORTAL_COND_DROWNING:
{
if ( m_pOuter )
{
// if the player is not already taunting, set the remote view to false so when we die, we don't get residual data (because it's only evaluated at the start of a normal taunt on the server)
if ( !InCond( PORTAL_COND_TAUNTING ) )
{
m_pOuter->m_bTauntRemoteView = false;
}
m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_MP_DROWNING_PRIMARY );
flDuration = 3.5f; // change this to animation length
m_pOuter->m_fTauntCameraDistance = portal_deathcam_dist.GetFloat();
}
}
break;
case PORTAL_COND_POINTING:
{
m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY );
}
break;
default:
break;
}
#ifdef GAME_DLL
// assign how long the taunt should last manually
if ( flDuration > 0.0f )
{
m_flTauntRemoveTime = gpGlobals->curtime + flDuration;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Called on both client and server. Server when we remove the bit,
// and client when it receives the new cond bits and finds one removed
//-----------------------------------------------------------------------------
void CPortalPlayerShared::OnConditionRemoved( int nCond )
{
switch( nCond )
{
case PORTAL_COND_TAUNTING:
{
if ( m_pOuter )
{
#ifdef GAME_DLL
if ( m_pOuter->m_hRemoteTauntCamera.Get() )
{
m_pOuter->m_hRemoteTauntCamera.Get()->TauntedByPlayerFinished( m_pOuter );
m_pOuter->m_hRemoteTauntCamera = NULL;
}
#endif
// UNDONE: Do NOT set to false after removing the taunt condition!
// We still need to have this state true when it turns off the taunt camera on the client!
//m_bTauntRemoteView = false;
}
}
break;
case PORTAL_COND_DROWNING:
{
if ( m_pOuter )
{
#ifdef GAME_DLL
if ( m_pOuter->m_hRemoteTauntCamera.Get() )
{
m_pOuter->m_hRemoteTauntCamera.Get()->TauntedByPlayerFinished( m_pOuter );
m_pOuter->m_hRemoteTauntCamera = NULL;
}
#endif
m_pOuter->m_bTauntRemoteView = false;
}
}
break;
default:
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Runs SERVER SIDE only Condition Think
// If a player needs something to be updated no matter what do it here (invul, etc).
//-----------------------------------------------------------------------------
void CPortalPlayerShared::ConditionGameRulesThink( void )
{
#ifdef GAME_DLL
int i;
for ( i=0;i<PORTAL_COND_LAST;i++ )
{
if ( m_nPlayerCond & (1<<i) )
{
// Ignore permanent conditions
if ( m_flCondExpireTimeLeft[i] != PERMANENT_CONDITION )
{
float flReduction = gpGlobals->frametime;
m_flCondExpireTimeLeft[i] = MAX( m_flCondExpireTimeLeft[i] - flReduction, 0 );
if ( m_flCondExpireTimeLeft[i] == 0 )
{
RemoveCond( i );
}
}
}
}
// Taunt
if ( InCond( PORTAL_COND_TAUNTING ) )
{
if ( gpGlobals->curtime > m_flTauntRemoveTime )
{
RemoveCond( PORTAL_COND_TAUNTING );
m_pOuter->SetTeamTauntState( TEAM_TAUNT_NONE );
// HEY JEEP: Should this be at the start or end of a taunt? Could do remove time / 2 to be more fair?
// Increment the air taunt count if the player has no ground entity
if ( m_pOuter->GetGroundEntity() == NULL )
{
m_pOuter->m_nAirTauntCount++;
// Award 'With Style' if they reach two air taunts without getting a ground entity set.
if ( m_pOuter->m_nAirTauntCount >= 2 )
{
UTIL_RecordAchievementEvent( "ACH.WITH_STYLE", m_pOuter );
}
}
}
}
if ( GameRules()->IsMultiplayer() )
{
// Death Crush
if ( InCond( PORTAL_COND_DEATH_CRUSH ) )
{
if ( gpGlobals->curtime > m_flTauntRemoveTime )
{
Vector vecOrigin = m_pOuter->GetAbsOrigin();
CPVSFilter filter( vecOrigin );
for ( int i = 0; i < 4; i++ )
{
Vector gibVelocity = RandomVector(-100,100);
int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
float flPropLifeTime = 20.f;
te->BreakModel( filter, 0.0, vecOrigin, m_pOuter->GetAbsAngles(), Vector(40,40,40), gibVelocity, iModelIndex, 150, 4, flPropLifeTime, BREAK_METAL );
}
ExplosionCreate( m_pOuter->WorldSpaceCenter(), vec3_angle, m_pOuter, 500, 300.f,
SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS|
SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
RemoveCond( PORTAL_COND_DEATH_CRUSH );
}
}
// Death Gib
if ( InCond( PORTAL_COND_DEATH_GIB ) )
{
if ( gpGlobals->curtime > m_flTauntRemoveTime )
{
RemoveCond( PORTAL_COND_DEATH_GIB );
}
}
if ( InCond( PORTAL_COND_DROWNING ) )
{
if ( gpGlobals->curtime > m_flTauntRemoveTime )
{
RemoveCond( PORTAL_COND_DROWNING );
}
}
}
#endif
}
void CPortal_Player::SetInTractorBeam( CTrigger_TractorBeam *pTractorBeam )
{
if ( !pTractorBeam )
return;
CTrigger_TractorBeam *pNewPrimaryTractorBeam = pTractorBeam;
// Special check if we're already in a tbeam
if ( m_PortalLocal.m_nTractorBeamCount > 0 )
{
CTrigger_TractorBeam *pOldTractorBeam = m_PortalLocal.m_hTractorBeam.Get();
if ( pOldTractorBeam )
{
CPortal_Base2D *pPortal = m_hPortalEnvironment.Get();
if ( pPortal && UTIL_Portal_EntityIsInPortalHole( pPortal, this ) )
{
// If we're in a portal that points the opposite way, don't take this as the primary tbeam
if ( pPortal->m_plane_Origin.normal.Dot( pTractorBeam->GetForceDirection() ) < -0.95f )
{
pNewPrimaryTractorBeam = pOldTractorBeam;
}
}
else if ( pOldTractorBeam->GetForceDirection().Dot( pTractorBeam->GetForceDirection() ) < -0.95f )
{
// If they new is opposite the old, don't take this as the primary tbeam
pNewPrimaryTractorBeam = pOldTractorBeam;
}
}
}
m_PortalLocal.m_hTractorBeam = pNewPrimaryTractorBeam;
m_PortalLocal.m_nTractorBeamCount++;
m_Local.m_bSlowMovement = true;
m_Local.m_fTBeamEndTime = 0.0f;
SetGravity( FLT_MIN );
#ifdef GAME_DLL
triggerevent_t event;
if ( PhysGetTriggerEvent( &event, pTractorBeam ) && event.pObject )
{
// these all get done again on save/load, so check
event.pObject->EnableGravity( false );
}
#endif
}
void CPortal_Player::SetLeaveTractorBeam( CTrigger_TractorBeam *pTractorBeam, bool bKeepFloating )
{
if ( !pTractorBeam || m_PortalLocal.m_hTractorBeam.Get() == pTractorBeam )
{
m_PortalLocal.m_hTractorBeam = NULL;
}
m_PortalLocal.m_nTractorBeamCount--;
Assert( m_PortalLocal.m_nTractorBeamCount >= 0 );
if ( m_PortalLocal.m_nTractorBeamCount <= 0 )
{
m_PortalLocal.m_nTractorBeamCount = 0;
// Don't turn off gravity if we're going through a portal that the tbeam is also going through
if ( !bKeepFloating )
{
m_Local.m_bSlowMovement = false;
SetGravity( 1.0f );
#ifdef GAME_DLL
triggerevent_t event;
if ( PhysGetTriggerEvent( &event, pTractorBeam ) && event.pObject )
{
event.pObject->EnableGravity( true );
}
#endif
}
m_Local.m_fTBeamEndTime = gpGlobals->curtime;
}
else if ( m_PortalLocal.m_hTractorBeam == NULL )
{
// We're probably touching another TBeam, lets find it
Ray_t ray;
ray.Init( GetAbsOrigin(), GetAbsOrigin(), GetPlayerMins(), GetPlayerMaxs() );
for ( int i = 0; i < ITriggerTractorBeamAutoList::AutoList().Count(); ++i )
{
CTrigger_TractorBeam *pBeam = static_cast< CTrigger_TractorBeam* >( ITriggerTractorBeamAutoList::AutoList()[ i ] );
if ( pBeam == pTractorBeam )
continue;
trace_t tr;
enginetrace->ClipRayToEntity( ray, MASK_SHOT, pBeam, &tr );
if ( tr.startsolid || ( tr.fraction < 1.0f && tr.m_pEnt == pTractorBeam ) )
{
m_PortalLocal.m_hTractorBeam = pBeam;
break;
}
}
}
}
void CPortal_Player::SetHullHeight( float flHeight )
{
m_flHullHeight = flHeight;
}
bool CPortal_Player::IsTaunting()
{
return ( m_Shared.InCond( PORTAL_COND_TAUNTING ) ||
m_Shared.InCond( PORTAL_COND_DROWNING ) ||
m_Shared.InCond( PORTAL_COND_DEATH_CRUSH ) ||
m_Shared.InCond( PORTAL_COND_DEATH_GIB ) );
}