mirror of
https://github.com/thug1src/thug.git
synced 2025-01-22 05:43:47 +00:00
8397 lines
289 KiB
C++
8397 lines
289 KiB
C++
|
//****************************************************************************
|
||
|
//* MODULE: Sk/Components
|
||
|
//* FILENAME: SkaterCorePhysicsComponent.cpp
|
||
|
//* OWNER: Dan
|
||
|
//* CREATION DATE: 3/21/3
|
||
|
//****************************************************************************
|
||
|
|
||
|
#include <sk/objects/rail.h>
|
||
|
#include <sk/objects/skatercareer.h>
|
||
|
#include <sk/objects/gap.h>
|
||
|
#include <sk/components/skatercorephysicscomponent.h>
|
||
|
#include <sk/components/skatersoundcomponent.h>
|
||
|
#include <sk/components/skaterrotatecomponent.h>
|
||
|
#include <sk/components/skaterscorecomponent.h>
|
||
|
#include <sk/components/skatermatrixqueriescomponent.h>
|
||
|
#include <sk/components/skaterendruncomponent.h>
|
||
|
#include <sk/components/skaterbalancetrickcomponent.h>
|
||
|
#include <sk/components/skaterloopingsoundcomponent.h>
|
||
|
#include <sk/components/skaterstatecomponent.h>
|
||
|
#include <sk/components/skaterflipandrotatecomponent.h>
|
||
|
#include <sk/components/skaterphysicscontrolcomponent.h>
|
||
|
#include <sk/components/skatergapcomponent.h>
|
||
|
|
||
|
#include <gel/objtrack.h>
|
||
|
#include <gel/object/compositeobject.h>
|
||
|
#include <gel/object/compositeobjectmanager.h>
|
||
|
#include <gel/scripting/checksum.h>
|
||
|
#include <gel/scripting/script.h>
|
||
|
#include <gel/scripting/struct.h>
|
||
|
#include <gel/scripting/symboltable.h>
|
||
|
#include <gel/components/inputcomponent.h>
|
||
|
#include <gel/components/skitchcomponent.h>
|
||
|
#include <gel/components/triggercomponent.h>
|
||
|
#include <gel/components/trickcomponent.h>
|
||
|
#include <gel/components/railmanagercomponent.h>
|
||
|
#include <gel/components/motioncomponent.h>
|
||
|
#include <gel/components/shadowcomponent.h>
|
||
|
#include <gel/components/movablecontactcomponent.h>
|
||
|
#include <gel/components/walkcomponent.h>
|
||
|
#include <gel/collision/collcache.h>
|
||
|
|
||
|
#include <sk/engine/contact.h>
|
||
|
#include <sk/modules/skate/skate.h>
|
||
|
#include <sk/parkeditor2/parked.h>
|
||
|
#include <sk/gamenet/gamenet.h>
|
||
|
|
||
|
#include <core/math/slerp.h>
|
||
|
|
||
|
#define FLAGEXCEPTION(X) GetObject()->SelfEvent(X)
|
||
|
|
||
|
void TrackingLine(int type, Mth::Vector &start, Mth::Vector &end)
|
||
|
{
|
||
|
if (Script::GetInt(CRCD(0x19fb78fa,"output_tracking_lines")))
|
||
|
{
|
||
|
// Gfx::AddDebugLine(start, end, 0xffffff);
|
||
|
printf ("Tracking%d %.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n",type, start[X], start[Y], start[Z], end[X], end[Y], end[Z]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern bool g_CheatsEnabled;
|
||
|
|
||
|
namespace Obj
|
||
|
{
|
||
|
Mth::Vector acid_hold;
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
CBaseComponent* CSkaterCorePhysicsComponent::s_create()
|
||
|
{
|
||
|
return static_cast< CBaseComponent* >( new CSkaterCorePhysicsComponent );
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
CSkaterCorePhysicsComponent::CSkaterCorePhysicsComponent() : CBaseComponent()
|
||
|
{
|
||
|
SetType( CRC_SKATERCOREPHYSICS );
|
||
|
|
||
|
mp_input_component = NULL;
|
||
|
mp_trigger_component = NULL;
|
||
|
mp_sound_component = NULL;
|
||
|
mp_trick_component = NULL;
|
||
|
mp_rotate_component = NULL;
|
||
|
mp_score_component = NULL;
|
||
|
mp_balance_trick_component = NULL;
|
||
|
mp_state_component = NULL;
|
||
|
mp_movable_contact_component = NULL;
|
||
|
mp_physics_control_component = NULL;
|
||
|
mp_walk_component = NULL;
|
||
|
|
||
|
mp_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
CSkaterCorePhysicsComponent::~CSkaterCorePhysicsComponent()
|
||
|
{
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(mp_coll_cache);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::InitFromStructure( Script::CStruct* pParams )
|
||
|
{
|
||
|
Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterCorePhysicsComponent added to non-skater composite object"));
|
||
|
|
||
|
m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
|
||
|
|
||
|
GetPos().Set(0.0f, 0.0f, 0.0f);
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
GetMatrix().Identity();
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
m_display_normal.Set(0.0f, 1.0f, 0.0f);
|
||
|
m_current_normal = m_display_normal;
|
||
|
m_last_display_normal = m_display_normal;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::RefreshFromStructure( Script::CStruct* pParams )
|
||
|
{
|
||
|
InitFromStructure(pParams);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::Finalize ( )
|
||
|
{
|
||
|
mp_input_component = GetInputComponentFromObject(GetObject());
|
||
|
mp_sound_component = GetSkaterSoundComponentFromObject(GetObject());
|
||
|
mp_trigger_component = GetTriggerComponentFromObject(GetObject());
|
||
|
mp_trick_component = GetTrickComponentFromObject(GetObject());
|
||
|
mp_rotate_component = GetSkaterRotateComponentFromObject(GetObject());
|
||
|
mp_score_component = GetSkaterScoreComponentFromObject(GetObject());
|
||
|
mp_balance_trick_component = GetSkaterBalanceTrickComponentFromObject(GetObject());
|
||
|
mp_state_component = GetSkaterStateComponentFromObject(GetObject());
|
||
|
mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
|
||
|
mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
|
||
|
mp_walk_component = GetWalkComponentFromObject(GetObject());
|
||
|
|
||
|
Dbg_Assert(mp_input_component);
|
||
|
Dbg_Assert(mp_sound_component);
|
||
|
Dbg_Assert(mp_trigger_component);
|
||
|
Dbg_Assert(mp_trick_component);
|
||
|
Dbg_Assert(mp_rotate_component);
|
||
|
Dbg_Assert(mp_score_component);
|
||
|
Dbg_Assert(mp_balance_trick_component);
|
||
|
Dbg_Assert(mp_movable_contact_component);
|
||
|
Dbg_Assert(mp_physics_control_component);
|
||
|
Dbg_Assert(mp_walk_component);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::Update()
|
||
|
{
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
m_frame_length = Tmr::FrameLength();
|
||
|
|
||
|
// bool up = GetVel()[Y] > 0.0f;
|
||
|
|
||
|
m_landed_this_frame = false;
|
||
|
|
||
|
m_began_frame_in_lip_state = GetState() == LIP;
|
||
|
m_began_frame_in_transfer = GetFlag(SPINE_PHYSICS);
|
||
|
|
||
|
handle_tensing();
|
||
|
|
||
|
limit_speed();
|
||
|
|
||
|
SetFlagFalse(SNAPPED_OVER_CURB); // this flag only gets set for one frame, to fix camera snaps
|
||
|
SetFlagFalse(SNAPPED); // this flag only gets set for one frame, to fix camera snaps
|
||
|
|
||
|
setup_default_collision_cache();
|
||
|
|
||
|
switch (GetState())
|
||
|
{
|
||
|
case GROUND:
|
||
|
// Mick: Remember the last ground position for calculating which side of the rail we're on later.
|
||
|
// Note, we do this BEFORE we move as the movement might take us off the ground
|
||
|
m_last_ground_pos = GetPos();
|
||
|
do_on_ground_physics();
|
||
|
maybe_skitch();
|
||
|
break;
|
||
|
|
||
|
case AIR:
|
||
|
do_in_air_physics();
|
||
|
if (GetState() == GROUND)
|
||
|
{
|
||
|
m_landed_this_frame = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WALL:
|
||
|
do_wallride_physics();
|
||
|
break;
|
||
|
|
||
|
case LIP:
|
||
|
do_lip_physics();
|
||
|
break;
|
||
|
|
||
|
case RAIL:
|
||
|
do_rail_physics();
|
||
|
break;
|
||
|
|
||
|
case WALLPLANT:
|
||
|
do_wallplant_physics();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
handle_post_transfer_limit_overrides();
|
||
|
|
||
|
// NOTE: moved this call from after CSkaterRotateComponent::Update to before it
|
||
|
maybe_stick_to_rail();
|
||
|
|
||
|
update_special_friction_index();
|
||
|
|
||
|
// if (up && GetVel()[Y] < 0.0f)
|
||
|
// {
|
||
|
// DUMPF(GetPos()[Y]);
|
||
|
// }
|
||
|
|
||
|
if (m_vert_air_last_frame != (GetState() == AIR && GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS)))
|
||
|
{
|
||
|
m_vert_air_last_frame = !m_vert_air_last_frame;
|
||
|
GetObject()->BroadcastEvent(m_vert_air_last_frame ? CRCD(0xf225fe69, "SkaterEnterVertAir") : CRCD(0x5e27200a, "SkaterExitVertAir"));
|
||
|
}
|
||
|
|
||
|
#ifdef __USER_DAN__
|
||
|
// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[Z], RED, 0, 1);
|
||
|
// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[X], BLUE, 0, 1);
|
||
|
// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[Y], GREEN, 0, 1);
|
||
|
#endif
|
||
|
|
||
|
CFeeler::sClearDefaultCache();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
CBaseComponent::EMemberFunctionResult CSkaterCorePhysicsComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
|
||
|
{
|
||
|
switch ( Checksum )
|
||
|
{
|
||
|
// @script | Jump |
|
||
|
// @flag BonelessHeight |
|
||
|
case CRCC(0x584cf9e9, "Jump"):
|
||
|
do_jump(pParams);
|
||
|
break;
|
||
|
|
||
|
// @script | Flipped | true if flipped
|
||
|
case CRCC(0xc7a712c, "Flipped"):
|
||
|
return GetFlag(FLIPPED) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | Switched | true if switched
|
||
|
case CRCC(0x8f66b80b, "Switched"):
|
||
|
return IsSwitched() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | Crouched | true if skater crouched
|
||
|
case CRCC(0x4adc6c2a, "Crouched"):
|
||
|
return GetFlag(TENSE) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | OnGround | true if current state is on ground
|
||
|
case CRCC(0x5ea287f2, "OnGround"):
|
||
|
return GetState() == GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | InAir | true if current state is in air
|
||
|
case CRCC(0x3527fc07, "InAir"):
|
||
|
return GetState() == AIR ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | OnWall | true if current state is on wall
|
||
|
case CRCC(0xa32c1a15, "OnWall"):
|
||
|
return GetState() == WALL ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | OnLip | true if current state is on lip
|
||
|
case CRCC(0x5cb1fbd8, "OnLip"):
|
||
|
return GetState() == LIP ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | OnRail | true if current state is on rail
|
||
|
case CRCC(0xe9851e62, "OnRail"):
|
||
|
return GetState() == RAIL ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | InWallplant | true if current state is in wallplant
|
||
|
case CRCC(0xa64dcf8b, "InWallplant"):
|
||
|
return GetState() == WALLPLANT ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | FirstTimeOnThisRail | true if this is the first time we grinded this rail wihout doing something else
|
||
|
// like skating or wallriding
|
||
|
case CRCC(0x262d42d5,"FirstTimeOnThisRail"):
|
||
|
return GetFlag(NEW_RAIL) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | StartSkitch | Start skitch physics
|
||
|
// This will send a SkitchOn exception to the object
|
||
|
case CRCC(0xca6b3809, "StartSkitch"):
|
||
|
start_skitch();
|
||
|
break;
|
||
|
|
||
|
// @script | Skitching | Returns True if we are skitching
|
||
|
case CRCC(0x9c6a7e41, "Skitching"):
|
||
|
return GetFlag(SKITCHING) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | StopSkitch | Stop Skitch physics
|
||
|
// this wills send a SkitchOff exeception to the object
|
||
|
case CRCC(0x32dcc9cf, "StopSkitch"):
|
||
|
StopSkitch();
|
||
|
break;
|
||
|
|
||
|
// @script | CancelWallpush | Cancels the current wallpush event
|
||
|
case CRCC(0x5e8f9e77, "CancelWallpush"):
|
||
|
SetFlagTrue(CANCEL_WALL_PUSH);
|
||
|
break;
|
||
|
|
||
|
// @script | AirTimeLessThan | true if the air time is
|
||
|
// less than the specified time
|
||
|
// @uparm 1.0 | time (default is milliseconds)
|
||
|
// @flag seconds | time in seconds
|
||
|
// @flag frames | time in frames
|
||
|
case CRCC(0xc890a84, "AirTimeLessThan"):
|
||
|
// @script | AirTimeGreaterThan | true if the air time is
|
||
|
// greater than the specified time
|
||
|
// @uparm 1.0 | time (default is milliseconds)
|
||
|
// @flag seconds | time in seconds
|
||
|
// @flag frames | time in frames
|
||
|
case CRCC(0xbbf2b570, "AirTimeGreaterThan"):
|
||
|
{
|
||
|
float t = 0;
|
||
|
pParams->GetFloat(NO_NAME, &t);
|
||
|
|
||
|
Tmr::Time TestTime;
|
||
|
if (pParams->ContainsFlag(CRCD(0xd029f619, "seconds")) || pParams->ContainsFlag(CRCD(0x49e0ee96, "second")))
|
||
|
{
|
||
|
TestTime = static_cast< Tmr::Time >(t * 1000);
|
||
|
}
|
||
|
else if (pParams->ContainsFlag(CRCD(0x19176c5, "frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "frame")))
|
||
|
{
|
||
|
TestTime = static_cast< Tmr::Time >(t * (1000 / 60));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TestTime = static_cast< Tmr::Time >(t);
|
||
|
}
|
||
|
|
||
|
Tmr::Time AirTime;
|
||
|
if (GetState() == AIR || GetState() == WALL)
|
||
|
{
|
||
|
AirTime = Tmr::ElapsedTime(m_went_airborne_time);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AirTime = m_landed_time - m_went_airborne_time;
|
||
|
}
|
||
|
|
||
|
if (Checksum == CRCD(0xc890a84, "AirTimeLessThan"))
|
||
|
{
|
||
|
return AirTime < TestTime ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return AirTime > TestTime ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// @script | GetAirTime | Puts the air time, in seconds, into a param called airtime
|
||
|
case CRCC(0xde583e34, "GetAirTime"):
|
||
|
{
|
||
|
Tmr::Time AirTime;
|
||
|
if (GetState() == AIR || GetState() == WALL)
|
||
|
{
|
||
|
AirTime = Tmr::ElapsedTime(m_went_airborne_time);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AirTime = m_landed_time - m_went_airborne_time;
|
||
|
}
|
||
|
pScript->GetParams()->AddFloat(CRCD(0xad6bcdb4, "AirTime"), AirTime * (1.0f / 1000.0f));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | GetAirTimeLeft | Puts the amount of air time left before landing, in seconds, into a param called AirTimeLeft
|
||
|
case CRCC(0x1996b797, "GetAirTimeLeft"):
|
||
|
{
|
||
|
pScript->GetParams()->AddFloat(CRCD(0x7e2a8993, "AirTimeLeft"),
|
||
|
Mth::ClampMin(calculate_time_to_reach_height(GetPos()[Y] - mp_state_component->m_height, GetPos()[Y], GetVel()[Y]), 0.0f));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | Braking | true if skater is braking
|
||
|
case CRCC(0x1f8bbd05, "Braking"):
|
||
|
return is_braking() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
case CRCC(0xebbcf455, "CanBrakeOff"):
|
||
|
m_pressing_down_brakes = false;
|
||
|
break;
|
||
|
|
||
|
case CRCC(0xbc19e291, "CanBrakeOn"):
|
||
|
m_pressing_down_brakes = true;
|
||
|
break;
|
||
|
|
||
|
// @script | CanKick | true if skater can kick
|
||
|
case CRCC(0x2f66333, "CanKick"):
|
||
|
return can_kick() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | CanKickOn | sets can kick to true
|
||
|
case CRCC(0x68bf6c13, "CanKickOn"):
|
||
|
m_force_cankick_off = false;
|
||
|
break;
|
||
|
|
||
|
// @script | CanKickOff | sets can kick to false
|
||
|
case CRCC(0xe8deb0d7, "CanKickOff"):
|
||
|
m_force_cankick_off = true;
|
||
|
break;
|
||
|
|
||
|
// @script | ForceAutokickOn | turns on auto kick
|
||
|
case CRCC(0x34dcfc97, "ForceAutokickOn"):
|
||
|
m_auto_kick = true;
|
||
|
break;
|
||
|
|
||
|
// @script | ForceAutoKickOff | turns off auto kick
|
||
|
case CRCC(0x257947e, "ForceAutokickOff"):
|
||
|
m_auto_kick = false;
|
||
|
break;
|
||
|
|
||
|
// @script | AutoKickIsOff | true if auto kick is off
|
||
|
case CRCC(0x1baa1d9, "AutoKickIsOff"):
|
||
|
return m_auto_kick == false ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | RestoreAutokick | restores auto kick to
|
||
|
// original preferences
|
||
|
case CRCC(0x9fcfdfeb, "RestoreAutokick"):
|
||
|
m_auto_kick = Mdl::Skate::Instance()->mp_controller_preferences[GetSkater()->m_skater_number].AutoKickOn;
|
||
|
break;
|
||
|
|
||
|
// @script | DoCarPlantBoost | boost after doing car plant
|
||
|
case CRCC(0x17846595, "DoCarPlantBoost"):
|
||
|
{
|
||
|
GetVel()[Y] = Mth::ClampMin(GetVel()[Y], 0.0f);
|
||
|
GetVel()[Y] += GetPhysicsFloat(CRCD(0xcb49b3f2, "Carplant_upward_boost"));
|
||
|
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
Mth::Vector front = GetVel();
|
||
|
front[Y] = 0.0f;
|
||
|
if (front.LengthSqr() < 10.0f * 10.0f)
|
||
|
{
|
||
|
front = GetMatrix()[Z];
|
||
|
front[Y] = 0.0f;
|
||
|
if (front.LengthSqr() < 0.01f * 0.01f)
|
||
|
{
|
||
|
front.Set(1.0f, 0.0f, 1.0f);
|
||
|
}
|
||
|
}
|
||
|
front.Normalize();
|
||
|
GetVel() += front * GetPhysicsFloat(CRCD(0xb7422173, "Carplant_forward_boost"));
|
||
|
|
||
|
DUMP_VELOCITY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | HandleLipOllieDirection |
|
||
|
case CRCC(0x259cb90d, "HandleLipOllieDirection"):
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
Mth::Vector out = GetMatrix()[Z];
|
||
|
out[Y] = 0.0f;
|
||
|
out.Normalize();
|
||
|
Mth::Vector along(out[Z], 0.0f, -out[X], 0.0f);
|
||
|
|
||
|
// We've jumped off a lip, so we want to give us some velocity to the left
|
||
|
if (control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
// don't allow right jumping
|
||
|
return CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
else if (BREAK_SPINE_BUTTONS)
|
||
|
{
|
||
|
GetVel() += out * GetPhysicsFloat(CRCD(0x1f96c224, "Lip_side_hop_speed"));
|
||
|
DUMP_VELOCITY;
|
||
|
return CBaseComponent::MF_TRUE;
|
||
|
}
|
||
|
else if (control_pad.m_left.GetPressed()
|
||
|
&& static_cast< int >(control_pad.m_left.GetPressedTime()) > GetPhysicsInt(CRCD(0x3080e9e7, "Lip_held_jump_out_time")))
|
||
|
{
|
||
|
GetVel() += out * GetPhysicsFloat(CRCD(0xdc818b7e, "Lip_side_jump_speed"));
|
||
|
DUMP_VELOCITY;
|
||
|
return CBaseComponent::MF_TRUE;
|
||
|
}
|
||
|
if (control_pad.m_up.GetPressed()
|
||
|
&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x9b25142a, "Lip_held_jump_along_time")))
|
||
|
{
|
||
|
GetVel() -= along * GetPhysicsFloat(CRCD(0x1ab3809f, "Lip_along_jump_speed"));
|
||
|
DUMP_VELOCITY;
|
||
|
return CBaseComponent::MF_TRUE;
|
||
|
}
|
||
|
else if (control_pad.m_down.GetPressed()
|
||
|
&& static_cast< int >(control_pad.m_down.GetPressedTime()) > GetPhysicsInt(CRCD(0x9b25142a,"Lip_held_jump_along_time")))
|
||
|
{
|
||
|
GetVel() += along * GetPhysicsFloat(CRCD(0x1ab3809f, "Lip_along_jump_speed"));
|
||
|
DUMP_VELOCITY;
|
||
|
return CBaseComponent::MF_TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// @script | OrientToNormal |
|
||
|
case CRCC(0x1d1fd4f0, "OrientToNormal"):
|
||
|
m_display_normal = GetMatrix()[Y];
|
||
|
m_current_normal = GetMatrix()[Y];
|
||
|
new_normal(m_feeler.GetNormal());
|
||
|
break;
|
||
|
|
||
|
// @script | SetSpeed |
|
||
|
// @uparm 0.0 | speed in inches per second, so 3000 is very very fast
|
||
|
// The skater's max speed is about 1100 inches per second (depends on stats)
|
||
|
// this is usually used in conjunction with Overridelimits, to provide a temporary speed boost
|
||
|
case CRCC(0x383b939b, "SetSpeed"):
|
||
|
{
|
||
|
float Speed = 0.0f;
|
||
|
pParams->GetFloat(NO_NAME, &Speed);
|
||
|
float length_sqr = GetVel().LengthSqr();
|
||
|
if (length_sqr < 0.001f)
|
||
|
{
|
||
|
GetVel() = GetMatrix()[Z];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel() *= 1.0f / sqrtf(length_sqr);
|
||
|
}
|
||
|
GetVel() *= Speed;
|
||
|
DUMP_VELOCITY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | NoSpin | sets flag, disabling spin
|
||
|
case CRCC(0x54ef2b79, "NoSpin"):
|
||
|
mNoSpin = true;
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
break;
|
||
|
|
||
|
// @script | CanSpin | sets flag enabling spin
|
||
|
case CRCC(0xe2998b9, "CanSpin"):
|
||
|
mNoSpin = false;
|
||
|
break;
|
||
|
|
||
|
// @script | InBail | sets is_bailing flag
|
||
|
case CRCC(0x6fc5aae0, "InBail"):
|
||
|
{
|
||
|
SetFlagTrue(IS_BAILING);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | NotInBail | clears is_bailing flag
|
||
|
case CRCC(0xbd4303f4, "NotInBail"):
|
||
|
{
|
||
|
SetFlagFalse(IS_BAILING);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | IsInBail | true if bailing (as defined by is_bailing flag)
|
||
|
case CRCC(0xa901a50c, "IsInBail"):
|
||
|
return GetFlag(IS_BAILING) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | BailOn | turn bails on
|
||
|
case CRCC(0xe0df1f0a, "BailOn"):
|
||
|
m_bail = true;
|
||
|
break;
|
||
|
|
||
|
// @script | BailOff | turn bails off
|
||
|
case CRCC(0x8c3d7864, "BailOff"):
|
||
|
m_bail = false;
|
||
|
break;
|
||
|
|
||
|
// @script | BailIsOn | true if bails on
|
||
|
case CRCC(0xe1d5168, "BailIsOn"):
|
||
|
return m_bail ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
|
||
|
// @script | FrontTruckSparks | true if sparks should be coming from the front trucks, false if from rear
|
||
|
case CRCC(0xe0760055, "FrontTruckSparks"):
|
||
|
return m_front_truck_sparks ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
break;
|
||
|
|
||
|
// @script | SetFrontTruckSparks | sets sparks to come from front trucks
|
||
|
case CRCC(0x19d0e5fb, "SetFrontTruckSparks"):
|
||
|
m_front_truck_sparks = true;
|
||
|
break;
|
||
|
|
||
|
// @script | SetRearTruckSparks | sets sparks to come from rear trucks
|
||
|
case CRCC(0xb48ec756, "SetRearTruckSparks"):
|
||
|
m_front_truck_sparks = false;
|
||
|
break;
|
||
|
|
||
|
// @script | SetState | sets the state to the specified state
|
||
|
// @uparm name | state name (lip, air, ground)
|
||
|
case CRCC(0x948ebf96, "SetState"):
|
||
|
{
|
||
|
uint32 StateChecksum = 0;
|
||
|
pParams->GetChecksum(NO_NAME, &StateChecksum);
|
||
|
|
||
|
if (GetState() == LIP && StateChecksum != CRCD(0xa549b57b, "Lip"))
|
||
|
{
|
||
|
// Trigger the lip off event.
|
||
|
maybe_trip_rail_trigger(TRIGGER_LIP_OFF);
|
||
|
}
|
||
|
|
||
|
// If we are going to "LIP" via script then clear the rail node, as it's a patch to get the skater to freeze
|
||
|
if (GetState() != LIP && StateChecksum == CRCD(0xa549b57b, "Lip"))
|
||
|
{
|
||
|
mp_rail_node = NULL;
|
||
|
}
|
||
|
|
||
|
// if script alters state from wallride (like for a bail), then snap upright
|
||
|
// note, this command does not support setting TO wall, if it does, we should check here
|
||
|
if (GetState() == WALL)
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
switch (StateChecksum)
|
||
|
{
|
||
|
case 0:
|
||
|
break;
|
||
|
case CRCC(0x439f4704, "Air"):
|
||
|
SetState(AIR);
|
||
|
break;
|
||
|
case CRCC(0x58007c97, "Ground"):
|
||
|
m_last_ground_feeler.SetTrigger(0); // prevent spurious gaps
|
||
|
SetState(GROUND);
|
||
|
break;
|
||
|
case CRCC(0xa549b57b, "Lip"):
|
||
|
SetState(LIP);
|
||
|
break;
|
||
|
default:
|
||
|
Dbg_Assert(false);
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | SetRailSound | sets the rail sound to either grind or slide
|
||
|
// @uparm Grind | pass either Grind or Slide
|
||
|
case CRCC(0x947fccf4, "SetRailSound"):
|
||
|
{
|
||
|
uint32 rail_sound_checksum = 0;
|
||
|
pParams->GetChecksum(NO_NAME, &rail_sound_checksum, Script::ASSERT);
|
||
|
Dbg_MsgAssert(rail_sound_checksum == CRCD(0x255ed86f,"Grind") || rail_sound_checksum == CRCD(0x8d10119d, "Slide"),
|
||
|
("\n%s\nBad rail sound type '%s' sent to SetRailSound", pScript->GetScriptInfo(), Script::FindChecksumName(rail_sound_checksum)));
|
||
|
if (rail_sound_checksum == CRCD(0x8d10119d, "Slide"))
|
||
|
{
|
||
|
SetFlagTrue(RAIL_SLIDING);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagFalse(RAIL_SLIDING);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | LockVelocityDirection | When passed the flag On this will cause the skater's
|
||
|
// velocity to be locked in its current direction and be unaffected by the skater's rotation.
|
||
|
// Only works when on the ground however.
|
||
|
// @flag On | Enable
|
||
|
// @flag Off | Disable (Ie, back to normal)
|
||
|
case CRCC(0xacb82c02, "LockVelocityDirection"):
|
||
|
m_lock_velocity_direction = pParams->ContainsFlag(CRCD(0xf649d637, "On"));
|
||
|
break;
|
||
|
|
||
|
// @script | SetRollingFriction | change the rolling friction value
|
||
|
// @flag Default | use the default value
|
||
|
// @uparm 0.0 | friction coeff
|
||
|
case CRCC(0x510f983b, "SetRollingFriction"):
|
||
|
pParams->GetFloat(NO_NAME, &m_rolling_friction);
|
||
|
if (pParams->ContainsFlag(CRCD(0x1ca1ff20, "Default")))
|
||
|
{
|
||
|
if (m_special_friction_duration == 0.0f)
|
||
|
{
|
||
|
m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
// @script | SetGrindTweak |
|
||
|
// @uparm int | grind tweak
|
||
|
case CRCC(0x71b993b7, "SetGrindTweak"):
|
||
|
pParams->GetInteger(NO_NAME, &mGrindTweak);
|
||
|
break;
|
||
|
|
||
|
// @script | Ledge | true if ledge
|
||
|
case CRCC(0x315d9ed4, "Ledge"):
|
||
|
return mLedge ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | BadLedge | true if bad ledge
|
||
|
case CRCC(0xbe371b7b, "BadLedge"):
|
||
|
return mBadLedge ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | SkateInAble | Do a collision check using a horizontal line a bit below the skater, and return true if it hits something skatable.
|
||
|
// @flag Left | go left to right
|
||
|
// Parameters in physics.q: SkateInAble_HorizOffset and SkateInAble_DownOffset
|
||
|
// @flag Lip | This will do a check to see if the other side of the spine that the
|
||
|
// skater might be doing a lip trick on is skateinable.
|
||
|
// The collision check is down relative to the skater, but horizontal relative to
|
||
|
// the world because of the skater's wacky orientation when doing a lip trick.
|
||
|
// The direction of the collision check is towards the skater, so that it detects the
|
||
|
// other side of the spine.
|
||
|
// Parameters in physics.q: SkateInAble_LipHorizOffset and SkateInAble_LipDownOffset
|
||
|
case CRCC(0x55934543, "SkateInAble"):
|
||
|
if (pParams->ContainsFlag(CRCD(0xa549b57b, "Lip")))
|
||
|
{
|
||
|
float HorizOff = GetPhysicsFloat(CRCD(0xb92cbe3b, "SkateInAble_LipHorizOffset"));
|
||
|
float DownOff = GetPhysicsFloat(CRCD(0xefdfe781, "SkateInAble_LipDownOffset"));
|
||
|
m_col_end = GetPos();
|
||
|
m_col_end[Y] -= DownOff;
|
||
|
m_col_start = m_col_end - HorizOff * GetMatrix()[Y];
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If the first check fails, do another check straight down. This is for when the skater is doing a lip trick on a high up rail that
|
||
|
// still has vert beneath it.
|
||
|
HorizOff = GetPhysicsFloat(CRCD(0x108613cb, "SkateInAble_LipExtraCheckHorizOffset"));
|
||
|
DownOff = GetPhysicsFloat(CRCD(0xf9dc446d, "SkateInAble_LipExtraCheckDownOffset"));
|
||
|
m_col_start = GetPos() - HorizOff * GetMatrix()[Y];
|
||
|
m_col_end = m_col_start;
|
||
|
m_col_end[Y] -= DownOff;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float HorizOff=GetPhysicsFloat(CRCD(0xc78ebe56,"SkateInAble_HorizOffset"));
|
||
|
Mth::Vector a = GetPos() + HorizOff * GetMatrix()[X];
|
||
|
a[Y] -= GetPhysicsFloat(CRCD(0xfca3378c,"SkateInAble_DownOffset"));
|
||
|
Mth::Vector b = a - 2.0f * HorizOff * GetMatrix()[X];
|
||
|
|
||
|
if (pParams->ContainsFlag(CRCD(0x85981897,"Left")))
|
||
|
|
||
|
{
|
||
|
m_col_start = a;
|
||
|
m_col_end = b;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_col_start = b;
|
||
|
m_col_end = a;
|
||
|
}
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
return CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | LastSpinWas | check if last spin was frontside or backside (requires one flag)
|
||
|
// @flag Backside |
|
||
|
// @flag Frontside |
|
||
|
case CRCC(0x6c8a1316, "LastSpinWas"):
|
||
|
if (pParams->ContainsFlag(CRCD(0x47953f00, "Backside")))
|
||
|
{
|
||
|
if (GetFlag(FLIPPED))
|
||
|
{
|
||
|
return !mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
else if (pParams->ContainsFlag(CRCD(0x996d5512, "Frontside")))
|
||
|
{
|
||
|
if (GetFlag(FLIPPED))
|
||
|
{
|
||
|
return mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return !mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Dbg_MsgAssert(false, ("LastSpinWas requires a FrontSide or BackSide flag"));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
// @script | LandedFromSpine |
|
||
|
case CRCC(0x448e3630, "LandedFromSpine"):
|
||
|
return mLandedFromSpine ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | LandedFromVert |
|
||
|
case CRCC(0xf37ce040, "LandedFromVert"):
|
||
|
return mLandedFromVert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | SetLandedFromVert | sets the mlandedfromvert flag
|
||
|
// (check with LandedFromVert)
|
||
|
case CRCC(0x133e1293, "SetLandedFromVert"):
|
||
|
mLandedFromVert = true;
|
||
|
break;
|
||
|
|
||
|
// @script | ResetLandedFromVert | clears mlandedfromvert flag
|
||
|
// (check with LandedFromVert)
|
||
|
case CRCC(0xf922431, "ResetLandedFromVert"):
|
||
|
mLandedFromVert = false;
|
||
|
break;
|
||
|
|
||
|
// @script | IsInVertAir | check if VERT_AIR flag is set
|
||
|
case CRCC(0xdfb8f052, "InVertAir"):
|
||
|
return GetFlag(VERT_AIR) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
|
||
|
// @script | AllowLipNoGrind |
|
||
|
case CRCC(0xc5b4b390, "AllowLipNoGrind"):
|
||
|
mAllowLipNoGrind = true;
|
||
|
break;
|
||
|
|
||
|
// @script | ClearAllLipNoGrind |
|
||
|
case CRCC(0xb9eebff6, "ClearAllowLipNoGrind"):
|
||
|
mAllowLipNoGrind = false;
|
||
|
break;
|
||
|
|
||
|
// @script | NoRailTricks | don't allow rail tricks
|
||
|
// (turn on with AllowRailTricks)
|
||
|
case CRCC(0xec681a59, "NoRailTricks"):
|
||
|
mNoRailTricks = true;
|
||
|
break;
|
||
|
|
||
|
// @script | AllowRailTricks | turn off with NoRailTricks
|
||
|
case CRCC(0x65a559a, "AllowRailTricks"):
|
||
|
mNoRailTricks = false;
|
||
|
break;
|
||
|
|
||
|
// @script | TurnToFaceVelocity | turn to face the proper direction
|
||
|
case CRCC(0x461e5b92, "TurnToFaceVelocity"):
|
||
|
// Mick: Don't do it if vel is too short as skater will collapse
|
||
|
if (GetVel().LengthSqr() < 0.01f * 0.01f) break;
|
||
|
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
GetMatrix()[X].Normalize();
|
||
|
GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
|
||
|
GetMatrix()[Y].Normalize();
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
break;
|
||
|
|
||
|
// @script | OverrideLimits | Overrides the limits to the maximum speed that the skater
|
||
|
// can achieve. This allows us to do temporary speed boosts whihc are much faster
|
||
|
// than regular gameplay.
|
||
|
// You can specify a time that this speed limit will last for
|
||
|
// @parm float | max | Maximum speed in inches per second (normal gameplay is around 1000, so 5000 is nice)
|
||
|
// @parmopt float | max_max | max | usualy the same as above. It's a hard cap, making it bigger that max will give
|
||
|
// you a smoother limit to the speed.
|
||
|
// @parmopt float | friction | 0.0000020 | air friction. Slows you down at a rate proportional to speed. So reduce this to stay fast longer
|
||
|
// @parmopt float | gravity | physics default | down_gravity. gravity that slows you down when going up a slope
|
||
|
// @parmopt float | time | 10000000000000,0f | time in seconds that this effect should last for
|
||
|
// @flag end | end the effect (other parameters are unneeded and ignored if end is flagged)
|
||
|
case CRCC(0x90f1d8d5, "OverrideLimits"):
|
||
|
{
|
||
|
if (pParams->ContainsFlag(CRCD(0xff03cc4e, "end")))
|
||
|
{
|
||
|
m_override_limits_time = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pParams->GetFloat(CRCD(0x6289dd76, "max"), &m_override_max, Script::ASSERT);
|
||
|
m_override_max_max = m_override_max;
|
||
|
pParams->GetFloat(CRCD(0x73a19e3a, "max_max"), &m_override_max_max);
|
||
|
m_override_limits_time = 10000000000000.0f;
|
||
|
pParams->GetFloat(CRCD(0x906b67ba, "time"), &m_override_limits_time);
|
||
|
m_override_air_friction = 0.0000020f;
|
||
|
pParams->GetFloat(CRCD(0xedf3e7f4, "friction"), &m_override_air_friction);
|
||
|
m_override_down_gravity = GetPhysicsFloat(CRCD(0x6618a713, "Physics_Ground_Gravity"));
|
||
|
pParams->GetFloat(CRCD(0xa5e2da58, "gravity"), &m_override_down_gravity);
|
||
|
|
||
|
if (pParams->ContainsFlag(CRCD(0xdf312928, "NoTimeLimit")))
|
||
|
{
|
||
|
// magic number meaning no time limit
|
||
|
m_override_limits_time = -1.0f;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | SetSpecialFriction | This command is used in the Revert script to specify a set of friction values that should be used for
|
||
|
// each Revert in the combo. m_special_friction_index gets reset as soon as the combo ends, ie by the ClearPanel_Landed & ClearPanel_Bailed
|
||
|
// functions.
|
||
|
// @uparm [] | array of friction values
|
||
|
case CRCC(0x5e8d9b80,"SetSpecialFriction"):
|
||
|
{
|
||
|
Script::CArray* pArray = NULL;
|
||
|
pParams->GetArray(NO_NAME, &pArray);
|
||
|
Dbg_MsgAssert(pArray, ("\n%s\nSetSpecialFriction requires an array of friction values", pScript->GetScriptInfo()));
|
||
|
if (m_special_friction_index >= static_cast< int >(pArray->GetSize()))
|
||
|
{
|
||
|
m_special_friction_index = static_cast< int >(pArray->GetSize()) - 1;
|
||
|
}
|
||
|
m_rolling_friction = pArray->GetFloat(m_special_friction_index);
|
||
|
if (m_special_friction_index == 0)
|
||
|
{
|
||
|
m_special_friction_decrement_time_stamp = Tmr::GetTime();
|
||
|
}
|
||
|
++m_special_friction_index;
|
||
|
|
||
|
pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &m_special_friction_duration);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | OverrideCancelGround | Overrided the Cancel_Ground flag in gaps
|
||
|
// for use with reverts
|
||
|
// @flag Off | turn this flag off
|
||
|
case CRCC(0xb94bc0c6, "OverrideCancelGround"):
|
||
|
SetFlag(OVERRIDE_CANCEL_GROUND, !pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")));
|
||
|
break;
|
||
|
|
||
|
// @script | SetExtraPush |
|
||
|
// @parmopt float | radius | 48.0 | wall push radius
|
||
|
// @parmopt float | speed | 100.0 | wall push speed
|
||
|
// @parmopt float | turn | 6.0 | wall rotate speed
|
||
|
case CRCC(0xe47ff4b5, "SetExtraPush"):
|
||
|
{
|
||
|
m_wall_push_radius = 48.0f;
|
||
|
m_wall_push_speed = 100.0f;
|
||
|
m_wall_rotate_speed = 6.0f;
|
||
|
pParams->GetFloat(CRCD(0xc48391a5, "radius"), &m_wall_push_radius);
|
||
|
pParams->GetFloat(CRCD(0xf0d90109, "speed"), &m_wall_push_speed);
|
||
|
pParams->GetFloat(CRCD(0xdfdfeab8, "turn"), &m_wall_rotate_speed);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// @script | GetMatrixNormal | places the skater's matrix normal in x, y, and z
|
||
|
case CRCC(0x5144783, "GetMatrixNormal"):
|
||
|
{
|
||
|
pScript->GetParams()->AddFloat(CRCD(0x7323e97c, "x"), GetMatrix()[Y][X]);
|
||
|
pScript->GetParams()->AddFloat(CRCD(0x0424d9ea, "y"), GetMatrix()[Y][Y]);
|
||
|
pScript->GetParams()->AddFloat(CRCD(0x9d2d8850, "z"), GetMatrix()[Y][Z]);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Overloading the CMotionComponent member function for skater specific logic
|
||
|
case CRCC(0x8819dd8b, "Obj_MoveToNode"):
|
||
|
{
|
||
|
CMotionComponent* p_motion_component = GetMotionComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_motion_component);
|
||
|
|
||
|
// call the member function, which will set m_matrix and m_pos
|
||
|
bool result = p_motion_component->CallMemberFunction( Checksum, pParams, pScript );
|
||
|
|
||
|
// and copy this into m_display_matrix
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// set the shadow to stick to his feet, assuming we are on the ground
|
||
|
// Note: These are used by the simple shadow, not the detailed one.
|
||
|
CShadowComponent* p_shadow_component = GetShadowComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_shadow_component);
|
||
|
|
||
|
p_shadow_component->SetShadowPos( GetSkater()->m_pos );
|
||
|
p_shadow_component->SetShadowNormal( GetSkater()->m_matrix[Y] );
|
||
|
|
||
|
m_safe_pos = GetPos(); // needed for uberfrig
|
||
|
GetOldPos() = GetPos(); // needed for camera, so it thinks we've stopped
|
||
|
|
||
|
GetObject()->SetTeleported();
|
||
|
|
||
|
// Force an update of the skater's camera if this is a local skater
|
||
|
if( GetSkater()->IsLocalClient())
|
||
|
{
|
||
|
Obj::CCompositeObject *p_obj = GetSkater()->GetCamera();
|
||
|
if (p_obj)
|
||
|
{
|
||
|
p_obj->Update(); // Not the best way of doing it...
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!pParams->ContainsFlag( CRCD(0xd607e2e6,"NoReset") ))
|
||
|
{
|
||
|
// assume we want the skater to stop...
|
||
|
m_last_ground_feeler.SetTrigger(0); // patch to stop spurious gaps
|
||
|
GetVel().Set();
|
||
|
DUMP_VELOCITY;
|
||
|
SetState(GROUND);
|
||
|
|
||
|
// Make sure the flippedness of the skater is in a stable state
|
||
|
// that reflects if he is goofy or not
|
||
|
SetFlag(FLIPPED, !GetSkater()->m_isGoofy);
|
||
|
|
||
|
// reset any flippedness of animation
|
||
|
CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_flip_and_rotate_component);
|
||
|
p_flip_and_rotate_component->ApplyFlipState();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// the "NoReset" flag is set
|
||
|
// so velocity is maintained
|
||
|
// but we need to check if the orient flag is set
|
||
|
// as that indicates that we want the velocity
|
||
|
// to be oriented as well.
|
||
|
// (if NoReset was set, then velocity will be zero)
|
||
|
if ( pParams->ContainsFlag( CRCD(0x90a91232, "orient") ))
|
||
|
{
|
||
|
float y_vel = GetVel()[Y]; // y velocity is preserved.....
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
float speed = GetVel().Length(); // how fast are we going?
|
||
|
GetVel() = GetMatrix()[Z] * speed; // face forward, at that speed
|
||
|
GetVel()[Y] = y_vel;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
return result ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
|
||
|
}
|
||
|
|
||
|
// Used by the create-a-goal cursor to initialise the cursor position.
|
||
|
// Cannot use the current skater's position because he might have just bailed by
|
||
|
// jumping into water, which would then cause the cursor to get stuck.
|
||
|
case 0xf7e21884: // GetLastGroundPos
|
||
|
{
|
||
|
pScript->GetParams()->AddVector(CRCD(0x7f261953,"Pos"),m_last_ground_pos[X],m_last_ground_pos[Y],m_last_ground_pos[Z]);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return CBaseComponent::MF_NOT_EXECUTED;
|
||
|
}
|
||
|
return CBaseComponent::MF_TRUE;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::GetDebugInfo(Script::CStruct *p_info)
|
||
|
{
|
||
|
#ifdef __DEBUG_CODE__
|
||
|
Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterCorePhysicsComponent::GetDebugInfo"));
|
||
|
|
||
|
CBaseComponent::GetDebugInfo(p_info);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::Reset ( )
|
||
|
{
|
||
|
// In Network games, don't set the skater to 0,0,0 because he is about to be teleported to a starting position and 0,0,0 means nothing.
|
||
|
if (!GameNet::Manager::Instance()->InNetGame())
|
||
|
{
|
||
|
/*
|
||
|
GetPos().Set(0.0f, 0.0f, 0.0f);
|
||
|
GetOldPos().Set(0.0f, 0.0f, 0.0f);
|
||
|
m_safe_pos.Set(0.0f, 0.0f, 0.0f);
|
||
|
*/
|
||
|
GetOldPos() = GetPos();
|
||
|
m_safe_pos = GetPos();
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
m_pre_lip_pos.Set();
|
||
|
|
||
|
/*
|
||
|
GetMatrix().Identity();
|
||
|
ResetLerpingMatrix();
|
||
|
*/
|
||
|
|
||
|
m_state_change_timestamp = Tmr::GetTime();
|
||
|
|
||
|
GetVel().Set(0.0f, 0.0f, 0.0f);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
SetState(GROUND);
|
||
|
m_previous_state = GROUND;
|
||
|
|
||
|
for (int flag = NUM_ESKATERFLAGS; flag--; )
|
||
|
{
|
||
|
SetFlagTrue(static_cast< ESkaterFlag >(flag));
|
||
|
SetFlagFalse(static_cast< ESkaterFlag >(flag));
|
||
|
}
|
||
|
SetFlag(FLIPPED, !GetSkater()->m_isGoofy);
|
||
|
|
||
|
mp_movable_contact_component->LoseAnyContact();
|
||
|
|
||
|
// bit nasty, prevents us getting "SKATE_OFF_EDGE" triggers
|
||
|
m_last_ground_feeler.SetTrigger(0);
|
||
|
|
||
|
m_wall_push_radius = 0.0f;
|
||
|
|
||
|
m_tense_time = 0;
|
||
|
|
||
|
mWallrideTime = 0;
|
||
|
|
||
|
mNoSpin = false;
|
||
|
|
||
|
m_bail = false;
|
||
|
|
||
|
m_force_cankick_off = false;
|
||
|
m_pressing_down_brakes = true;
|
||
|
|
||
|
mp_state_component->mJumpedOutOfLipTrick = false;
|
||
|
|
||
|
// mp_rail_node points to previous rail grinded, which might no longer be valid
|
||
|
// best to set it to NULL (fixed crash in park editor with multiple test-plays TT#678)
|
||
|
mp_rail_node = NULL;
|
||
|
|
||
|
m_override_limits_time = 0.0f;
|
||
|
m_transfer_overrides_factor = 1.0f;
|
||
|
|
||
|
m_last_wallplant_time_stamp = 0;
|
||
|
m_last_wallpush_time_stamp = 0;
|
||
|
|
||
|
m_last_jump_time_stamp = 0;
|
||
|
|
||
|
m_special_friction_duration = 0.0f;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::ReadySkateState ( bool to_ground_state, const SRailData* p_rail_data, const SAcidDropData* p_acid_drop_data )
|
||
|
{
|
||
|
// setup the state in preparation for being in skating mode next object update
|
||
|
|
||
|
// reset all flags except FLIPPED
|
||
|
ResetFlags();
|
||
|
|
||
|
if (to_ground_state)
|
||
|
{
|
||
|
SetState(GROUND);
|
||
|
m_previous_state = GROUND;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetState(AIR);
|
||
|
m_previous_state = AIR;
|
||
|
|
||
|
// no in-air orientation control after walking
|
||
|
SetFlagTrue(NO_ORIENTATION_CONTROL);
|
||
|
|
||
|
if (mp_walk_component->m_disallow_acid_drops)
|
||
|
{
|
||
|
SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_state_change_timestamp = Tmr::GetTime();
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
m_display_normal = m_last_display_normal = m_current_normal = GetMatrix()[Y];
|
||
|
|
||
|
GetOldPos() = m_safe_pos = GetPos();
|
||
|
|
||
|
// bit nasty, prevents us getting "SKATE_OFF_EDGE" triggers
|
||
|
m_last_ground_feeler.SetTrigger(0);
|
||
|
|
||
|
m_wall_push_radius = 0.0f;
|
||
|
|
||
|
m_tense_time = 0;
|
||
|
|
||
|
mWallrideTime = 0;
|
||
|
|
||
|
mNoSpin = false;
|
||
|
|
||
|
// m_bail = false;
|
||
|
|
||
|
m_force_cankick_off = false;
|
||
|
m_pressing_down_brakes = true;
|
||
|
|
||
|
mp_state_component->mJumpedOutOfLipTrick = false;
|
||
|
|
||
|
m_override_limits_time = 0.0f;
|
||
|
m_transfer_overrides_factor = 1.0f;
|
||
|
|
||
|
m_last_wallplant_time_stamp = 0;
|
||
|
m_last_wallpush_time_stamp = 0;
|
||
|
|
||
|
m_last_jump_time_stamp = 0;
|
||
|
|
||
|
// handle special transitions
|
||
|
if (p_rail_data)
|
||
|
{
|
||
|
// TT8717. If walker had a movable contact, but that contact was not the same as the object containing the relavent rail manager component,
|
||
|
// the skater was asserting in do_rail_physics. We need to lose any old contact and acquire the appropriate contact.
|
||
|
mp_movable_contact_component->LoseAnyContact();
|
||
|
if (p_rail_data->p_movable_contact)
|
||
|
{
|
||
|
mp_movable_contact_component->ObtainContact(p_rail_data->p_movable_contact);
|
||
|
}
|
||
|
got_rail(p_rail_data->rail_pos, p_rail_data->p_node, p_rail_data->p_rail_man, true, true);
|
||
|
GetOldPos() = m_safe_pos = GetPos();
|
||
|
}
|
||
|
else if (p_acid_drop_data)
|
||
|
{
|
||
|
enter_acid_drop(*p_acid_drop_data);
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
const char* p_state_names [ ] =
|
||
|
{
|
||
|
"GROUND",
|
||
|
"AIR",
|
||
|
"WALL",
|
||
|
"LIP",
|
||
|
"RAIL",
|
||
|
"WALLPLANT"
|
||
|
};
|
||
|
DUMPS(p_state_names[GetState()]);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::CleanUpSkateState ( )
|
||
|
{
|
||
|
if (m_vert_air_last_frame)
|
||
|
{
|
||
|
m_vert_air_last_frame = false;
|
||
|
GetObject()->BroadcastEvent(CRCD(0x5e27200a, "SkaterExitVertAir"));
|
||
|
}
|
||
|
|
||
|
// longer rerail delay when walking
|
||
|
m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0xe4412eb7, "Rail_walk_rerail_time")));
|
||
|
|
||
|
// lip time counts towards rerail delay when walking
|
||
|
if (m_lip_time > m_rail_time)
|
||
|
{
|
||
|
m_rail_time = m_lip_time;
|
||
|
}
|
||
|
|
||
|
// no grinding from walking after jumping out of a lip
|
||
|
if (GetState() == AIR && m_previous_state == LIP)
|
||
|
{
|
||
|
SetFlagFalse(CAN_RERAIL);
|
||
|
}
|
||
|
|
||
|
// Extra clean up so that an observing camera will act OK while walking. Note that observing cameras always use skater camera logic, even when the
|
||
|
// observed player is walking.
|
||
|
mp_state_component->m_state = GROUND;
|
||
|
mp_state_component->m_camera_display_normal.Set(0.0f, 1.0f, 0.0f);
|
||
|
mp_state_component->m_camera_current_normal.Set(0.0f, 1.0f, 0.0f);
|
||
|
mp_state_component->m_spine_vel.Set();
|
||
|
mp_state_component->mJumpedOutOfLipTrick = false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::ResetFlags ( )
|
||
|
{
|
||
|
// reset all flags except FLIPPED
|
||
|
for (int flag = NUM_ESKATERFLAGS; flag--; )
|
||
|
{
|
||
|
ESkaterFlag skater_flag = static_cast< ESkaterFlag >(flag);
|
||
|
if (skater_flag == FLIPPED) continue;
|
||
|
SetFlagTrue(skater_flag);
|
||
|
SetFlagFalse(skater_flag);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::SetState ( EStateType state )
|
||
|
{
|
||
|
if (mp_state_component->m_state != state)
|
||
|
{
|
||
|
m_state_change_timestamp = Tmr::GetTime();
|
||
|
m_previous_state = mp_state_component->m_state;
|
||
|
}
|
||
|
|
||
|
// if going out of lip state, and previous lip pos is valid (not zero), then move back to the previous lip position (so we drop down nicely)
|
||
|
if (mp_state_component->m_state == LIP
|
||
|
&& state != LIP
|
||
|
&& (m_pre_lip_pos[X] != 0.0f || m_pre_lip_pos[Y] != 0.0f || m_pre_lip_pos[Z] != 0.0f))
|
||
|
{
|
||
|
// *** (SetState(LIP)) kind of nasty, but we want to allow them to move through the lip
|
||
|
// we leave m_old_trigger_pos alone
|
||
|
GetPos() = m_pre_lip_pos;
|
||
|
GetOldPos() = GetPos();
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
// If setting the state to anything other than lip then kill the pre_lip_pos
|
||
|
if (state != LIP)
|
||
|
{
|
||
|
m_pre_lip_pos.Set(0.0f, 0.0f, 0.0f);
|
||
|
}
|
||
|
|
||
|
if (state != GROUND)
|
||
|
{
|
||
|
// start recording graffiti tricks
|
||
|
mp_trick_component->SetGraffitiTrickStarted( true );
|
||
|
}
|
||
|
|
||
|
if (mp_state_component->m_state == RAIL && state != RAIL)
|
||
|
{
|
||
|
SetFlagFalse(RAIL_SLIDING);
|
||
|
}
|
||
|
|
||
|
if (mp_state_component->m_state == AIR && state != AIR)
|
||
|
{
|
||
|
// If going from AIR to any other state, record the landing time. Required for the AirTimeLessThan and AirTimeGreaterThan functions.
|
||
|
m_landed_time = Tmr::GetTime();
|
||
|
|
||
|
SetFlagFalse(NO_ORIENTATION_CONTROL);
|
||
|
SetFlagFalse(OLLIED_FROM_RAIL);
|
||
|
}
|
||
|
else if (mp_state_component->m_state != AIR && state == AIR)
|
||
|
{
|
||
|
// If going to AIR, record the takeoff time, unless going from WALL to AIR.
|
||
|
// (The 'unless' bit fixes TT493, where hitting the ground from doing a wall-ride was not setting a big enough airtime)
|
||
|
if (mp_state_component->m_state != WALL)
|
||
|
{
|
||
|
m_went_airborne_time = Tmr::GetTime();
|
||
|
}
|
||
|
|
||
|
// Reset the spin tracking stuff.
|
||
|
mp_trick_component->mTallyAngles = 0.0f;
|
||
|
|
||
|
// Clear the L1 and R1 triggers, since they're used for spin taps.
|
||
|
mp_input_component->GetControlPad().m_L1.ClearTrigger();
|
||
|
mp_input_component->GetControlPad().m_R1.ClearTrigger();
|
||
|
// Just to be sure
|
||
|
m_tap_turns = 0.0f;
|
||
|
}
|
||
|
|
||
|
mp_state_component->m_state = state;
|
||
|
|
||
|
mp_sound_component->SetState(state);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::ReverseFacing ( )
|
||
|
{
|
||
|
GetMatrix()[Z] = -GetMatrix()[Z];
|
||
|
GetMatrix()[X] = -GetMatrix()[X];
|
||
|
ResetLerpingMatrix();
|
||
|
mRail_Backwards = !mRail_Backwards;
|
||
|
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
if (DebugSkaterScripts && GetObject()->GetType() == SKATE_TYPE_SKATER)
|
||
|
{
|
||
|
printf("%d: Rotating skater\n", (int) Tmr::GetRenderFrame());
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater )
|
||
|
{
|
||
|
// reset all flags except FLIPPED
|
||
|
ResetFlags();
|
||
|
|
||
|
SetState(AIR);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::limit_speed ( )
|
||
|
{
|
||
|
float save_y = GetVel()[Y];
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
|
||
|
float speed = GetVel().Length();
|
||
|
float max_max_speed = GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat"));
|
||
|
float max_speed = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
|
||
|
|
||
|
if (m_override_limits_time != 0.0f)
|
||
|
{
|
||
|
// -1.0f is a magic number causing no time limit
|
||
|
if (m_override_limits_time != -1.0f)
|
||
|
{
|
||
|
m_override_limits_time -= m_frame_length;
|
||
|
if (m_override_limits_time < 0.0f)
|
||
|
{
|
||
|
m_override_limits_time = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
max_max_speed = m_override_max_max;
|
||
|
max_speed = m_override_max;
|
||
|
}
|
||
|
|
||
|
if (speed > max_max_speed)
|
||
|
{
|
||
|
speed = max_max_speed;
|
||
|
GetVel().Normalize(speed);
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
if (speed > max_speed)
|
||
|
{
|
||
|
apply_wind_resistance(GetPhysicsFloat(CRCD(0x850eb87a, "Physics_Heavy_Air_Friction")));
|
||
|
}
|
||
|
|
||
|
GetVel()[Y] = save_y;
|
||
|
|
||
|
// decrement the special friction counter
|
||
|
if (m_special_friction_duration != 0.0f)
|
||
|
{
|
||
|
m_special_friction_duration -= m_frame_length;
|
||
|
if (m_special_friction_duration <= 0.0f)
|
||
|
{
|
||
|
// reset the special friction to default
|
||
|
m_special_friction_duration = 0.0f;
|
||
|
m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::apply_wind_resistance ( float friction )
|
||
|
{
|
||
|
// air friction is proportional to the square of the velocity; this is a limiting friction, and has much more effect at high speeds
|
||
|
float speed_squared = GetVel().LengthSqr();
|
||
|
if (speed_squared < 0.00001f) return;
|
||
|
|
||
|
Mth::Vector air_friction = GetVel();
|
||
|
air_friction.Normalize(friction * 60.0f * m_frame_length * speed_squared);
|
||
|
|
||
|
if (air_friction.LengthSqr() > speed_squared)
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel() -= air_friction;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_on_ground_physics ( )
|
||
|
{
|
||
|
// clear any flags that have no meaning when on the ground
|
||
|
SetFlagFalse(CAN_BREAK_VERT);
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(IN_RECOVERY);
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
// ground skateing will cancel any memory of what rail we were on, so the next one seems fresh
|
||
|
mp_rail_node = NULL;
|
||
|
m_last_rail_trigger_node_name = 0;
|
||
|
|
||
|
// rotate velocity, so it lays in the plane we are on: (as skaters orientation can lag behind)
|
||
|
// Dave note: calling RotateToPlane() with too small |vector| causes Nan problems - fix at some point.
|
||
|
if( GetVel().Length() > 0.001f )
|
||
|
{
|
||
|
GetVel().RotateToPlane(m_current_normal);
|
||
|
}
|
||
|
|
||
|
// get gravitational force
|
||
|
Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0x6618a713, "Physics_Ground_Gravity")), 0.0f);
|
||
|
|
||
|
// allow for override if we are going up the slope (we might want to set gravity to zero when rolling up a slope)
|
||
|
if (m_override_limits_time != 0.0f && GetVel()[Y] > 0.0f)
|
||
|
{
|
||
|
gravity.Set(0.0f, m_override_down_gravity, 0.0f);
|
||
|
}
|
||
|
|
||
|
// project gravity onto the plane we are on
|
||
|
gravity.ProjectToPlane(m_current_normal);
|
||
|
|
||
|
// add gravity into our velocity, adjusting for time
|
||
|
GetVel() += gravity * m_frame_length;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// based on if we are doing a balance trick, then either handle braking/kicking, or do the balance physics
|
||
|
switch (mp_balance_trick_component->mBalanceTrickType)
|
||
|
{
|
||
|
case 0:
|
||
|
// Only do braking & speeding up when not doing a balance trick like a manual
|
||
|
if (is_trying_to_brake())
|
||
|
{
|
||
|
do_brake();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_braking = false;
|
||
|
if (can_kick())
|
||
|
{
|
||
|
do_kick();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CRCC(0xac90769, "NoseManual"):
|
||
|
case CRCC(0xef24413b, "Manual"):
|
||
|
m_braking = false;
|
||
|
|
||
|
mp_balance_trick_component->mManual.DoManualPhysics();
|
||
|
|
||
|
// start recording graffiti tricks
|
||
|
mp_trick_component->SetGraffitiTrickStarted(true);
|
||
|
|
||
|
if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")))
|
||
|
{
|
||
|
// Here, set the flag. It may seem redundant, but the above line is very likely
|
||
|
// to be hacked by gameshark. They probably won't notice this one, which will
|
||
|
// set the flags as if they had actually enabled the cheat -- which enables us
|
||
|
// to detect that it has been turned on more easily.
|
||
|
Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")));
|
||
|
mp_balance_trick_component->mManual.mManualLean = 0.0f;
|
||
|
mp_balance_trick_component->mManual.mManualLeanDir = 0.0f;
|
||
|
g_CheatsEnabled = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CRCC(0x3506ce64, "Skitch"):
|
||
|
mp_balance_trick_component->mSkitch.DoManualPhysics();
|
||
|
if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")))
|
||
|
{
|
||
|
// Here, set the flag. It may seem redundant, but the above line is very likely
|
||
|
// to be hacked by gameshark. They probably won't notice this one, which will
|
||
|
// set the flags as if they had actually enabled the cheat -- which enables us
|
||
|
// to detect that it has been turned on more easily.
|
||
|
Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")));
|
||
|
mp_balance_trick_component->mSkitch.mManualLean = 0.0f;
|
||
|
mp_balance_trick_component->mSkitch.mManualLeanDir = 0.0f;
|
||
|
g_CheatsEnabled = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CRCC(0x255ed86f, "Grind"):
|
||
|
case CRCC(0x8d10119d, "Slide"):
|
||
|
{
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
CSkaterEndRunComponent* p_endrun_component = GetSkaterEndRunComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_endrun_component);
|
||
|
Dbg_MsgAssert(!p_endrun_component->IsEndingRun() || !p_endrun_component->RunHasEnded(), ("Grind balance trick done on ground?"));
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case CRCC(0xa549b57b, "Lip"):
|
||
|
{
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
CSkaterEndRunComponent* p_endrun_component = GetSkaterEndRunComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_endrun_component);
|
||
|
Dbg_MsgAssert(!p_endrun_component->IsEndingRun() || !p_endrun_component->RunHasEnded(), ("Lip balance trick done on ground?"));
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
Dbg_Assert(false);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Ground friction is rolling friction + wind resistance
|
||
|
// Update velocity for friction before using velocity
|
||
|
handle_ground_friction();
|
||
|
|
||
|
// if too close to a wall, then adjust velocity to move the skater away from it
|
||
|
// the distance that is checked can be changed by script, and probably gets big in bails, to stop the skater clipping through walls
|
||
|
push_away_from_walls();
|
||
|
|
||
|
// Calculate how much we want to move, based on the velocity
|
||
|
Mth::Vector m_movement = GetVel() * m_frame_length;
|
||
|
|
||
|
// when skitching, we get sucked into the skitch point
|
||
|
if (GetFlag(SKITCHING))
|
||
|
{
|
||
|
if (mp_movable_contact_component->GetContact() && mp_movable_contact_component->GetContact()->GetObject())
|
||
|
{
|
||
|
GetVel() = mp_movable_contact_component->GetContact()->GetObject()->GetVel() * GetPhysicsFloat(CRCD(0xdef25b34, "skitch_speed_match"));
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
m_movement.Set();
|
||
|
move_to_skitch_point();
|
||
|
}
|
||
|
|
||
|
// if we are in contact with something, then factor in that "movement," but not if (we are skitching, and in not in initial movement)
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos())) // still need to update it every frame, otherwise it gets confused
|
||
|
{
|
||
|
if (!GetFlag(SKITCHING))
|
||
|
{
|
||
|
// handle movement due to contact seperately from normal physic movement
|
||
|
|
||
|
GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
GetOldPos() = GetPos(); // (WITH CONTACT) frig, as movement is due to something carying us, no need to incorporate that into collision
|
||
|
m_safe_pos = GetPos(); // (WITH CONTACT) frig, as movement is due to something carying us, no need to incorporate that into collision
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
if (mp_movable_contact_component->GetContact()->IsRotated())
|
||
|
{
|
||
|
GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// set "move_again" flag, for while loop
|
||
|
// generally this will be cleared after the first time through the loop
|
||
|
// but it can be set again, indicating that out movmeent was brought short
|
||
|
// by hitting a curver surface that we can track around (like a Qp)
|
||
|
bool move_again = true;
|
||
|
|
||
|
// we only let them move again once
|
||
|
// so we have a flag that says if we have already done it this frame
|
||
|
bool already_moved_again = false;
|
||
|
|
||
|
while (move_again)
|
||
|
{
|
||
|
move_again = false;
|
||
|
|
||
|
Mth::Vector start_pos = GetPos(); // remember where we started on this iteration
|
||
|
GetPos() += m_movement; // Move him
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Handle collisions //////////////////////////////////////////////
|
||
|
if (!GetFlag(SKITCHING)) // if skitching, then dont bother with forward collision
|
||
|
{
|
||
|
handle_forward_collision();
|
||
|
}
|
||
|
|
||
|
|
||
|
// Mick - removed the test for m_moving_to_skitch for now
|
||
|
// as we want the cars to be able to drag the player over gaps and jumps
|
||
|
// and it does not seem to look bad this way
|
||
|
// the only problem might be during very abrupt cahnges in ground angle,
|
||
|
// or maybe very uneven ground
|
||
|
if (GetFlag(SKITCHING) /* && m_moving_to_skitch*/)
|
||
|
{
|
||
|
// moving to skitch point, so don't snap to ground, as
|
||
|
// we might snap onto the vehicle
|
||
|
// instead, just project the skater onto the plane of the car
|
||
|
|
||
|
// Dan: We still need to update terrain, however
|
||
|
m_feeler.m_start = GetPos() + 36.0f * GetMatrix()[Y];
|
||
|
m_feeler.m_end = GetPos() - 200.0f * GetMatrix()[Y];
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
set_terrain(m_feeler.GetTerrain());
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
snap_to_ground();
|
||
|
}
|
||
|
|
||
|
// now see how far we have moved
|
||
|
// interestingly we often move more than we are supposed to
|
||
|
// probably due to the "snap to ground" stuff.
|
||
|
Mth::Vector actual_movement_vector;
|
||
|
actual_movement_vector = GetPos() - start_pos;
|
||
|
float actual_distance_moved = actual_movement_vector.Length();
|
||
|
float attempted_distance = m_movement.Length();
|
||
|
|
||
|
if (!already_moved_again && GetState() == GROUND)
|
||
|
{
|
||
|
if (actual_distance_moved < (attempted_distance - 0.1f))
|
||
|
{
|
||
|
m_movement = GetVel();
|
||
|
m_movement.Normalize(); // get new direction of travel
|
||
|
m_movement *= attempted_distance; // at old speed
|
||
|
m_movement *= 1.0f - actual_distance_moved / attempted_distance; // scale to account for movement
|
||
|
move_again = true;
|
||
|
already_moved_again = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The remaining "ground physics" should only be done if we are still on the ground (might have skated off it)
|
||
|
if (GetState() == GROUND)
|
||
|
{
|
||
|
handle_ground_rotation();
|
||
|
|
||
|
// don't let the board slide sideways
|
||
|
// K: Avoid anything that might change the velocity direction if this flag is set.
|
||
|
if (!m_lock_velocity_direction)
|
||
|
{
|
||
|
remove_sideways_velocity(GetVel());
|
||
|
}
|
||
|
|
||
|
if (!GetFlag(SKITCHING))
|
||
|
{
|
||
|
// check if we are too close to a wall, and pop us out and away
|
||
|
check_side_collisions();
|
||
|
|
||
|
// check if we are moving slowly and leaning, and mush us more away from a wall if so
|
||
|
check_leaning_into_wall();
|
||
|
}
|
||
|
|
||
|
// K: This flag is to allow the skater to be rotated on the ground without affecting his
|
||
|
// velocity direction, so also disable the flipping otherwise 360 rotations won't be possible.
|
||
|
if (!m_lock_velocity_direction)
|
||
|
{
|
||
|
// might have gone upa slope, and gravity pulled us down, so we are not skating backwards
|
||
|
flip_if_skating_backwards();
|
||
|
}
|
||
|
|
||
|
// Check for jumping
|
||
|
if (maybe_flag_ollie_exception())
|
||
|
{
|
||
|
maybe_straight_up();
|
||
|
SetFlagTrue(CAN_BREAK_VERT);
|
||
|
}
|
||
|
|
||
|
mp_trick_component->TrickOffObject(m_last_ground_feeler.GetNodeChecksum());
|
||
|
|
||
|
m_tap_turns = 0.0f;
|
||
|
}
|
||
|
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
|
||
|
{
|
||
|
Gfx::AddDebugLine(GetPos() + m_current_normal, GetOldPos() + m_current_normal, GREEN, 0, 0);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::is_trying_to_brake ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
// if not autokick, and not accelerating (square pressed), and going slow, then brake
|
||
|
if (!m_auto_kick && !control_pad.m_square.GetPressed() && GetVel().Length() < 50.0f)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return control_pad.m_down.GetPressed() // Down must be pressed
|
||
|
&& m_pressing_down_brakes // and must be enabled
|
||
|
&&
|
||
|
(
|
||
|
GetVel().Length() < 50.0f // and either going really slow
|
||
|
||
|
||
|
(
|
||
|
!control_pad.m_right.GetPressed() // or not pressed right or left
|
||
|
&&
|
||
|
!control_pad.m_left.GetPressed()
|
||
|
)
|
||
|
||
|
||
|
( // or going backwards
|
||
|
Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_brake ( )
|
||
|
{
|
||
|
// Handle braking
|
||
|
|
||
|
m_braking = true;
|
||
|
|
||
|
if (on_steep_slow_slope())
|
||
|
{
|
||
|
m_braking = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float speed = GetVel().Length();
|
||
|
|
||
|
// if already really slow (or stopped), then just stop
|
||
|
if (speed < GetPhysicsFloat(CRCD(0xe5d73f6f, "Physics_Brake_Acceleration")) * 2.0f * m_frame_length)
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#if 0 // old code, this check doesn't seem necessary given the above if branch; Dan
|
||
|
// Mth::Vector forward = GetVel();
|
||
|
// forward.Normalize();
|
||
|
// forward *= -PHYSICS_BRAKE_ACCELERATION;
|
||
|
// Mth::Vector old_vel = GetVel(); // remember old velocity
|
||
|
// m_vel += forward * m_frame_length; // apply braking force
|
||
|
// if (Mth::DotProduct(GetVel(), old_vel) < 0.0f) // if the velocty now in other direction
|
||
|
// {
|
||
|
// GetVel().Set(); // then clear it to zero velocity
|
||
|
// }
|
||
|
#else
|
||
|
Mth::Vector brake = GetVel();
|
||
|
brake.Normalize(-GetPhysicsFloat(CRCD(0xe5d73f6f, "Physics_Brake_Acceleration")) * m_frame_length);
|
||
|
GetVel() += brake;
|
||
|
DUMP_VELOCITY;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::on_steep_slow_slope ( )
|
||
|
{
|
||
|
float speed = GetVel().Length();
|
||
|
if (speed < GetPhysicsFloat(CRCD(0x62a1fa03,"Skater_max_sloped_turn_speed"))
|
||
|
&& m_current_normal[Y] < GetPhysicsFloat(CRCD(0xc3527ef2, "Skater_max_sloped_turn_cosine")))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::can_kick ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
if (m_force_cankick_off)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// don't allow kicking if autokick is off and square not pressed
|
||
|
if (!m_auto_kick && !control_pad.m_square.GetPressed())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// don't allow kicking (accelerating) if "down" is pressed
|
||
|
// as we would either be braking or sharp turning
|
||
|
if (control_pad.m_down.GetPressed())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
float speed = GetVel().Length();
|
||
|
|
||
|
if (!GetFlag(TENSE))
|
||
|
{
|
||
|
if (speed > GetSkater()->GetScriptedStat(CRCD(0x4610c2e3, "Skater_Max_Standing_Kick_Speed_Stat")))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (speed > GetSkater()->GetScriptedStat(CRCD(0x92e0247c, "Skater_Max_Crouched_Kick_Speed_Stat")))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Dbg_Assert(m_auto_kick || control_pad.m_square.GetPressed());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_kick ( )
|
||
|
{
|
||
|
// Get skater's forward direction
|
||
|
Mth::Vector forward;
|
||
|
|
||
|
// K: Avoid anything that might change the velocity direction if this flag is set.
|
||
|
if (m_lock_velocity_direction)
|
||
|
{
|
||
|
forward = GetVel();
|
||
|
|
||
|
// Make sure the skater doesn't get stuck unable to move.
|
||
|
if (forward.Length() < 0.5f)
|
||
|
{
|
||
|
forward = GetMatrix()[Z];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
forward.Normalize();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
forward = GetMatrix()[Z];
|
||
|
}
|
||
|
|
||
|
if (GetFlag(TENSE))
|
||
|
{
|
||
|
forward *= GetSkater()->GetScriptedStat(CRCD(0x3d24128e, "Physics_crouching_Acceleration_stat"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
forward *= GetSkater()->GetScriptedStat(CRCD(0x5f9b864d, "Physics_Standing_Acceleration_stat"));
|
||
|
}
|
||
|
|
||
|
// apply to velocity
|
||
|
GetVel() += forward * m_frame_length;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_ground_friction ( )
|
||
|
{
|
||
|
// Apply the various type of friction that are acting when on the ground
|
||
|
|
||
|
// if no autokick, and we are not bailing, and we have default friction then don't apply any friction
|
||
|
// as it sucks to slow down when you do not have autokick
|
||
|
if (!m_auto_kick && !GetFlag(IS_BAILING) && m_rolling_friction == GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction")))
|
||
|
{
|
||
|
// autokick friction
|
||
|
// currently none
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// non-autokick friction
|
||
|
handle_wind_resistance();
|
||
|
handle_rolling_resistance();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_wind_resistance ( )
|
||
|
{
|
||
|
// Wind resistance differs, based on if we are crouched or not (as when we are crouched, we have a lower profile, so less wind resistance)
|
||
|
|
||
|
float crouched_friction = GetPhysicsFloat(CRCD(0xbed96eda, "Physics_Crouched_Air_Friction"));
|
||
|
float standing_friction = GetPhysicsFloat(CRCD(0x1a78b6fc, "Physics_Standing_Air_Friction"));
|
||
|
|
||
|
if (m_override_limits_time != 0.0f)
|
||
|
{
|
||
|
crouched_friction = m_override_air_friction;
|
||
|
standing_friction = m_override_air_friction;
|
||
|
}
|
||
|
|
||
|
if (GetFlag(TENSE))
|
||
|
{
|
||
|
apply_wind_resistance(crouched_friction);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
apply_wind_resistance(standing_friction);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_rolling_resistance ( )
|
||
|
{
|
||
|
// Only have rolling resistance if we are not going slow on a steep slope.
|
||
|
if (!slide_off_slow_steep_slope())
|
||
|
{
|
||
|
apply_rolling_friction();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::slide_off_slow_steep_slope ( )
|
||
|
{
|
||
|
// If we are on very steep ground and moving slowly then do not allow the skater to brake
|
||
|
if (on_steep_slow_slope())
|
||
|
{
|
||
|
float speed = GetVel().Length();
|
||
|
|
||
|
Mth::Vector forward;
|
||
|
if (speed < 0.001f)
|
||
|
{
|
||
|
forward = GetMatrix()[Z];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
forward = GetVel();
|
||
|
|
||
|
}
|
||
|
|
||
|
Mth::Vector fall(0.0f, -1.0f, 0.0f);
|
||
|
fall.ProjectToPlane(m_current_normal);
|
||
|
|
||
|
float angle = Mth::GetAngleAbout(forward, fall, GetMatrix()[Y]);
|
||
|
float rot = GetPhysicsFloat(CRCD(0x7dd5678b, "Skater_Slow_Turn_on_slopes")) * Mth::Sgn(angle) * m_frame_length;
|
||
|
if (Mth::Abs(rot) > Mth::Abs(angle))
|
||
|
{
|
||
|
// just about done, so just turn the last bit of angle left
|
||
|
rot = angle;
|
||
|
}
|
||
|
GetMatrix().RotateYLocal(rot);
|
||
|
m_lerping_display_matrix.RotateYLocal(rot);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::apply_rolling_friction ( )
|
||
|
{
|
||
|
// apply a constant braking friction; if the velocity reaches 0, then we will stop (and not change direction)
|
||
|
// This funtion will typically be used for rolling resistance
|
||
|
|
||
|
// rolling friction is a constant, and is mostly noticable at slow speeds
|
||
|
// if your speed is less than that produced by the force of rolling friction, then you will abruptly stop
|
||
|
Mth::Vector rolling_friction = GetVel();
|
||
|
float length = rolling_friction.Length();
|
||
|
|
||
|
if (length < 0.0001f)
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
DUMP_VELOCITY;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
rolling_friction *= 60.0f * m_rolling_friction * m_frame_length / length;
|
||
|
|
||
|
if (rolling_friction.LengthSqr() > length * length)
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel() -= rolling_friction;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::push_away_from_walls ( )
|
||
|
{
|
||
|
if (!m_wall_push_radius || !m_wall_push_speed) return;
|
||
|
|
||
|
m_wall_push.Set();
|
||
|
m_wall_dist = 1.0f;
|
||
|
|
||
|
Mth::Vector start, end;
|
||
|
|
||
|
start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2, "Skater_side_collide_height"));
|
||
|
|
||
|
end = start + GetMatrix()[X] * m_wall_push_radius;
|
||
|
check_for_wall_push(start, end, 0);
|
||
|
|
||
|
end = start - GetMatrix()[X] * m_wall_push_radius;
|
||
|
check_for_wall_push(start, end, 1);
|
||
|
|
||
|
end = start + GetMatrix()[Z] * m_wall_push_radius;
|
||
|
check_for_wall_push(start, end, 2);
|
||
|
|
||
|
end = start - GetMatrix()[Z] * m_wall_push_radius;
|
||
|
check_for_wall_push(start, end, 3);
|
||
|
|
||
|
if (m_wall_dist == 1.0f) return;
|
||
|
|
||
|
float push_speed = m_wall_push_speed * (1.0f - m_wall_dist);
|
||
|
m_wall_push.Normalize(push_speed);
|
||
|
GetVel() += m_wall_push;
|
||
|
GetVel().RotateToPlane(m_current_normal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// if facing into the wall, then rotate away from it
|
||
|
if (Mth::DotProduct(GetMatrix()[Z], m_feeler.GetNormal()) < 0.0f)
|
||
|
{
|
||
|
Mth::Vector target = GetMatrix()[Z];
|
||
|
target.RotateToPlane(m_feeler.GetNormal());
|
||
|
float angle = Mth::GetAngleAbout(GetMatrix()[Z], target, GetMatrix()[Y]);
|
||
|
|
||
|
float rot = m_wall_rotate_speed * Mth::Sgn(angle) * m_frame_length;
|
||
|
if (Mth::Abs(rot) > Mth::Abs(angle))
|
||
|
{
|
||
|
// just about done, so just turn the last bit of angle left
|
||
|
rot = angle;
|
||
|
}
|
||
|
GetMatrix().RotateYLocal(rot);
|
||
|
m_lerping_display_matrix.RotateYLocal(rot);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::check_for_wall_push ( const Mth::Vector& start, const Mth::Vector& end, int index )
|
||
|
{
|
||
|
// do not allow a push in this direction until a push in any other direction has NOT happenend for half a second
|
||
|
// this should prevent the nasty flickering
|
||
|
for (int i = 0; i < 4; i++)
|
||
|
{
|
||
|
if (i == index) continue;
|
||
|
|
||
|
// get time for opposite direction
|
||
|
Tmr::Time t = Tmr::ElapsedTime(m_push_time[index ^ 1]);
|
||
|
|
||
|
if (t > 500)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_col_start = start;
|
||
|
m_col_end = end;
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
m_wall_push += m_feeler.GetNormal();
|
||
|
if (m_feeler.GetDist() < m_wall_dist)
|
||
|
{
|
||
|
m_wall_dist = m_feeler.GetDist();
|
||
|
}
|
||
|
m_push_time[index] = Tmr::GetTime();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::move_to_skitch_point ( )
|
||
|
{
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
if (!mp_movable_contact_component->GetContact() || !mp_movable_contact_component->GetContact()->GetObject())
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x47d44b84, "OffMeterBottom"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CCompositeObject* p_skitch_object = mp_movable_contact_component->GetContact()->GetObject();
|
||
|
|
||
|
CSkitchComponent* p_skitch_comp = GetSkitchComponentFromObject(p_skitch_object);
|
||
|
Dbg_Assert(p_skitch_comp);
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Do moving between the skitch nodes
|
||
|
// if L1 or R1 pressed
|
||
|
// Basically we just look for nodes to the left or right of us at a
|
||
|
// one foot interval
|
||
|
// up a a maximum of 10 feet
|
||
|
// if one is found, then switch the m_node_index to that node
|
||
|
|
||
|
float dir = 0.0f;
|
||
|
|
||
|
if (control_pad.m_L1.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_L1.ClearTrigger();
|
||
|
dir = 1.0f;
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_R1.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_R1.ClearTrigger();
|
||
|
dir = -1.0f;
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_L2.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_L2.ClearTrigger();
|
||
|
dir = 1.0f;
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_R2.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_R2.ClearTrigger();
|
||
|
dir = -1.0f;
|
||
|
}
|
||
|
|
||
|
// if moving, and either was not moving or moving in opposite direction, then we can actually try looking for a node
|
||
|
if (dir != 0.0f && (!m_moving_to_skitch || dir != m_skitch_dir))
|
||
|
{
|
||
|
float len = 6.0f;
|
||
|
float step = 6.0f;
|
||
|
float max = 12.0f * 10.0f;
|
||
|
while (len < max)
|
||
|
{
|
||
|
// get an offset to the right or left
|
||
|
Mth::Vector offset = GetMatrix()[X]; // X points left, from the camera's POV
|
||
|
offset *= len * dir;
|
||
|
Mth::Vector test_pos = GetPos() + offset;
|
||
|
|
||
|
// see if there is a skitch point there
|
||
|
Mth::Vector dummy(0.0f, 0.0f, 0.0f);
|
||
|
int index = p_skitch_comp->GetNearestSkitchPoint(&dummy, test_pos);
|
||
|
|
||
|
// if there is, then switch to that point
|
||
|
if (index != m_skitch_index)
|
||
|
{
|
||
|
m_skitch_index = index;
|
||
|
m_moving_to_skitch = true;
|
||
|
m_skitch_dir = dir;
|
||
|
|
||
|
if (dir == 1.0f)
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x74bf80cf, "SkitchLeft"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x2e7474b5, "SkitchRight"));
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
len += step;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// end of moving between skitch nodes
|
||
|
///////////////////////////////////////////////////
|
||
|
|
||
|
if (!mp_movable_contact_component->GetContact() || !GetFlag(SKITCHING)) return;
|
||
|
|
||
|
///////////////////////////////////////////////////////
|
||
|
// Do the actual movement
|
||
|
|
||
|
Mth::Vector skitch_point;
|
||
|
if (!p_skitch_comp->GetIndexedSkitchPoint( &skitch_point, m_skitch_index)) return;
|
||
|
|
||
|
// zero out W component to prevent overflows (in theory this should not be necessary)
|
||
|
GetPos()[W] = 0.0f;
|
||
|
|
||
|
Mth::Vector target = skitch_point + GetPhysicsFloat(CRCD(0x21fb182c, "skitch_offset")) * -p_skitch_object->GetMatrix()[Z];
|
||
|
|
||
|
if (m_moving_to_skitch)
|
||
|
{
|
||
|
Mth::Vector con_move = mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
|
||
|
GetPos() += con_move;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
Mth::Vector dir = target - GetPos();
|
||
|
float dir_length_sqr = dir.LengthSqr();
|
||
|
float suck_speed = GetPhysicsFloat(CRCD(0x97496256, "skitch_suck_speed")) * m_frame_length;
|
||
|
|
||
|
// if skater is stuck in a wall, then end the skitch when car is fifteen feet away
|
||
|
if (dir_length_sqr > FEET(15.0f) * FEET(15.0f))
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x47d44b84, "OffMeterBottom"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (dir_length_sqr <= suck_speed * suck_speed)
|
||
|
{
|
||
|
// we have arrived, so no need for sucking later
|
||
|
GetPos() = target;
|
||
|
m_moving_to_skitch = false;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dir *= suck_speed / sqrtf(dir_length_sqr);
|
||
|
GetPos() += dir;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetPos() = target;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
// Copy the objects Display matrix over the skater's
|
||
|
// will orient the skater the same way as the thing that is dragging it, so if the car looks solid, then so should the skater...
|
||
|
GetMatrix() = p_skitch_object->GetDisplayMatrix();
|
||
|
|
||
|
// we also set the display matrix, to avoid little glitches
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_forward_collision ( )
|
||
|
{
|
||
|
if (GetPos() == GetOldPos()) return;
|
||
|
|
||
|
Mth::Vector forward = GetPos() - GetOldPos();
|
||
|
forward.Normalize();
|
||
|
|
||
|
Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height"));
|
||
|
|
||
|
m_col_start = GetOldPos() + up_offset;
|
||
|
|
||
|
m_col_end = GetPos() + up_offset + forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
|
||
|
|
||
|
if (!get_member_feeler_collision()) return;
|
||
|
|
||
|
// we have hit something going forward
|
||
|
// either it is a wall, and we need to bounce off it or it is a steep QP, and we need to stick to it.
|
||
|
// (note, with the slow normal changing, then it is possible that we might even collide with the poly that we are on
|
||
|
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
float dot = Mth::DotProduct(normal, m_current_normal);
|
||
|
|
||
|
// For fairly shallow curves, the dot between two normals will be pretty large
|
||
|
// it's very important here to distinguish between a tight curve (like in a narrow QP) and a direct hit with a wall.
|
||
|
|
||
|
if (!m_col_flag_skatable || Mth::Abs(dot) < 0.01f)
|
||
|
{
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
|
||
|
bounce_off_wall(normal);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// it's a qp, stick to it
|
||
|
|
||
|
// Just move our contact point to the point of collision. This is not right, as it could kill a lot of the movement for this frame
|
||
|
// but should do for now (and stops you falling through)
|
||
|
GetPos() = m_feeler.GetPoint();
|
||
|
|
||
|
// move it off the surface a little, so we are not IN it (which would be indeterminates as to which side)
|
||
|
GetPos() += normal * 0.1f;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::bounce_off_wall ( const Mth::Vector& normal )
|
||
|
{
|
||
|
if (check_for_wallpush()) return;
|
||
|
|
||
|
// Given the normal of the wall, then bounce off it, turning the skater away from the wall
|
||
|
|
||
|
Mth::Vector forward = GetPos() - GetOldPos();
|
||
|
Mth::Vector movement = forward; // remember how far we moved
|
||
|
forward.Normalize();
|
||
|
|
||
|
Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height"));
|
||
|
|
||
|
float turn_angle;
|
||
|
float angle = rotate_away_from_wall(normal, turn_angle);
|
||
|
|
||
|
float min = Mth::DegToRad(GetPhysicsFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle")));
|
||
|
|
||
|
if (Mth::Abs(angle) > min)
|
||
|
{
|
||
|
float old_speed = GetVel().Length();
|
||
|
|
||
|
// The maximum value of Abs(angle) would be PI/2 (90 degrees), so scale the velocity
|
||
|
float x = Mth::Abs(angle) - min;
|
||
|
x /= (Mth::PI / 2.0f) - min; // get in the range 0 .. 1
|
||
|
x = 1.0f - x; // invert, as we want to stop when angle is 90
|
||
|
|
||
|
GetVel() *= x;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// if (negative ^ flipped) then backwards flail, otherwise forward flail
|
||
|
|
||
|
if (old_speed > GetPhysicsFloat(CRCD(0xbe0a58a0, "Wall_Bounce_Dont_Flail_Speed")))
|
||
|
{
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
{
|
||
|
Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height"));
|
||
|
Mth::Vector start = up_offset+GetOldPos();
|
||
|
Mth::Vector end = up_offset+GetPos();
|
||
|
TrackingLine(1, start, end); // 1 = wall bounce flail
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ((angle < 0.0f) ^ (GetFlag(FLIPPED)))
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0xb4101d70,"FlailLeft"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x756a7535,"FlailRight"));
|
||
|
}
|
||
|
|
||
|
// Player's terrain isn't set to the terrain in m_feeler, as this is a wall we're bouncing off of (or chain link fence or something)...
|
||
|
// Steve: we gots ta figure out how to do this...
|
||
|
// Perhaps have another terrain value sent to client skaters that tell them to play a bonk sound?
|
||
|
mp_sound_component->PlayBonkSound( old_speed / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_feeler.GetTerrain());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Bit of a patch here to move the skater away from the wall
|
||
|
// Not needed so much with new sideways collision checks but we keep it in for low ledges.
|
||
|
// Should perhaps standardize the height so collision checks for side and front but we'd probably still have problems.
|
||
|
|
||
|
GetPos() = m_feeler.GetPoint() - up_offset;
|
||
|
GetPos() += normal * 6.0f;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Now the majority of cases have been taken care of; we need to see if the skater is going to get stuck in a corner.
|
||
|
|
||
|
float old_speed = movement.Length(); // get how much we moved last time
|
||
|
Mth::Vector next_movement = GetVel(); // get new direction of velocity
|
||
|
forward = GetVel();
|
||
|
forward.Normalize();
|
||
|
next_movement = forward * old_speed; // extend by same movment as last time
|
||
|
|
||
|
m_col_start = GetPos() + up_offset;
|
||
|
m_col_start += up_offset;
|
||
|
|
||
|
m_col_end = GetPos() + up_offset + next_movement
|
||
|
+ forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
|
||
|
|
||
|
if (get_member_feeler_collision() && GetSkater()->IsLocalClient())
|
||
|
{
|
||
|
// Just rotating the skater will lead to another collision, so try just inverting the skater's velocity from it's original and halving it....
|
||
|
|
||
|
// First reverse the rotation, and rotate 180 degrees
|
||
|
GetVel().RotateY(Mth::DegToRad(180.0f) - turn_angle);
|
||
|
GetMatrix().RotateYLocal(Mth::DegToRad(180.0f) - turn_angle);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
GetVel() *= 0.5f;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
if (old_speed > GetPhysicsFloat(CRCD(0xbe0a58a0, "Wall_Bounce_Dont_Flail_Speed")))
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0xb4101d70, "FlailLeft"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::snap_to_ground ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
float up_dot = 0.0f;
|
||
|
|
||
|
// Since we really don't want to loose contact with the ground while skitching, we use a much bigger snap up dist
|
||
|
// The problem will come when we get dragged down a slope. The car will flatten out well ahead of us, so pushing us down through the slop
|
||
|
// (as we are a few feet behind it) and we will be so far under the ground that our normal snap up will not be able to dig us out of it,
|
||
|
// so we go in air, uberfrig, and get dragged to a random spot under the level.
|
||
|
// (This would not happen if we just skitch on flat ground)
|
||
|
|
||
|
// Dan: snap_to_ground is never called while skitching
|
||
|
// if (GetFlag(SKITCHING))
|
||
|
// {
|
||
|
// m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0x5c0d9610,"Physics_Ground_Snap_Up_SKITCHING")); // much above feet
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up")); // bit above the feet
|
||
|
// }
|
||
|
|
||
|
m_col_end = GetMatrix()[Y] * -200.0f; // WAY! below the feet, we check distance later
|
||
|
|
||
|
m_col_start += GetPos();
|
||
|
m_col_end += GetPos();
|
||
|
|
||
|
bool sticking = false;
|
||
|
// get disatnce to ground and snap the skater to it, but only if the ground is skatable, otherwise we just go to "AIR"
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
Mth::Vector movement = GetPos() - m_feeler.GetPoint();
|
||
|
float drop_dist = movement.Length();
|
||
|
float drop_sign = Mth::DotProduct(GetMatrix()[Y], movement); // might be approx +/- 0.00001f
|
||
|
if (!m_col_flag_skatable)
|
||
|
{
|
||
|
// if below the face (or very close to it), then push us away from it
|
||
|
if (drop_sign < 0.001f)
|
||
|
{
|
||
|
// at point of contact, and move away from surface
|
||
|
GetPos() = m_feeler.GetPoint() + m_feeler.GetNormal();
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sticking = true;
|
||
|
|
||
|
// Note the two ways of calculating the angle between two faces
|
||
|
// the more "accurate" method simply takes angle between the two normals
|
||
|
// however, this fails to account for the direction the skater is travelling
|
||
|
// when in conjunction with the "snap" up.
|
||
|
// If the skater approaches a slope from the side, then he can snap up to the slope
|
||
|
// however, if the angle between the ground and the face to which we are snapping up to
|
||
|
// is too great, then we transition to in-air
|
||
|
// and will drop through the slope
|
||
|
// the solution is to take the angle between the front vector rotated onto each face.
|
||
|
|
||
|
// Firstly we check the angle between the two faces
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
Mth::Vector forward = GetMatrix()[Z];
|
||
|
float front_dot = Mth::DotProduct(forward,normal);
|
||
|
|
||
|
Mth::Vector old_forward = forward;
|
||
|
forward.RotateToPlane(normal);
|
||
|
old_forward.RotateToPlane(m_current_normal);
|
||
|
|
||
|
// angle between front vectors, projected onto faces
|
||
|
up_dot = Mth::DotProduct(forward, old_forward);
|
||
|
|
||
|
float stick_angle_cosine;
|
||
|
if (!control_pad.m_up.GetPressed())
|
||
|
{
|
||
|
stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xef161c2a, "Ground_stick_angle"))));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x4138283e, "Ground_stick_angle_forward"))));
|
||
|
}
|
||
|
|
||
|
if (front_dot > 0.0f && up_dot > 0.0f && up_dot < stick_angle_cosine)
|
||
|
{
|
||
|
sticking = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// only need to test if snap is downwards
|
||
|
if (drop_sign > 0.0f)
|
||
|
{
|
||
|
// angle between the old plane and the new plane is either the same, or withing the set limits
|
||
|
// (like, < 60 degrees, or so, see physics.q) now calculate the drop distance, based on the angle
|
||
|
|
||
|
#ifdef __PLAT_NGC__
|
||
|
float angle = acosf(Mth::Clamp(up_dot, -1.0f, 1.0f));
|
||
|
#else
|
||
|
float angle = acosf(up_dot);
|
||
|
#endif // __PLAT_NGC__
|
||
|
|
||
|
Mth::Vector last_move = GetPos() - GetOldPos();
|
||
|
|
||
|
float max_drop = last_move.Length() * tanf(angle);
|
||
|
|
||
|
float min_drop = GetPhysicsFloat(CRCD(0x899ba3d0, "Physics_Ground_Snap_Down"));
|
||
|
// if (GetFlag(SKITCHING))
|
||
|
// {
|
||
|
// min_drop = GetPhysicsFloat(CRCD(0x20df7e33, "Physics_Ground_Snap_Down_Skitching"));
|
||
|
// }
|
||
|
|
||
|
if (max_drop < min_drop)
|
||
|
{
|
||
|
max_drop = min_drop;
|
||
|
}
|
||
|
|
||
|
if (drop_dist > max_drop)
|
||
|
{
|
||
|
sticking = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (sticking)
|
||
|
{
|
||
|
SetFlag(LAST_POLY_WAS_VERT, m_col_flag_vert);
|
||
|
new_normal(normal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (sticking)
|
||
|
{
|
||
|
// if there is a collision, then snap to it
|
||
|
GetPos() = m_feeler.GetPoint();
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
if (GetState() == GROUND && movement.Length() > 2.0f)
|
||
|
{
|
||
|
// curbs are assumed to be between parallel surfaces, so check this...
|
||
|
if (up_dot > 0.99f)
|
||
|
{
|
||
|
SetFlag(SNAPPED_OVER_CURB, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// will return trivially if terrain is already set to this type...
|
||
|
set_terrain(m_feeler.GetTerrain());
|
||
|
|
||
|
adjust_normal();
|
||
|
|
||
|
// check to see if we have skated onto a movable object
|
||
|
check_movable_contact();
|
||
|
|
||
|
// still on ground, so store the latest ground collision data
|
||
|
// check first to see if we are about to change
|
||
|
if (m_last_ground_feeler.GetSector() != m_feeler.GetSector())
|
||
|
{
|
||
|
// changin sectors, so check the sector we came from and the one we are going to
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
}
|
||
|
|
||
|
if (!mp_trick_component->GraffitiTrickStarted())
|
||
|
{
|
||
|
// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
|
||
|
mp_trick_component->SetGraffitiTrickStarted(false);
|
||
|
}
|
||
|
|
||
|
set_last_ground_feeler(m_feeler);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// skated off surface into the air
|
||
|
if (!sticking)
|
||
|
{
|
||
|
SetState(AIR);
|
||
|
GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
|
||
|
FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
|
||
|
|
||
|
maybe_straight_up();
|
||
|
|
||
|
if (GetFlag(VERT_AIR))
|
||
|
{
|
||
|
SetFlagTrue(CAN_BREAK_VERT);
|
||
|
|
||
|
// we want to break vert, but don't want to go into spine physics for a frame
|
||
|
// so don't do this check if we are trying to break the spine
|
||
|
// (we can still break vert or spine on the next frame, when in the air)
|
||
|
if (!BREAK_SPINE_BUTTONS)
|
||
|
{
|
||
|
maybe_break_vert();
|
||
|
}
|
||
|
|
||
|
// if we did not break vert now
|
||
|
// then only allow us to break vert later if we've been tapping the "up" button
|
||
|
// this is indicated by us having RELEASED or Pressed in teh last few ticks
|
||
|
if (static_cast< int >(control_pad.m_up.GetReleasedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time"))
|
||
|
&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time")))
|
||
|
{
|
||
|
// "UP" was not pressed any time recently, so don't let us break late
|
||
|
SetFlagFalse(CAN_BREAK_VERT);
|
||
|
}
|
||
|
}
|
||
|
else if (BREAK_SPINE_BUTTONS)
|
||
|
{
|
||
|
SAcidDropData acid_drop_data;
|
||
|
if (maybe_acid_drop(true, GetPos(), GetOldPos(), GetVel(), acid_drop_data))
|
||
|
{
|
||
|
enter_acid_drop(acid_drop_data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::new_normal ( Mth::Vector normal )
|
||
|
{
|
||
|
if (Ed::CParkEditor::Instance()->UsingCustomPark())
|
||
|
{
|
||
|
// check if this new normal will make us lean into a wall
|
||
|
if (normal[Y] > 0.0f)
|
||
|
{
|
||
|
CFeeler feeler(GetPos(), GetPos() + 72.0f * normal);
|
||
|
if (feeler.GetCollision())
|
||
|
{
|
||
|
normal.Set(0.0f,1.0f,0.0f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (normal != m_current_normal)
|
||
|
{
|
||
|
m_current_normal = normal; // remember this, for detecting if it changes
|
||
|
m_last_display_normal = m_display_normal; // remember start position for lerping
|
||
|
m_normal_lerp = 1.0f; // set lerp counter
|
||
|
|
||
|
GetMatrix()[Y] = normal;
|
||
|
GetMatrix().OrthoNormalizeAbout(Y); // set regular normal immediately
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::adjust_normal ( )
|
||
|
{
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// As the skater moves over the ground, he will go from being in contact
|
||
|
// with one polygon, to another.
|
||
|
// The "normal" of a polygon is the vector perpendicular to the surface of
|
||
|
// the polygon, and the skater is normally aligned so that the "up" vector
|
||
|
// of the skater (the Y component of the orientation matrix) is the same
|
||
|
// as the normal or the polgon he is in contact with.
|
||
|
//
|
||
|
// However, as we go from one polygon to another (in a quarterpipe, for example)
|
||
|
// the normal will change abruptly, and this looks very jerky
|
||
|
// So, we try to smooth this out by remembering the old normal, and interpolating
|
||
|
// towards the current display normal
|
||
|
// this is done at a fixed speed, controled by the script value "Normal_Lerp_Speed"
|
||
|
// whihc is defined in phsyics.q
|
||
|
//
|
||
|
// m_normal_lerp represents how far off the current face normal we are, it
|
||
|
// will vary from 1.0 (still at the last display normal) to 0.0 (at current normal)
|
||
|
// the intermediate normal is stored in m_display_normal
|
||
|
// and is also copied directly into m_lerping_display_matrix, to rotate the skater
|
||
|
|
||
|
// if m_normal_lerp is 0.0, then we don't need to do anything, as we should already be there
|
||
|
if (m_normal_lerp != 0.0f) // if lerping
|
||
|
{
|
||
|
// If the last display normal is the same as the current normal, then we can't interpolate between them
|
||
|
if (m_last_display_normal == m_current_normal)
|
||
|
{
|
||
|
m_normal_lerp = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// adjust lerp at constant speed from 1.0 to 0.0, accounting for framerate
|
||
|
m_normal_lerp -= GetPhysicsFloat(CRCD(0xd8120182, "Normal_Lerp_Speed")) * m_frame_length * 60.0f;
|
||
|
|
||
|
// if gone all the way, then clear lerping values and set m_display_normal to be the current face normal
|
||
|
if (m_normal_lerp <= 0.0f)
|
||
|
{
|
||
|
m_normal_lerp = 0.0f;
|
||
|
m_display_normal = m_current_normal;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Still between old and current normal, so calculate intermediate normal
|
||
|
m_display_normal = Mth::Lerp(m_current_normal, m_last_display_normal, m_normal_lerp);
|
||
|
m_display_normal.Normalize();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now update the orientation matrix.
|
||
|
// We need our up (Y) vector to be this vector
|
||
|
// if it changes, rotate the X and Z vectors to match
|
||
|
if (m_lerping_display_matrix[Y] != m_display_normal)
|
||
|
{
|
||
|
// lerp the y axis
|
||
|
m_lerping_display_matrix[Y] = m_display_normal;
|
||
|
m_lerping_display_matrix.OrthoNormalizeAbout(Y);
|
||
|
m_lerping_display_matrix[X] = GetMatrix()[X];
|
||
|
m_lerping_display_matrix[Z] = GetMatrix()[Z];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::check_movable_contact ( )
|
||
|
{
|
||
|
// given m_feeler is our current contact point with the ground, then check if our mp_movable_contact_component->GetContact() information needs updating
|
||
|
|
||
|
if (GetFlag(SKITCHING)) return;
|
||
|
|
||
|
if (mp_movable_contact_component->CheckForMovableContact(m_feeler))
|
||
|
{
|
||
|
GetVel() -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::maybe_straight_up ( )
|
||
|
{
|
||
|
// Skater has just left gound, either jumped, or skated off it
|
||
|
// so we need to check if the ground we left was flagged as VERT and if it is,
|
||
|
// then we need to set our skater's velocity and orientation so they go straight up
|
||
|
|
||
|
if (GetFlag(LAST_POLY_WAS_VERT))
|
||
|
{
|
||
|
TrackingLine(3, GetOldPos(), GetPos()); // 3 = going vert
|
||
|
|
||
|
// Get the normal to the plane we jumped from
|
||
|
Mth::Vector up_plane_normal = m_current_normal;
|
||
|
|
||
|
m_vert_normal = m_current_normal;
|
||
|
m_vert_pos = GetOldPos();
|
||
|
|
||
|
// move vert pos down one inch to better track subtle changes in edge height
|
||
|
m_vert_pos[Y] -= 1.0f;
|
||
|
|
||
|
// clear any Y component to this plane, makes it vertical
|
||
|
up_plane_normal[Y] = 0.0f;
|
||
|
|
||
|
if (up_plane_normal.Length() > 0.001f)
|
||
|
{
|
||
|
// and re-normalize to get a unit normal to the vertical plane.
|
||
|
up_plane_normal.Normalize();
|
||
|
|
||
|
GetVel().RotateToPlane(up_plane_normal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
new_normal(up_plane_normal);
|
||
|
|
||
|
// Fall line is used for auto turn
|
||
|
m_fall_line = GetMatrix()[Z];
|
||
|
m_fall_line[Y] = -m_fall_line[Y];
|
||
|
|
||
|
// offset the jumper away from the plane by an inch
|
||
|
GetPos() += up_plane_normal * GetPhysicsFloat(CRCD(0x78384871, "Physics_Vert_Push_Out"));
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
SetFlagTrue(VERT_AIR);
|
||
|
SetFlagTrue(TRACKING_VERT);
|
||
|
SetFlagTrue(AUTOTURN);
|
||
|
m_vert_upstep = 6.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::maybe_break_vert ( )
|
||
|
{
|
||
|
// We "Break Vert" when we are pressing forward at the start of transitioning from the ground into a "vert" state
|
||
|
// however, we need to defer the test until there is no ground in that "forward" direction (which will be directly beneath the skater's feet)
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
if (
|
||
|
(
|
||
|
control_pad.m_up.GetPressed()
|
||
|
&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x9d2f8cc8, "Skater_vert_push_time"))
|
||
|
&& !control_pad.m_left.GetPressed() // must be JUST up, to avoid accidents when turning
|
||
|
&& !control_pad.m_right.GetPressed()
|
||
|
&& !control_pad.m_square.GetPressed() // and don't break if trying to do a trick involving up
|
||
|
&& !control_pad.m_circle.GetPressed()
|
||
|
)
|
||
|
||
|
||
|
(
|
||
|
BREAK_SPINE_BUTTONS
|
||
|
)
|
||
|
)
|
||
|
{
|
||
|
// aha... up is pressed, so we want to break the air poly
|
||
|
|
||
|
if (!BREAK_SPINE_BUTTONS)
|
||
|
{
|
||
|
// normal breaking the air polygon, we just fly forward
|
||
|
|
||
|
float speed = GetVel().Length() * GetPhysicsFloat(CRCD(0x13f33b41, "physics_break_air_speed_scale"));
|
||
|
|
||
|
GetVel()[X] += -m_display_normal[X] * speed;
|
||
|
GetVel()[Z] += -m_display_normal[Z] * speed;
|
||
|
GetVel()[Y] *= GetPhysicsFloat(CRCD(0x848c5cd6, "physics_break_air_up_scale"));
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// Now, since we broke the vert poly, the way it was set up, the skater will have alrready been snapped to vertical position
|
||
|
// so we need to rotate him forwads 45 degrees to componsate
|
||
|
GetMatrix().RotateXLocal(Mth::DegToRad(GetPhysicsFloat(CRCD(0xd757f4bb, "Skater_Break_Vert_forward_tilt"))));
|
||
|
|
||
|
SetFlagFalse(VERT_AIR); // just regular air, if we broke the air poly
|
||
|
SetFlagFalse(TRACKING_VERT); // and certainly not tracking the vert
|
||
|
SetFlagFalse(CAN_BREAK_VERT); // and as we broke vert, we don't want to do it again
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED); // allow acid drops once once again
|
||
|
|
||
|
// and we want to be going in the direction of our velocity, so set front x and Z, but leave Y
|
||
|
Mth::Vector vel_normal = GetVel();
|
||
|
vel_normal.Normalize();
|
||
|
|
||
|
GetMatrix()[Z][X] = vel_normal[X];
|
||
|
GetMatrix()[Z][Z] = vel_normal[Z];
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix().OrthoNormalizeAbout(Z);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!maybe_spine_transfer())
|
||
|
{
|
||
|
// cannot find a transfer target, so just break the air polygon
|
||
|
GetVel()[X] += -m_display_normal[X] * 24.0f;
|
||
|
GetVel()[Z] += -m_display_normal[Z] * 24.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
GetMatrix().RotateXLocal(Mth::DegToRad(GetPhysicsFloat(CRCD(0xd757f4bb, "Skater_Break_Vert_forward_tilt"))));
|
||
|
|
||
|
SetFlagFalse(VERT_AIR); // just regular air, if we broke the air poly
|
||
|
SetFlagFalse(TRACKING_VERT); // and certainly not tracking the vert
|
||
|
SetFlagFalse(CAN_BREAK_VERT); // and as we broke vert, we don't want to do it again
|
||
|
SetFlagTrue(IN_RECOVERY); // tell him to just upright himself
|
||
|
|
||
|
// and we want to be going in the direction of our velocity, so set front X and Z, but leave Y
|
||
|
Mth::Vector vel_normal = GetVel();
|
||
|
vel_normal.Normalize();
|
||
|
|
||
|
GetMatrix()[Z][X] = vel_normal[X];
|
||
|
GetMatrix()[Z][Z] = vel_normal[Z];
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix().OrthoNormalizeAbout(Z);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::maybe_spine_transfer ( )
|
||
|
{
|
||
|
// Break spin button is pressed, so try to break the spine
|
||
|
|
||
|
// The line to check along is the skater's forward directinal vector, rotated onto the XZ plane
|
||
|
// if you go straight up the wall, then this will be the same as the normal of the wall (in XY) as we previously calculated
|
||
|
// however we also want to handle the cases where you approach the QP at an angle
|
||
|
|
||
|
// Need to take the forward vector (Z) and rotate it "forward" 90 degrees
|
||
|
// Rotate about an axis perpendicular to both the horizontal part of m_matrix[Y] and also the world up (0,1,0)
|
||
|
|
||
|
Mth::Vector skater_up = GetMatrix()[Y]; // skater_up is expected to be horizontal here, as we are "vert"
|
||
|
skater_up[Y] = 0.0f;
|
||
|
skater_up.Normalize();
|
||
|
|
||
|
// get a vector perpendicular to the plane containing m_matrix[Z] and the world up
|
||
|
#if 0 // old code - crossing by axis alined vector bugs me
|
||
|
// Mth::Vector world_up(0.0f, 1.0f, 0.0f);
|
||
|
// Mth::Vector side = Mth::CrossProduct(skater_up, world_up);
|
||
|
#else
|
||
|
Mth::Vector side(-skater_up[Z], 0.0f, skater_up[X], 0.0f);
|
||
|
#endif
|
||
|
|
||
|
// assuming we have not slowed down much, then our velocity should roughly be in the direction we took off from
|
||
|
Mth::Vector forward = -GetVel();
|
||
|
forward.Normalize();
|
||
|
|
||
|
Mth::Vector wall_out = forward; // forward facing vector
|
||
|
wall_out.Rotate(side, Mth::PI / 2.0f); // rotate fowrad 90 degrees
|
||
|
|
||
|
float speed;
|
||
|
float dist = 12.0f;
|
||
|
float time = 1.0f;
|
||
|
bool hip_transfer = false;
|
||
|
|
||
|
CFeeler feeler;
|
||
|
|
||
|
// Here the "wall" is what we are currently skating on, anything with "wall" in the name refers to that
|
||
|
|
||
|
Mth::Vector target;
|
||
|
Mth::Vector target_normal;
|
||
|
bool target_found = false;
|
||
|
|
||
|
// First find a point beneath our current position
|
||
|
// Nice long line, higher than we can posibly jump
|
||
|
feeler.m_start = GetPos() + wall_out * 0.5f;
|
||
|
feeler.m_end = GetPos() + wall_out * 0.5f;
|
||
|
feeler.m_end[Y] -= 4000.0f;
|
||
|
|
||
|
// ignore everything that is NOT vert
|
||
|
// feeler.SetIgnore(0, mFD_VERT);
|
||
|
|
||
|
Mth::Vector wall_pos;
|
||
|
if (feeler.GetCollision())
|
||
|
{
|
||
|
wall_pos = feeler.GetPoint();
|
||
|
|
||
|
Mth::Vector start_normal = feeler.GetNormal();
|
||
|
start_normal[Y] = 0.0f;
|
||
|
start_normal.Normalize();
|
||
|
|
||
|
target_found = look_for_transfer_target(-wall_out, start_normal, hip_transfer, target, target_normal);
|
||
|
|
||
|
if (!target_found)
|
||
|
{
|
||
|
Mth::Vector left_along_vert(-start_normal[Z], 0.0f, start_normal[X]);
|
||
|
|
||
|
// no target was found in the forward direction, perhaps we should look slightly left or right; look in the horizontal direction which is
|
||
|
// halfway between the previous search direction and the plane of the vert
|
||
|
if (mp_input_component->GetControlPad().m_left.GetPressed() && !mp_input_component->GetControlPad().m_right.GetPressed())
|
||
|
{
|
||
|
Mth::Vector search_dir = left_along_vert + -wall_out;
|
||
|
search_dir.Normalize();
|
||
|
target_found = look_for_transfer_target(search_dir, start_normal, hip_transfer, target, target_normal);
|
||
|
}
|
||
|
else if (mp_input_component->GetControlPad().m_right.GetPressed() && !mp_input_component->GetControlPad().m_left.GetPressed())
|
||
|
{
|
||
|
Mth::Vector search_dir = -left_along_vert + -wall_out;
|
||
|
search_dir.Normalize();
|
||
|
target_found = look_for_transfer_target(search_dir, start_normal, hip_transfer, target, target_normal);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!target_found) return false;
|
||
|
|
||
|
Mth::Vector XZ_to_target = target - wall_pos;
|
||
|
XZ_to_target[Y] = 0.0f;
|
||
|
dist = XZ_to_target.Length();
|
||
|
|
||
|
// We are only going to allow this later if the target point is the same level
|
||
|
// as the takeoff point, and we have a clear line
|
||
|
// so set it to this now, so we calculate the time correctly
|
||
|
target[Y] = GetPos()[Y];
|
||
|
|
||
|
// if the two faces are not really perpendicular or if the spine is wider than
|
||
|
// then we determine that we are on a "hip" and we just want to go across it without drifting left or right
|
||
|
// so we want to project all our velocity straight up
|
||
|
|
||
|
Mth::Vector horizontal_target_normal = target_normal;
|
||
|
horizontal_target_normal[Y] = 0.0f;
|
||
|
horizontal_target_normal.Normalize();
|
||
|
|
||
|
Mth::Vector cache_vel = GetVel();
|
||
|
|
||
|
float face_dot = Mth::Abs(Mth::DotProduct(skater_up, horizontal_target_normal));
|
||
|
if (face_dot < 0.9f)
|
||
|
{
|
||
|
GetVel()[Y] = GetVel().Length();
|
||
|
GetVel()[X] = 0.0f;
|
||
|
GetVel()[Z] = 0.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if spine more than two feet wide, then also don't allow drift
|
||
|
if (dist > FEET(2.0f))
|
||
|
{
|
||
|
GetVel()[Y] = GetVel().Length();
|
||
|
GetVel()[X] = 0.0f;
|
||
|
GetVel()[Z] = 0.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// one inch out, to ensure miss the lip
|
||
|
dist += 1.0f;
|
||
|
|
||
|
#if 0 // old transfer code
|
||
|
// get angle to rotate about, being the vector perpendicular to the world up vector and the difference between the two face normals
|
||
|
// (generally for a spine these normals will be opposite, however they might be up to 90 degrees or more off when doing a hip)
|
||
|
|
||
|
Mth::Vector normal_diff = target_normal - skater_up;
|
||
|
normal_diff[Y] = 0.0f;
|
||
|
normal_diff.Normalize();
|
||
|
|
||
|
m_spine_rotate_axis[X] = -normal_diff[Z];
|
||
|
m_spine_rotate_axis[Y] = 0.0f;
|
||
|
m_spine_rotate_axis[Z] = normal_diff[X];
|
||
|
m_spine_rotate_axis[W] = 0.0f;;
|
||
|
#endif
|
||
|
|
||
|
// for gravity calculations, temporarily pretend we are doing spine physics, so g is constant
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
time = calculate_time_to_reach_height(target[Y], GetPos()[Y], GetVel()[Y]);
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
|
||
|
// subtract some frames of time, to ensure we make it
|
||
|
// time -= m_frame_length * 2.0f;
|
||
|
|
||
|
if (time < 0.1f)
|
||
|
{
|
||
|
time = 0.1f;
|
||
|
}
|
||
|
|
||
|
speed = dist / time;
|
||
|
|
||
|
// if spine more than two foot wide, then make sure that we have enough speed to get over it
|
||
|
// otherwise, just do a little pop over, and allow them to recover
|
||
|
if (dist > 24.0f && speed * speed > GetVel().LengthSqr())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// we have found a target point, either by looking directly in front or by doing the drop-down method
|
||
|
// but we don't want to go for it until there is a clear line from our current position to the target
|
||
|
|
||
|
Mth::Vector target_XZ = target;
|
||
|
target_XZ[Y] = GetPos()[Y];
|
||
|
|
||
|
feeler.m_start = GetPos();
|
||
|
feeler.m_end = target_XZ;
|
||
|
if (feeler.GetCollision())
|
||
|
{
|
||
|
// don't do anything. We have a valid transfer but we can wait until we get high enough before we try for it
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// setup the transfer's matrix slerp
|
||
|
|
||
|
Mth::Vector land_facing;
|
||
|
if (!hip_transfer)
|
||
|
{
|
||
|
land_facing = target - GetPos();
|
||
|
land_facing[Y] = -(land_facing[X] * target_normal[X] + land_facing[Z] * target_normal[Z]) / target_normal[Y];
|
||
|
land_facing.Normalize();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Mth::Vector offset = target - GetPos();
|
||
|
offset.Normalize();
|
||
|
float dot = Mth::DotProduct(offset, horizontal_target_normal);
|
||
|
if (dot < 0.0f)
|
||
|
{
|
||
|
land_facing.Set(0.0f, 1.0f, 0.0f);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
land_facing.Set(0.0f, -1.0f, 0.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Mth::Matrix transfer_slerp_start = GetMatrix();
|
||
|
|
||
|
// calculate the facing we want when we land; retain our horizontal direction and choose a vertical component which puts us parallel so the target
|
||
|
// poly's plane
|
||
|
|
||
|
// calculate goal matrix
|
||
|
Mth::Matrix transfer_slerp_goal;
|
||
|
transfer_slerp_goal[Z] = land_facing;
|
||
|
transfer_slerp_goal[Z].ProjectToPlane(target_normal);
|
||
|
transfer_slerp_goal[Z].Normalize();
|
||
|
transfer_slerp_goal[Y] = target_normal;
|
||
|
transfer_slerp_goal[X] = Mth::CrossProduct(transfer_slerp_goal[Y], transfer_slerp_goal[Z]);
|
||
|
transfer_slerp_goal[W].Set();
|
||
|
|
||
|
// store the goal facing for use in adjusting the velocity at land time
|
||
|
m_transfer_goal_facing = transfer_slerp_goal[Z];
|
||
|
|
||
|
// if the skater is entering the spine transfer with an odd facing due to rotation, we want to preserve that angle in the slerp's goal matrix
|
||
|
|
||
|
// calculate the deviation between the skater's velocity and facing
|
||
|
float angle = Mth::GetAngleAbout(GetMatrix()[Z], cache_vel, GetMatrix()[Y]);
|
||
|
|
||
|
// be a bit forgiving for hip transfers, as you often have to hit left/right to trigger them, which causes rotation
|
||
|
if (Mth::Abs(angle) < Mth::DegToRad(30.0f))
|
||
|
{
|
||
|
angle = 0.0f;
|
||
|
}
|
||
|
|
||
|
// rotate goal facing to reflect the deviation in the initial facing
|
||
|
transfer_slerp_goal.RotateYLocal(-angle);
|
||
|
|
||
|
// setup the slerp state
|
||
|
m_transfer_slerper.setMatrices(&transfer_slerp_start, &transfer_slerp_goal);
|
||
|
m_transfer_slerp_timer = 0.0f;
|
||
|
m_transfer_slerp_duration = Mth::ClampMin(time, 0.9f); // clamp the time to stop super fast rotations
|
||
|
m_transfer_slerp_previous_matrix = transfer_slerp_start;
|
||
|
|
||
|
// insure that the slerp takes us over the top, and doesn't invert us
|
||
|
Mth::Matrix slerp_test;
|
||
|
m_transfer_slerper.getMatrix(&slerp_test, 0.5f);
|
||
|
if (slerp_test[Y][Y] < 0.0f)
|
||
|
{
|
||
|
m_transfer_slerper.invertDirection();
|
||
|
}
|
||
|
|
||
|
// remember the height we are aiming for, so when we come down through this height
|
||
|
// then we remove the non vert velocity (or make it very small....)
|
||
|
m_transfer_target_height = target[Y];
|
||
|
|
||
|
// set velocity over the wall fast enough to land on the target point
|
||
|
mp_state_component->m_spine_vel = (target - GetPos()) / time; // velocity from start to target
|
||
|
mp_state_component->m_spine_vel[Y] = 0.0f; // but ignore Y, as gravity handles that...
|
||
|
|
||
|
// tell the code we are doing spine physics, so we lean quicker
|
||
|
if (!hip_transfer)
|
||
|
{
|
||
|
GetObject()->SpawnAndRunScript(CRCD(0xa5179e9e, "SkaterAwardTransfer")); // award a trick (might want to do it as an exception later)
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetObject()->SpawnAndRunScript(CRCD(0x283bb5d6, "SkaterAwardHipTransfer")); // award a trick (might want to do it as an exception later)
|
||
|
}
|
||
|
|
||
|
// no late jumps during a transfer
|
||
|
GetObject()->RemoveEventHandler(CRCD(0x8ffefb28, "Ollied"));
|
||
|
|
||
|
SetFlagTrue(SPINE_PHYSICS); // flag in spin physics, to do the lean forward, and also allow downcoming lip tricks
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(TRACKING_VERT); // we are still vert, but not tracking the vert
|
||
|
SetFlagFalse(CAN_BREAK_VERT); // and as we "broke" vert, we don't want to do it again
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::look_for_transfer_target ( const Mth::Vector& search_dir, const Mth::Vector& start_normal, bool& hip_transfer, Mth::Vector& target, Mth::Vector& target_normal )
|
||
|
{
|
||
|
// take a bunch of steps forward until we find one
|
||
|
// This is not very good, as we have to do 80 collision checks....
|
||
|
// we really need to optimize our collision detection to be able to select a set of "nearby" object
|
||
|
// or to select a set that intersects a sphere, or a plane
|
||
|
// (here, we could just get the set that intersects the plane)
|
||
|
// this could be statically cached by the colision code, and have one set
|
||
|
// or perhaps more flexibly, each "feeler" could have a set of objects
|
||
|
// that it deals with (defaulting to the set of all objects)
|
||
|
|
||
|
CFeeler feeler;
|
||
|
|
||
|
// setup collision cache
|
||
|
Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
|
||
|
Mth::CBBox bbox;
|
||
|
Mth::Vector p;
|
||
|
p = GetPos() + search_dir * 10.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p[Y] -= 4000.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p = GetPos() + search_dir * 500.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p[Y] -= 4000.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p_coll_cache->Update(bbox);
|
||
|
feeler.SetCache(p_coll_cache);
|
||
|
|
||
|
for (float step = 10.0f; step < 500.0f; step += 6.0f)
|
||
|
{
|
||
|
// First find a VERT point a bit in front of us
|
||
|
// can be some distance below us
|
||
|
// allowing us to transfer from high to low pools
|
||
|
// (and low to high, proving you can jump up from the low point to the high point first)
|
||
|
feeler.m_start = GetPos() + search_dir * step; // start at current height
|
||
|
feeler.m_end = feeler.m_start;
|
||
|
feeler.m_end[Y] -= 4000.0f; // long way below
|
||
|
|
||
|
if (feeler.GetCollision() && (feeler.GetFlags() & mFD_VERT) && is_vert_for_transfers(feeler.GetNormal()))
|
||
|
{
|
||
|
Mth::Vector horizontal_normal = feeler.GetNormal();
|
||
|
horizontal_normal[Y] = 0.0f;
|
||
|
horizontal_normal.Normalize();
|
||
|
float dot = Mth::DotProduct(start_normal, horizontal_normal);
|
||
|
if (dot <= 0.95f)
|
||
|
{
|
||
|
target = feeler.GetPoint();
|
||
|
target_normal = feeler.GetNormal();
|
||
|
|
||
|
hip_transfer = dot > -0.866f;
|
||
|
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(255, 100, 100, 0);
|
||
|
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(100, 255, 100, 0);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(100, 100, 255, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::maybe_acid_drop ( bool skated_off_edge, const Mth::Vector &pos, const Mth::Vector& old_pos, Mth::Vector& vel, SAcidDropData& acid_drop_data )
|
||
|
{
|
||
|
// horizontal direction in which a drop would occur
|
||
|
Mth::Vector drop_direction;
|
||
|
if (mp_physics_control_component->IsSkating())
|
||
|
{
|
||
|
drop_direction = vel;
|
||
|
drop_direction[Y] = 0.0f;
|
||
|
float length = drop_direction.Length();
|
||
|
if (length < 0.01f) return false;
|
||
|
drop_direction *= 1.0f / length;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
drop_direction = mp_walk_component->m_facing;
|
||
|
}
|
||
|
|
||
|
bool target_found = false;
|
||
|
Mth::Vector target;
|
||
|
|
||
|
// in order not to miss vert polys with a thin horizontal projection, we check for them starting at this frame's initial position
|
||
|
Mth::Vector search_pos = old_pos;
|
||
|
search_pos[Y] = Mth::Max(pos[Y], old_pos[Y]);
|
||
|
|
||
|
float scan_distance = 500.0f;
|
||
|
float scan_height = 0.0f;
|
||
|
if (mp_physics_control_component->IsWalking())
|
||
|
{
|
||
|
if (mp_walk_component->m_state == CWalkComponent::WALKING_GROUND)
|
||
|
{
|
||
|
// and use a reduced scan distance
|
||
|
scan_distance = Script::GetFloat(CRCD(0xe50a9d56, "Physics_Acid_Drop_Walking_On_Ground_Search_Distance"));
|
||
|
|
||
|
// and look for vert polys above us
|
||
|
scan_height = 200.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// look slightly behind us for acid drops (we may be facing down a vert we're standing on)
|
||
|
search_pos -= 12.0f * drop_direction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CFeeler feeler;
|
||
|
|
||
|
// setup collision cache
|
||
|
Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
|
||
|
Mth::CBBox bbox;
|
||
|
Mth::Vector p;
|
||
|
p = search_pos;
|
||
|
p[Y] += scan_height;
|
||
|
bbox.AddPoint(p);
|
||
|
p[Y] -= 4200.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p = search_pos + drop_direction * scan_distance;
|
||
|
p[Y] += scan_height;
|
||
|
bbox.AddPoint(p);
|
||
|
p[Y] -= 4200.0f;
|
||
|
bbox.AddPoint(p);
|
||
|
p_coll_cache->Update(bbox);
|
||
|
feeler.SetCache(p_coll_cache);
|
||
|
|
||
|
Mth::Vector target_normal;
|
||
|
Mth::Vector horizontal_target_normal;
|
||
|
float distance;
|
||
|
for (distance = 0.01f; distance < scan_distance; distance += 3.0f)
|
||
|
{
|
||
|
// look for a vert poly below us
|
||
|
feeler.m_start = search_pos + distance * drop_direction;
|
||
|
feeler.m_end = feeler.m_start;
|
||
|
feeler.m_start[Y] += scan_height;
|
||
|
feeler.m_end[Y] -= 4000.0f;
|
||
|
if (feeler.GetCollision() && (feeler.GetFlags() & mFD_VERT) && is_vert_for_transfers(feeler.GetNormal()))
|
||
|
{
|
||
|
// the horizontal projection of the vert's normal just correspond somewhat to our direction
|
||
|
target_normal = horizontal_target_normal = feeler.GetNormal();
|
||
|
horizontal_target_normal[Y] = 0.0f;
|
||
|
horizontal_target_normal.Normalize();
|
||
|
|
||
|
if (mp_physics_control_component->IsWalking() && mp_walk_component->m_state == CWalkComponent::WALKING_AIR)
|
||
|
{
|
||
|
// special acceptance rules for walking in-air acid drops
|
||
|
target_found = Mth::DotProduct(drop_direction, horizontal_target_normal) <= -0.25f
|
||
|
|| Mth::DotProduct(drop_direction, horizontal_target_normal) >= 0.05f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
target_found = Mth::DotProduct(drop_direction, horizontal_target_normal) >= 0.05f;
|
||
|
}
|
||
|
|
||
|
if (target_found)
|
||
|
{
|
||
|
target = feeler.GetPoint();
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(255, 100, 100, 0);
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(100, 255, 100, 0);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// feeler.m_end[Y] += 3960.0f;
|
||
|
// feeler.DebugLine(100, 100, 255, 0);
|
||
|
}
|
||
|
|
||
|
// use a larger incrememt at larger distances, as we have several frames yet to find these polys
|
||
|
if (distance > 100.0f)
|
||
|
{
|
||
|
distance += 24.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!target_found)
|
||
|
{
|
||
|
// no valid acid drop target found
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
float original_target_height = target[Y];
|
||
|
|
||
|
// because our search began behind us, the horizontal offset to the target may not be forward
|
||
|
Mth::Vector horizontal_offset = target - pos;
|
||
|
horizontal_offset[Y] = 0.0f;
|
||
|
distance = horizontal_offset.Length();
|
||
|
if (Mth::DotProduct(horizontal_offset, drop_direction) < 0.0f)
|
||
|
{
|
||
|
distance = -distance;
|
||
|
}
|
||
|
drop_direction = horizontal_offset / distance;
|
||
|
|
||
|
// stash a copy of velocity so we can pretend it has an adjusted value
|
||
|
Mth::Vector hold_vel = vel;
|
||
|
|
||
|
if (mp_physics_control_component->IsWalking())
|
||
|
{
|
||
|
// because when walking they are necessarily in the same direction, project our horizontal velocity in the drop direction
|
||
|
vel.ProjectToNormal(drop_direction);
|
||
|
vel[Y] = hold_vel[Y];
|
||
|
}
|
||
|
|
||
|
// calculate our effective horizontal velocity
|
||
|
float initial_horiz_speed = sqrtf(vel[X] * vel[X] + vel[Z] * vel[Z]);
|
||
|
|
||
|
if (mp_physics_control_component->IsWalking())
|
||
|
{
|
||
|
// boost our effective horizontal speed up to maximum run speed
|
||
|
float horizontal_boost = mp_walk_component->get_run_speed();
|
||
|
if (initial_horiz_speed < horizontal_boost)
|
||
|
{
|
||
|
vel[X] = horizontal_boost * GetWalkComponentFromObject(GetObject())->m_facing[X];
|
||
|
vel[Z] = horizontal_boost * GetWalkComponentFromObject(GetObject())->m_facing[Z];
|
||
|
initial_horiz_speed = horizontal_boost;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// give a slight upward pop
|
||
|
if (skated_off_edge)
|
||
|
{
|
||
|
vel[Y] = Mth::Max(vel[Y], GetPhysicsFloat(CRCD(0x95a79c32, "Physics_Acid_Drop_Pop_Speed")));
|
||
|
}
|
||
|
|
||
|
// but limit upward speed to something reasonable
|
||
|
if (!mp_physics_control_component->IsWalking() || mp_walk_component->m_state != CWalkComponent::WALKING_GROUND)
|
||
|
{
|
||
|
vel[Y] = Mth::Min(vel[Y], 2.0f * GetPhysicsFloat(CRCD(0x95a79c32, "Physics_Acid_Drop_Pop_Speed")));
|
||
|
}
|
||
|
|
||
|
// grab the acceleration we will have during our acid drop
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
float acceleration = get_air_gravity();
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
|
||
|
// calculate what height we would have if we used our current horizontal velocity to reach the target position
|
||
|
float final_height;
|
||
|
if (distance > 0.0f && initial_horiz_speed > 0.0001f)
|
||
|
{
|
||
|
float time_to_target = distance / initial_horiz_speed;
|
||
|
final_height = pos[Y] + vel[Y] * time_to_target + 0.5f * acceleration * time_to_target * time_to_target;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// for backwards acid drops, just act as through we are directly over the target point
|
||
|
final_height = pos[Y];
|
||
|
}
|
||
|
|
||
|
// if we need to jump up to the target
|
||
|
if (mp_physics_control_component->IsWalking() && vel[Y] > 0.0f && pos[Y] < target[Y])
|
||
|
{
|
||
|
// check to see if we'll ever reach that height with our effective upward velocity
|
||
|
float max_height = pos[Y] + vel[Y] * vel[Y] / (-2.0f * acceleration);
|
||
|
float time_to_target = Mth::Abs(distance) / initial_horiz_speed;
|
||
|
float time_to_max_height = vel[Y] / -acceleration;
|
||
|
if (time_to_target < time_to_max_height)
|
||
|
{
|
||
|
// effectively, this means that we're willing to reduce our horizontal boost in order allow more time to reach the required height
|
||
|
final_height = max_height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we can't reach the target with our current velocity, ditch the acid drop
|
||
|
if (final_height < target[Y])
|
||
|
{
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
vel = hold_vel;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// calculate the air time before the acid drop would hit its true target; prevent acid drops from occuring moments before landing
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
float time_to_reach_target_height = calculate_time_to_reach_height(original_target_height, pos[Y], vel[Y]);
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
if (time_to_reach_target_height < Script::GetFloat(CRCD(0x32c20f7e, "Physics_Acid_Drop_Min_Air_Time")))
|
||
|
{
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
vel = hold_vel;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// ensure that we have a clear shot to the target
|
||
|
|
||
|
bool clear_path = false;
|
||
|
|
||
|
// keep shifting our target point up until we can get a clear shot to it, or we get to an unreachable height
|
||
|
while (target[Y] < final_height)
|
||
|
{
|
||
|
feeler.m_start = pos;
|
||
|
|
||
|
// check a path constructed from two concatenated lines, with the midpoint halfway along the acid drop trajectory; this is an attempt
|
||
|
// to allow most acid drop which might be disallowed by a ledge which would block a straight line
|
||
|
|
||
|
// calculate the time span required to fall to the target height
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
float half_time_to_reach_target_height = 0.5f * calculate_time_to_reach_height(target[Y], pos[Y], vel[Y]);
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
|
||
|
// calculate the spine velocity which would be used for this target
|
||
|
float required_speed = 0.5f * distance / half_time_to_reach_target_height;
|
||
|
|
||
|
// calculate the height we will be at halfway through the acid drop
|
||
|
float height_halfway = pos[Y] + vel[Y] * half_time_to_reach_target_height
|
||
|
+ 0.5f * acceleration * Mth::Sqr(half_time_to_reach_target_height);
|
||
|
|
||
|
// calculate the point halfway through the acid drop
|
||
|
Mth::Vector halfway_point = pos;
|
||
|
halfway_point[Y] = height_halfway;
|
||
|
halfway_point += required_speed * half_time_to_reach_target_height * drop_direction;
|
||
|
|
||
|
// check for collisions alone the two-line path
|
||
|
feeler.m_end = halfway_point;
|
||
|
if (!feeler.GetCollision())
|
||
|
{
|
||
|
// feeler.DebugLine(255, 255, 0);
|
||
|
feeler.m_start = feeler.m_end;
|
||
|
feeler.m_end = target;
|
||
|
feeler.m_end[Y] += 1.0f;
|
||
|
if (!feeler.GetCollision())
|
||
|
{
|
||
|
// feeler.DebugLine(255, 255, 0);
|
||
|
clear_path = true;
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// feeler.DebugLine(0, 0, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// feeler.DebugLine(0, 0, 0, 0);
|
||
|
|
||
|
// try a higher target point
|
||
|
target[Y] += 24.0f;
|
||
|
}
|
||
|
|
||
|
// no clear path was found along the acid drop
|
||
|
if (!clear_path)
|
||
|
{
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
vel = hold_vel;
|
||
|
return false;
|
||
|
}
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
|
||
|
|
||
|
acid_drop_data.target_pos = target;
|
||
|
acid_drop_data.target_normal = target_normal;
|
||
|
acid_drop_data.true_target_height = original_target_height;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::enter_acid_drop ( const SAcidDropData& acid_drop_data )
|
||
|
{
|
||
|
const Mth::Vector& target_pos = acid_drop_data.target_pos;
|
||
|
const Mth::Vector& target_normal = acid_drop_data.target_normal;
|
||
|
const float& true_target_height = acid_drop_data.true_target_height;
|
||
|
|
||
|
// setup the skater state for the acid drop
|
||
|
|
||
|
Mth::Vector horizontal_offset = target_pos - GetPos();
|
||
|
horizontal_offset[Y] = 0.0f;
|
||
|
float distance = horizontal_offset.Length();
|
||
|
if (Mth::DotProduct(horizontal_offset, GetVel()) < 0.0f)
|
||
|
{
|
||
|
distance = -distance;
|
||
|
}
|
||
|
Mth::Vector drop_direction = horizontal_offset / distance;
|
||
|
|
||
|
// calculate the spine speed required to reach the target given our current vertical velocity
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
float time_to_reach_target_height = calculate_time_to_reach_height(target_pos[Y], GetPos()[Y], GetVel()[Y]);
|
||
|
float required_speed = distance / time_to_reach_target_height;
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
mp_state_component->m_spine_vel.Set(required_speed * drop_direction[X], 0.0f, required_speed * drop_direction[Z]);
|
||
|
acid_hold = mp_state_component->m_spine_vel;
|
||
|
|
||
|
// once we reach this height, the skater's horizontal velocity will be zeroed out
|
||
|
m_transfer_target_height = target_pos[Y];
|
||
|
|
||
|
// Gfx::AddDebugStar(target, 24.0f, RED, 0);
|
||
|
|
||
|
// enter the acid drop state
|
||
|
SetFlagTrue(SPINE_PHYSICS);
|
||
|
SetFlagTrue(VERT_AIR);
|
||
|
SetFlagTrue(IN_ACID_DROP);
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
|
||
|
// zero our horizontal velocity
|
||
|
GetVel()[X] = 0.0f;
|
||
|
GetVel()[Z] = 0.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// setup the acid drop's matrix slerp
|
||
|
|
||
|
Mth::Matrix acid_drop_slerp_start = GetMatrix();
|
||
|
|
||
|
// calculate the facing we want when we land; retain our horizontal direction and choose a vertical component which puts us parallel so the target
|
||
|
// poly's plane
|
||
|
Mth::Vector land_facing = drop_direction;
|
||
|
land_facing[Y] = -(land_facing[X] * target_normal[X] + land_facing[Z] * target_normal[Z]) / target_normal[Y];
|
||
|
land_facing.Normalize();
|
||
|
|
||
|
// calculate goal matrix
|
||
|
Mth::Matrix acid_drop_slerp_goal;
|
||
|
acid_drop_slerp_goal[Z] = land_facing;
|
||
|
acid_drop_slerp_goal[Z].ProjectToPlane(target_normal);
|
||
|
acid_drop_slerp_goal[Z].Normalize();
|
||
|
acid_drop_slerp_goal[Y] = target_normal;
|
||
|
acid_drop_slerp_goal[X] = Mth::CrossProduct(acid_drop_slerp_goal[Y], acid_drop_slerp_goal[Z]);
|
||
|
acid_drop_slerp_goal[W].Set();
|
||
|
|
||
|
// store the goal facing for use in adjusting the velocity at land time
|
||
|
m_transfer_goal_facing = acid_drop_slerp_goal[Z];
|
||
|
|
||
|
// setup a good camera matrix for the acid drop (before applying any deviation preserving adjustments)
|
||
|
m_acid_drop_camera_matrix = acid_drop_slerp_goal;
|
||
|
if (m_acid_drop_camera_matrix[Z][Y] > 0.0f)
|
||
|
{
|
||
|
m_acid_drop_camera_matrix[Z] *= -1.0f;
|
||
|
m_acid_drop_camera_matrix[X] *= -1.0f;
|
||
|
}
|
||
|
|
||
|
// if the skater is entering the acid drop with an odd facing due to rotation, we want to preserve that angle in the slerp's goal matrix
|
||
|
|
||
|
// calculate the deviation between the skater's velocity and facing
|
||
|
Mth::Vector horizontal_facing = GetMatrix()[Z];
|
||
|
horizontal_facing[Y] = 0.0f;
|
||
|
float angle = Mth::GetAngleAbout(horizontal_facing, drop_direction, GetMatrix()[Y]);
|
||
|
|
||
|
// rotate goal facing to reflect the deviation in the initial facing
|
||
|
acid_drop_slerp_goal.RotateYLocal(-angle);
|
||
|
|
||
|
// setup the slerp state
|
||
|
m_transfer_slerper.setMatrices(&acid_drop_slerp_start, &acid_drop_slerp_goal);
|
||
|
m_transfer_slerp_timer = 0.0f;
|
||
|
m_transfer_slerp_duration = time_to_reach_target_height;
|
||
|
m_transfer_slerp_previous_matrix = acid_drop_slerp_start;
|
||
|
|
||
|
// trigger the appropriate script
|
||
|
Script::CStruct* p_params = new Script::CStruct;
|
||
|
p_params->AddFloat(CRCD(0xbb00fe40, "DropHeight"), GetPos()[Y] - true_target_height);
|
||
|
GetObject()->SpawnAndRunScript(CRCD(0xc7ed5fef, "SkaterAcidDropTriggered"), -1, false, false, p_params);
|
||
|
|
||
|
// no late jumps during an acid drop
|
||
|
GetObject()->RemoveEventHandler(CRCD(0x8ffefb28, "Ollied"));
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_post_transfer_limit_overrides ( )
|
||
|
{
|
||
|
// After a transfer, if we are above our standard speed limits, we ignore those limits for a short duration. Over the duration the speed limits
|
||
|
// lerp from our starting speed down to the standard speed limits. Only non-air time is counted towards the duration.
|
||
|
|
||
|
// detect leaving an acid drop
|
||
|
if (m_began_frame_in_transfer && !GetFlag(SPINE_PHYSICS) && !GetFlag(IS_BAILING))
|
||
|
{
|
||
|
// only override limits if we start above the limits
|
||
|
float standard_max = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
|
||
|
float speed = GetVel().Length();
|
||
|
if (speed < 1.25f * standard_max) return;
|
||
|
|
||
|
// setup state to ignore speed limits
|
||
|
m_transfer_overrides_factor = Mth::Min(speed / standard_max, GetPhysicsFloat(CRCD(0xc6b38be0, "Physics_Transfer_Speed_Limit_Override_Max")));
|
||
|
|
||
|
// large values
|
||
|
m_override_max = 1e20f;
|
||
|
m_override_max_max = 1e20f;
|
||
|
}
|
||
|
else if (m_transfer_overrides_factor == 1.0f) return;
|
||
|
|
||
|
// turn on speed limit overrides
|
||
|
m_override_limits_time = -1.0f;
|
||
|
|
||
|
// count our timer down
|
||
|
m_transfer_overrides_factor -= m_frame_length * GetPhysicsFloat(CRCD(0xf9b006aa, "Physics_Transfer_Speed_Limit_Override_Drop_Rate"));
|
||
|
|
||
|
// end the ignoring of speed limits if the duration is up
|
||
|
if (m_transfer_overrides_factor < 1.0f)
|
||
|
{
|
||
|
m_transfer_overrides_factor = 1.0f;
|
||
|
m_override_limits_time = 0.0f;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// grab the standard speed limit
|
||
|
float standard_max = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
|
||
|
|
||
|
// calculate the appropriate speed limit based on the time since the acid drop and the speed at the end of the acid drop
|
||
|
float time_based_appropriate_max = m_transfer_overrides_factor * standard_max;
|
||
|
|
||
|
// calculate a speed limit based on the current speed; thus, if we break during the ignoring of speed limits, our speed limits will turn back on
|
||
|
float speed_based_appropriate_max = 1.1f * GetVel().Length();
|
||
|
|
||
|
// take the lowest speed limit; never increase the speed limit
|
||
|
float appropriate_max;
|
||
|
if (GetState() != AIR)
|
||
|
{
|
||
|
appropriate_max = Mth::Min3(time_based_appropriate_max, speed_based_appropriate_max, m_override_max);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// in air, don't drop the limits when your current speed drops; otherwise you lose your overrides as the top of vert air
|
||
|
appropriate_max = Mth::Min(time_based_appropriate_max, m_override_max);
|
||
|
}
|
||
|
|
||
|
// end the ignoring of speed limits if the duration is up
|
||
|
if (appropriate_max < standard_max)
|
||
|
{
|
||
|
m_transfer_overrides_factor = 1.0f;
|
||
|
m_override_limits_time = 0.0f;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// set the artificially high speed limit override
|
||
|
m_override_max = appropriate_max;
|
||
|
|
||
|
// the max max speed limit will never fall below the standard max max speed limit
|
||
|
m_override_max_max = m_override_max / standard_max * GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
|
||
|
|
||
|
PERIODIC(10)
|
||
|
{
|
||
|
printf("Post-Transfer Speed Limit Overrides: current / standard = %.2f\n", m_override_max / standard_max);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
float CSkaterCorePhysicsComponent::get_air_gravity ( )
|
||
|
{
|
||
|
// given that we are in the air, figure out what gravity to use
|
||
|
// based on if we are VERT or regular air and the cheat modes
|
||
|
// make sure that if you use this in calculations, then your flags do not change while you expect it to be the same
|
||
|
float gravity;
|
||
|
|
||
|
if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS)) // Note, spine is treated same as vert
|
||
|
{
|
||
|
// gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetSkater()->GetScriptedStat(CRCD(0x441c38a0, "Physics_vert_hang_Stat"));
|
||
|
gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetPhysicsFloat(CRCD(0x441c38a0, "Physics_vert_hang_Stat"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetSkater()->GetScriptedStat(CRCD(0xc31ca696, "Physics_Air_hang_Stat"));
|
||
|
gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetPhysicsFloat(CRCD(0xc31ca696, "Physics_Air_hang_Stat"));
|
||
|
}
|
||
|
|
||
|
if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0x9c8c6df1, "CHEAT_MOON")))
|
||
|
{
|
||
|
// Here, set the flag. It may seem redundant, but the above line is very likely
|
||
|
// to be hacked by gameshark. They probably won't notice this one, which will
|
||
|
// set the flags as if they had actually enabled the cheat -- which enables us
|
||
|
// to detect that it has been turned on more easily.
|
||
|
Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0x9c8c6df1, "CHEAT_MOON")));
|
||
|
gravity *= GetPhysicsFloat(CRCD(0xec128f0, "moon_gravity"));
|
||
|
g_CheatsEnabled = true;
|
||
|
}
|
||
|
|
||
|
return gravity;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
float CSkaterCorePhysicsComponent::calculate_time_to_reach_height ( float target_height, float pos_Y, float vel_Y )
|
||
|
{
|
||
|
// s = ut - 1/2 * g * t^2 (note, -g = a in the more traditional formula)
|
||
|
// solve this using the quadratic equation, gives us the formula below
|
||
|
// Note the sign of s is important.....
|
||
|
float distance = pos_Y - target_height;
|
||
|
float velocity = vel_Y;
|
||
|
float acceleration = -get_air_gravity();
|
||
|
return (velocity + sqrtf(velocity * velocity + 2.0f * acceleration * distance)) / acceleration;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_ground_rotation ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
float rot = 0.0f;
|
||
|
float speed = GetVel().Length();
|
||
|
|
||
|
if (control_pad.m_left.GetPressed())
|
||
|
{
|
||
|
if (!control_pad.m_down.GetPressed())
|
||
|
{
|
||
|
rot = GetPhysicsFloat(CRCD(0x374e056b, "Physics_Ground_Rotation"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rot = GetPhysicsFloat(CRCD(0x7933a8ef, "Physics_Ground_Sharp_Rotation"));
|
||
|
if (speed < 10.0f)
|
||
|
{
|
||
|
// If not moving, then we want more control over turning, so ramp up the turing speed over a second
|
||
|
int pressed_time = control_pad.m_left.GetPressedTime();
|
||
|
if (pressed_time < STOPPED_TURN_RAMP_TIME)
|
||
|
{
|
||
|
rot = rot * pressed_time / STOPPED_TURN_RAMP_TIME;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
if (!control_pad.m_down.GetPressed())
|
||
|
{
|
||
|
rot = -GetPhysicsFloat(CRCD(0x374e056b, "Physics_Ground_Rotation"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rot = -GetPhysicsFloat(CRCD(0x7933a8ef, "Physics_Ground_Sharp_Rotation"));
|
||
|
if (speed < 10.0f)
|
||
|
{
|
||
|
// If not moving, then we want more control over turning, so ramp up the turing speed over a second
|
||
|
int pressed_time = control_pad.m_right.GetPressedTime();
|
||
|
if (pressed_time < STOPPED_TURN_RAMP_TIME)
|
||
|
{
|
||
|
rot = rot * pressed_time / STOPPED_TURN_RAMP_TIME;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
bool do_cess = false;
|
||
|
if (CESS_SLIDE_BUTTONS)
|
||
|
{
|
||
|
float cess_turn_min_speed = GetPhysicsFloat(CRCD(0xae84e34a, "cess_turn_min_speed"));
|
||
|
if (speed > cess_turn_min_speed)
|
||
|
{
|
||
|
do_cess = true;
|
||
|
|
||
|
float cess_turn_cap_speed = GetPhysicsFloat(CRCD(0x8242c4fe, "cess_turn_cap_speed"));
|
||
|
|
||
|
float scale = speed;
|
||
|
if (scale > cess_turn_cap_speed)
|
||
|
{
|
||
|
scale = cess_turn_cap_speed;
|
||
|
}
|
||
|
scale -= cess_turn_min_speed;
|
||
|
scale /= cess_turn_cap_speed - cess_turn_min_speed;
|
||
|
|
||
|
rot = rot * scale * GetPhysicsFloat(CRCD(0x22834151, "cess_turn_multiplier"));
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if (rot == 0.0f) return;
|
||
|
|
||
|
rot *= m_frame_length;
|
||
|
|
||
|
mYAngleIncreased = rot > 0.0f;
|
||
|
|
||
|
// K: Avoid anything that might change the velocity direction if this flag is set.
|
||
|
if (!m_lock_velocity_direction /* && !do_cess*/)
|
||
|
{
|
||
|
GetVel().RotateY(rot); // Note: Need to rotate this about UP vector
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
GetMatrix().RotateYLocal(rot);
|
||
|
m_lerping_display_matrix.RotateYLocal(rot);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::remove_sideways_velocity ( Mth::Vector& vel )
|
||
|
{
|
||
|
// Remove any non-forward component of the velocity
|
||
|
|
||
|
float speed = vel.Length(); // get size of velocity
|
||
|
if (speed > 0.00001f)
|
||
|
{
|
||
|
// if (!USE_BIKE_PHYSICS)
|
||
|
// {
|
||
|
vel *= 1.0f / speed; // get unit vector in direction of velocity
|
||
|
float direction = Mth::Sgn(Mth::DotProduct(vel, GetMatrix()[Z])); // get fwds or backwards
|
||
|
|
||
|
vel = GetMatrix()[Z]; // get forward direction
|
||
|
vel *= speed * direction; // apply all speed in this direction
|
||
|
DUMP_VELOCITY;
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// Mth::Vector old_vel = vel;
|
||
|
|
||
|
// vel.ProjectToNormal(GetMatrix()[Z]); // leave forward velocity alone
|
||
|
|
||
|
// old_vel -= GetVel(); // find remaining sideways velocity
|
||
|
|
||
|
// old_vel *= 1.0f - GetPhysicsFloat(CRCD(0x53385759, "cess_Friction"));
|
||
|
// vel += old_vel;
|
||
|
// DUMP_VELOCITY;
|
||
|
// }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::check_side_collisions ( )
|
||
|
{
|
||
|
// check for collisins left and right of the skater; if we get collisions on both sides then restore him to the original position
|
||
|
|
||
|
Mth::Vector debounce = GetPos();
|
||
|
|
||
|
float side_col = GetPhysicsFloat(CRCD(0x406b425f, "Skater_side_collide_length"));
|
||
|
|
||
|
if (check_side(-1.0f, side_col))
|
||
|
{
|
||
|
if (check_side(1.0f, side_col))
|
||
|
{
|
||
|
GetPos() = debounce; // two collisions, back to safety
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (check_side(1.0f, side_col))
|
||
|
{
|
||
|
if (check_side(-1.0f, side_col))
|
||
|
{
|
||
|
GetPos() = debounce; // two collisions, back to safety
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::check_side ( float side, float side_col )
|
||
|
{
|
||
|
#ifdef STICKY_WALLRIDES
|
||
|
// Prevous checks might have put us into a wall ride, so just ignore future checks
|
||
|
if (GetState() == WALL)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// - - - side collision detection - - - - - - - - - - - - - - - - -
|
||
|
|
||
|
m_col_start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2, "Skater_side_collide_height"));
|
||
|
|
||
|
float col_len = side_col;
|
||
|
#ifdef STICKY_WALLRIDES
|
||
|
if (GetState() == AIR)
|
||
|
{
|
||
|
col_len += GetPhysicsFloat(CRCD(0x1c58c8b4, "Skater_air_extra_side_col"));
|
||
|
}
|
||
|
#endif
|
||
|
m_col_end = m_col_start + side * GetMatrix()[X] * col_len;
|
||
|
|
||
|
if (!get_member_feeler_collision()) return false;
|
||
|
|
||
|
Mth::Vector WallFloorNormal = m_feeler.GetNormal();
|
||
|
|
||
|
#ifdef STICKY_WALLRIDES
|
||
|
if (GetState() == AIR)
|
||
|
{
|
||
|
if (check_for_wallride())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// push a bit away from the wall if in the air
|
||
|
Mth::Vector to_wall = m_feeler.GetPoint();
|
||
|
to_wall -= m_col_start; // vector towards the wall
|
||
|
float push_dist = to_wall.Length(); // distance to wall
|
||
|
push_dist -= col_len - side_col; // adjust by the extra push we gave
|
||
|
if (push_dist > 0.0f) // if closer to wall than side_col
|
||
|
{
|
||
|
to_wall.Normalize(push_dist); // then get direction to wall, scaled by dist we want to move
|
||
|
GetPos() -= to_wall / 10.0f; // move 1/10th of the way, for nice lerp
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
float turn_angle;
|
||
|
// Rotate away from wall only if not rotating myself
|
||
|
// otherwise velocity can be continually rotated one way while
|
||
|
// the orientation is rated the other way via the d-pad
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
// don't rotate at all if in the air, as it changes our direction, usually not what we want
|
||
|
if (GetState() != AIR
|
||
|
&& !control_pad.m_R1.GetPressed()
|
||
|
&& !control_pad.m_L1.GetPressed()
|
||
|
&& !control_pad.m_left.GetPressed()
|
||
|
&& !control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
rotate_away_from_wall(WallFloorNormal, turn_angle, 0.2f); // and rotate away from the wall
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
float angle = Mth::DotProduct(WallFloorNormal, GetMatrix()[Y]);
|
||
|
// Consider 90+-30 degrees as wall (_0_5)
|
||
|
// Consider 90+-15 degrees as wall (_0_25)
|
||
|
if (angle < 0.25f && angle > -0.25f)
|
||
|
{
|
||
|
// Undo movement
|
||
|
GetPos() = m_safe_pos;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
float turn_angle;
|
||
|
rotate_away_from_wall(WallFloorNormal, turn_angle, 0.2f);
|
||
|
|
||
|
// Try moving him off the wall:
|
||
|
CFeeler feeler(m_feeler.GetPoint() + WallFloorNormal, m_feeler.GetPoint() + WallFloorNormal * side_col);
|
||
|
if (!feeler.GetCollision())
|
||
|
{
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return false;
|
||
|
|
||
|
// Lower skater back down to the ground
|
||
|
GetPos() = feeler.m_end - GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2,"Skater_side_collide_height"));
|
||
|
DUMP_POSITION;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::check_for_wallpush ( )
|
||
|
{
|
||
|
// requires that m_feeler is a wall
|
||
|
|
||
|
if (!mp_input_component->GetControlPad().m_triangle.GetPressed()) return false;
|
||
|
|
||
|
// last wallpush must not have been too recently
|
||
|
if (Tmr::ElapsedTime(m_last_wallpush_time_stamp) < Script::GetFloat(CRCD(0x17d543, "Physics_Disallow_Rewallpush_Duration"))) return false;
|
||
|
|
||
|
// wall normal must be opposite our forward direction; just under the maximum flail angle
|
||
|
if (Mth::DotProduct(GetMatrix()[Z], m_feeler.GetNormal()) >= -sinf(Mth::DegToRad(Script::GetFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle")) - 1.0f))) return false;
|
||
|
|
||
|
// last wallplant must not have been too recently
|
||
|
if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < Script::GetFloat(CRCD(0x17d543, "Physics_Disallow_Rewallpush_Duration"))) return false;
|
||
|
|
||
|
// throw a wallpush event for the scripts
|
||
|
GetObject()->SelfEvent(CRCD(0x4c03635b, "WallPush"));
|
||
|
|
||
|
// check to see if the wallpush has been canceled during the event
|
||
|
if (GetFlag(CANCEL_WALL_PUSH))
|
||
|
{
|
||
|
SetFlagFalse(CANCEL_WALL_PUSH);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// reverse direction of velocity perpendicular to the wall
|
||
|
Mth::Vector perp_vel = Mth::DotProduct(GetVel(), m_feeler.GetNormal()) * m_feeler.GetNormal();
|
||
|
GetVel() -= 2.0f * perp_vel;
|
||
|
|
||
|
// damp horizontal velocity
|
||
|
float speed = GetVel().Length();
|
||
|
if (speed > 0.001f)
|
||
|
{
|
||
|
GetVel() *= Mth::Max(
|
||
|
Script::GetFloat(CRCD(0xb78542c2, "Physics_Wallpush_Min_Exit_Speed")),
|
||
|
speed - Script::GetFloat(CRCD(0x1112fb1c, "Physics_Wallpush_Speed_Loss"))
|
||
|
) / speed;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel() = -Script::GetFloat(CRCD(0xb78542c2, "Physics_Wallpush_Min_Exit_Speed")) * GetMatrix()[Z];
|
||
|
}
|
||
|
|
||
|
// project the resulting velocity into the ground's plane
|
||
|
GetVel().RotateToPlane(m_current_normal);
|
||
|
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// set orientation along new velocity
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[Y] = m_current_normal;
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// time stamp the wallplant
|
||
|
m_last_wallpush_time_stamp = Tmr::GetTime();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::check_for_wallplant ( )
|
||
|
{
|
||
|
// requires that m_feeler is a wall
|
||
|
|
||
|
// we must be somewhat vertical
|
||
|
if (GetMatrix()[Y][Y] < 0.1f) return false;
|
||
|
|
||
|
// last wallplant must not have been too recently
|
||
|
if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < Script::GetFloat(CRCD(0x82135dd7, "Physics_Disallow_Rewallplant_Duration"))) return false;
|
||
|
|
||
|
// not when you're too near the ground
|
||
|
if (mp_state_component->m_height < Script::GetFloat(CRCD(0xd5349cc6, "Physics_Min_Wallplant_Height"))) return false;
|
||
|
|
||
|
// wall must be substantially vertical
|
||
|
if (!(m_feeler.GetFlags() & mFD_VERT) && Mth::Abs(m_feeler.GetNormal()[Y]) > 0.1f) return false;
|
||
|
|
||
|
float speed = GetVel().Length();
|
||
|
if (speed < 0.01f) return false;
|
||
|
Mth::Vector forward = GetVel() / speed;
|
||
|
|
||
|
// horizontal wall normal must be opposite our horizontal velocity
|
||
|
Mth::Vector horizontal_forward = forward;
|
||
|
horizontal_forward[Y] = 0.0f;
|
||
|
horizontal_forward.Normalize();
|
||
|
Mth::Vector horizontal_normal = m_feeler.GetNormal();
|
||
|
horizontal_normal[Y] = 0.0f;
|
||
|
horizontal_normal.Normalize();
|
||
|
if (Mth::DotProduct(horizontal_forward, horizontal_normal) > -sinf(Mth::DegToRad(Script::GetFloat(CRCD(0x8f79cc1c, "Physics_Wallplant_Min_Approach_Angle"))))) return false;
|
||
|
|
||
|
// here we attempt to stop wallplant when in is more likely that the player is going for a grind
|
||
|
if (GetVel()[Y] > 0.0f && mp_input_component->GetControlPad().m_triangle.GetPressed())
|
||
|
{
|
||
|
Mth::Vector wall_point = m_feeler.GetPoint();
|
||
|
Mth::Vector wall_normal = m_feeler.GetNormal();
|
||
|
|
||
|
Mth::Vector wall_up_vel(0.0f, GetVel()[Y] * 0.15f, 0.0f); // check 0.15 seconds ahead
|
||
|
wall_up_vel.RotateToPlane(wall_normal);
|
||
|
|
||
|
// check at what height will be in two frames
|
||
|
wall_point += wall_up_vel;
|
||
|
|
||
|
CFeeler feeler(wall_point + wall_normal * 6.0f, wall_point - wall_normal * 6.0f);
|
||
|
if (!feeler.GetCollision())
|
||
|
{
|
||
|
#ifdef __USER_DAN__
|
||
|
if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
|
||
|
{
|
||
|
feeler.DebugLine(255, 255, 0, 0);
|
||
|
}
|
||
|
#endif
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef __USER_DAN__
|
||
|
if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
|
||
|
{
|
||
|
feeler.DebugLine(255, 0, 255, 0);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for wallplant trick
|
||
|
// K: Modified this to take an array of triggers, so that Kurt could check out using
|
||
|
// Down,X DownLeft,X or DownRight,X as a trigger.
|
||
|
bool triggered=false;
|
||
|
Script::CArray *p_trick_query_array=Script::GetArray(CRCD(0x5d1f84a7, "Wallplant_Trick"));
|
||
|
for (uint32 i=0; i<p_trick_query_array->GetSize(); ++i)
|
||
|
{
|
||
|
Script::CStruct *p_trick_query_struct=p_trick_query_array->GetStructure(i);
|
||
|
if (mp_trick_component->QueryEvents(p_trick_query_struct))
|
||
|
{
|
||
|
triggered=true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!triggered)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// trip wallplant triggers
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return true;
|
||
|
|
||
|
// zero vertical velocity
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
|
||
|
// reverse velocity in the direction of the wall's normal
|
||
|
Mth::Vector perp_vel = Mth::DotProduct(GetVel(), horizontal_normal) * horizontal_normal;
|
||
|
GetVel() -= 2.0f * perp_vel;
|
||
|
|
||
|
// damp horizontal velocity
|
||
|
float horizontal_speed = sqrtf(GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z]);
|
||
|
if (horizontal_speed > 0.0001f)
|
||
|
{
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
GetVel().Normalize(Mth::Max(
|
||
|
Script::GetFloat(CRCD(0x7cee396c, "Physics_Wallplant_Min_Exit_Speed")),
|
||
|
horizontal_speed - Script::GetFloat(CRCD(0x9b2d4a3, "Physics_Wallplant_Speed_Loss"))
|
||
|
));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel() = -Script::GetFloat(CRCD(0x7cee396c, "Physics_Wallplant_Min_Exit_Speed")) * horizontal_forward;
|
||
|
}
|
||
|
|
||
|
// replace vertical velocity with a wallplant boost
|
||
|
GetVel()[Y] = Script::GetFloat(CRCD(0x74957fa, "Physics_Wallplant_Vertical_Exit_Speed"));
|
||
|
|
||
|
if (m_feeler.IsMovableCollision())
|
||
|
{
|
||
|
// if the wall is moving, we are now in contact with it
|
||
|
if (!mp_movable_contact_component->HaveContact() || m_feeler.GetMovingObject() != mp_movable_contact_component->GetContact()->GetObject())
|
||
|
{
|
||
|
mp_movable_contact_component->LoseAnyContact();
|
||
|
mp_movable_contact_component->ObtainContact(m_feeler.GetMovingObject());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// move to just outside the wall, insuring that there is no additional collision along the line to that point
|
||
|
m_feeler.m_start = m_feeler.GetPoint();
|
||
|
m_feeler.m_end = m_feeler.GetPoint() + Script::GetFloat(CRCD(0x24be8f0, "Physics_Wallplant_Distance_From_Wall")) * m_feeler.GetNormal();
|
||
|
if (m_feeler.GetCollision())
|
||
|
{
|
||
|
GetPos() = m_feeler.GetPoint() + 0.1f * m_feeler.GetNormal();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetPos() = m_feeler.m_end;
|
||
|
}
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// set orientation along new velocity
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
GetMatrix()[Z][Y] = 0.0f;
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[Y].Set(0.0f, 1.0f, 0.0f);
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// time stamp the wallplant
|
||
|
m_last_wallplant_time_stamp = Tmr::GetTime();
|
||
|
|
||
|
// throw a wallplant event for the scripts
|
||
|
GetObject()->SelfEvent(CRCD(0xcf74f6b7, "WallPlant"));
|
||
|
|
||
|
// trick off the object
|
||
|
mp_trick_component->TrickOffObject(m_feeler.GetNodeChecksum());
|
||
|
|
||
|
// turn back on orientation control in case we just came out of walking
|
||
|
SetFlagFalse(NO_ORIENTATION_CONTROL);
|
||
|
|
||
|
// acid drops are always allowed after a wallplant
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
// let the camera know we're snapping our position slightly
|
||
|
SetFlagTrue(SNAPPED);
|
||
|
|
||
|
// enter wallplant state
|
||
|
SetState(WALLPLANT);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::check_for_wallride ( )
|
||
|
{
|
||
|
// Requires that m_feeler is a valid wall
|
||
|
|
||
|
if (!m_col_flag_wallable) return false;
|
||
|
|
||
|
// Allow a wall-ride attempt if triangle being pressed & long enough after the last wall-ride.
|
||
|
if ((mp_trick_component->GetButtonState(CRCD(0x20689278, "Triangle")) || mp_trick_component->TriggeredInLastNMilliseconds(
|
||
|
CRCD(0x20689278, "Triangle"),
|
||
|
1000 * GetPhysicsFloat(CRCD(0x5d241b00, "Wall_Ride_Triangle_Window"))
|
||
|
)) && static_cast< int >(Tmr::ElapsedTime(mWallrideTime)) > 1000 * GetPhysicsFloat(CRCD(0xf0c74ec2, "Wall_Ride_Delay")))
|
||
|
{
|
||
|
////////////////////////////////////////////////
|
||
|
// check to see if we are going upwards, and within 0.15 seconds from the top of the wall (based on current up speed)
|
||
|
// and we are going upwards, then don't wallride, as we will probably snap into a rail soon after
|
||
|
//
|
||
|
|
||
|
if (GetVel()[Y] > 0.0f)
|
||
|
{
|
||
|
Mth::Vector wall_point = m_feeler.GetPoint();
|
||
|
Mth::Vector wall_normal = m_feeler.GetNormal();
|
||
|
|
||
|
Mth::Vector wall_up_vel(0.0f, GetVel()[Y] * 0.15f, 0.0f); // check 0.15 seconds ahead
|
||
|
wall_up_vel.RotateToPlane(wall_normal);
|
||
|
|
||
|
// check at what height will be in two frames
|
||
|
wall_point += wall_up_vel;
|
||
|
|
||
|
CFeeler check_feeler(wall_point + wall_normal * 6.0f, wall_point - wall_normal * 6.0f);
|
||
|
if (!check_feeler.GetCollision()) return false;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////
|
||
|
mWallNormal = m_feeler.GetNormal();
|
||
|
|
||
|
Mth::Vector SquashedWallNormal = mWallNormal; // wall normal in the XZ plane
|
||
|
SquashedWallNormal[Y] = 0;
|
||
|
SquashedWallNormal.Normalize();
|
||
|
|
||
|
Mth::Vector SquashedVel = GetVel(); // velocity in the XZ plane
|
||
|
SquashedVel[Y] = 0;
|
||
|
SquashedVel.Normalize();
|
||
|
|
||
|
// Calculate the speed "along" the wall (i.e., in the XZ plane) needed to have enough speed along the wall to make the wallride viable
|
||
|
// (otherwise we just go up and down, and end up at odd angles)
|
||
|
Mth::Vector vel_along_wall = GetVel();
|
||
|
vel_along_wall[Y] = 0;
|
||
|
vel_along_wall.ProjectToPlane(SquashedWallNormal);
|
||
|
float speed_along_wall = vel_along_wall.Length();
|
||
|
|
||
|
if (speed_along_wall < GetPhysicsFloat(CRCD(0xf0636a67, "Wall_Ride_Min_Speed"))) return false;
|
||
|
|
||
|
float dot = fabsf(Mth::DotProduct(SquashedVel, SquashedWallNormal));
|
||
|
|
||
|
// If all angles OK then trigger a wall-ride.
|
||
|
if (mWallNormal[Y] > -sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xae23e850, "Wall_Ride_Upside_Down_Angle")))) &&
|
||
|
dot < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x6ac9f64d, "Wall_Ride_Max_Incident_Angle")))) &&
|
||
|
GetMatrix()[Y][Y] > cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xbfcbb7e8, "Wall_Ride_Max_Tilt")))))
|
||
|
{
|
||
|
if (!mp_trick_component->GraffitiTrickStarted())
|
||
|
{
|
||
|
// clear the graffiti trick buffer if we're moving to a new
|
||
|
// world sector, but haven't started our trick yet...
|
||
|
mp_trick_component->SetGraffitiTrickStarted(false);
|
||
|
}
|
||
|
|
||
|
// landed in a wall ride, check for triggers associated with this object
|
||
|
// note we pass "TRIGGER_LAND_ON", perhaps not semanticaly correct but we use it for landing on rails,
|
||
|
// so the meaning is the same across ground-rail-wallride
|
||
|
set_last_ground_feeler(m_feeler);
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return false;
|
||
|
|
||
|
Mth::Vector point = m_feeler.GetPoint();
|
||
|
|
||
|
// check to see if we are going into a corner
|
||
|
// by checking the vector from out current pos along a line parallel to our movment rotated onto the wall.
|
||
|
Mth::Vector forward_vel = GetPos() - GetOldPos();
|
||
|
forward_vel.RotateToPlane(mWallNormal);
|
||
|
m_col_start = GetPos();
|
||
|
m_col_end = GetPos() + forward_vel * 3.0f;
|
||
|
if (get_member_feeler_collision()) return false;
|
||
|
|
||
|
// Record start time.
|
||
|
mWallrideTime = static_cast< int >(Tmr::GetTime());
|
||
|
|
||
|
// Snap position to wall.
|
||
|
// we don't want to snap too close
|
||
|
// previously we moved him an inch away from the wall after snapping him to the collision point.
|
||
|
// However, this seemed to cause problems in corner so now I just move him back one inch along the collision line
|
||
|
// which is hence guarenteed not to push him through walls.
|
||
|
|
||
|
GetPos() = point; // move skater
|
||
|
GetPos() += mWallNormal; // move away from surface
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Rotate velocity to plane.
|
||
|
GetVel().RotateToPlane(mWallNormal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// Mick: if we set the velocity as direction, skater will keep going up more
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[Y] = mWallNormal;
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
GetMatrix()[X].Normalize();
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// Decide whether left or right wall-ride.
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector Up(0.0f, 1.0f, 0.0f);
|
||
|
// Mth::Vector Cross = Mth::CrossProduct(mWallNormal, Up);
|
||
|
#else
|
||
|
Mth::Vector Cross(-mWallNormal[Z], 0.0f, mWallNormal[X], 0.0f);
|
||
|
#endif
|
||
|
|
||
|
if (Mth::DotProduct(GetVel(), Cross) < 0.0f)
|
||
|
{
|
||
|
// Let the script do any extra logic, like playing anims & stuff.
|
||
|
FLAGEXCEPTION(CRCD(0x5de19c83, "WallRideLeft"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FLAGEXCEPTION(CRCD(0x51372712, "WallRideRight"));
|
||
|
}
|
||
|
|
||
|
SetState(WALL);
|
||
|
|
||
|
// Handle contact
|
||
|
check_movable_contact();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Otherwise trigger the bail script.
|
||
|
// FLAGEXCEPTION(CRCD(0x2ec3c7f5, "WallRideBail"));
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
float CSkaterCorePhysicsComponent::rotate_away_from_wall ( const Mth::Vector& normal, float &turn_angle, float lerp )
|
||
|
{
|
||
|
// given a wall normal, then calculate the "turn_angle" to rotate the skater by and rotate the matrix and display matrix by this
|
||
|
// return the angle between the skater and the wall
|
||
|
// note turn_angle is passed by reference, and is altered !!!!
|
||
|
|
||
|
// given m_right(dot)normal, we should be able to get a nice angle
|
||
|
float dot_right_normal = Mth::DotProduct(GetMatrix()[X], normal);
|
||
|
|
||
|
float angle = acosf(Mth::Clamp(dot_right_normal, -1.0f, 1.0f));
|
||
|
|
||
|
if (angle > Mth::PI / 2.0f)
|
||
|
{
|
||
|
angle -= Mth::PI;
|
||
|
}
|
||
|
|
||
|
// angle away from the wall
|
||
|
turn_angle = angle * GetPhysicsFloat(CRCD(0xe07ee1a9, "Wall_Bounce_Angle_Multiplier")) * lerp;
|
||
|
|
||
|
// Rotate the skater so he is at a slight angle to the wall, especially if we are in a right angled corner, where the skater will bounce out
|
||
|
|
||
|
GetVel().RotateY(turn_angle);
|
||
|
DUMP_VELOCITY;
|
||
|
GetMatrix().RotateYLocal(turn_angle);
|
||
|
m_lerping_display_matrix.RotateYLocal(turn_angle);
|
||
|
|
||
|
return angle;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::check_leaning_into_wall ( )
|
||
|
{
|
||
|
// If we are leaning left or right, then do a collision check in that direction, and proportional to the amount of the lean.
|
||
|
// If there is a collision, then push the skater away from the wall.
|
||
|
|
||
|
// First determine if we actually need to be doing this
|
||
|
|
||
|
// Need to be moving at a reasonable rate
|
||
|
if (GetVel().Length() > GetPhysicsFloat(CRCD(0xbc33d268, "Skate_min_wall_lean_push_speed"))) return;
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
float time;
|
||
|
if (control_pad.m_left.GetPressed())
|
||
|
{
|
||
|
time = control_pad.m_left.GetPressedTime();
|
||
|
}
|
||
|
else if (control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
time = control_pad.m_right.GetPressedTime();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
time *= 1.0f / 1000.0f;
|
||
|
|
||
|
// Calculate the length of the vector based on how long you have held down the left or right button.
|
||
|
// (Ideally it would be tied to the animation, but this is simple, and it works)
|
||
|
float min_time = GetPhysicsFloat(CRCD(0x435f0653, "Skate_wall_lean_push_time"));
|
||
|
if (time > min_time)
|
||
|
{
|
||
|
time = min_time;
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
time = -time;
|
||
|
}
|
||
|
float length = time / min_time * GetPhysicsFloat(CRCD(0x4b8bab12, "Skate_wall_lean_push_length"));
|
||
|
|
||
|
|
||
|
// Now we've got the length, and in the right direction, check for a collision
|
||
|
|
||
|
m_col_start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xbfbbd0af, "Skate_wall_lean_push_height"));
|
||
|
m_col_end = m_col_start + GetMatrix()[X] * length;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
|
||
|
// see how much we are into the wall
|
||
|
Mth::Vector push = m_feeler.GetPoint() - m_col_end;
|
||
|
|
||
|
// Only push directly out from the wall (like the upwards collision, otherwise we get pushed back)
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
push.ProjectToNormal(normal);
|
||
|
|
||
|
m_col_start = GetPos() + GetMatrix()[Y];
|
||
|
m_col_end = m_col_start + push;
|
||
|
if (!get_member_feeler_collision())
|
||
|
{
|
||
|
// just move him, might put him 1 inch above the ground, but regular physics should snap him down
|
||
|
GetPos() = m_col_end - GetMatrix()[Y];
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::flip_if_skating_backwards ( )
|
||
|
{
|
||
|
// if sufficient speed, and facing backwards
|
||
|
if (!GetFlag(IS_BAILING)
|
||
|
&& !GetFlag(SKITCHING) // if skitching, probably just car turning a slow sharp corner
|
||
|
&& !is_braking()
|
||
|
&& Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f
|
||
|
&& GetVel().LengthSqr() > Mth::Sqr(GetPhysicsFloat(CRCD(0x2bf71eeb, "Skater_Flip_Speed")))
|
||
|
)
|
||
|
{
|
||
|
// flip Z and X, to rotate 180 degrees about y
|
||
|
GetMatrix()[Z] = -GetMatrix()[Z];
|
||
|
GetMatrix()[X] = -GetMatrix()[X];
|
||
|
m_lerping_display_matrix[Z] = -m_lerping_display_matrix[Z];
|
||
|
m_lerping_display_matrix[X] = -m_lerping_display_matrix[X];
|
||
|
|
||
|
// Dan: we can no longer flip mid animation
|
||
|
/*
|
||
|
CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_flip_and_rotate_component);
|
||
|
p_flip_and_rotate_component->ToggleFlipState();
|
||
|
*/
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::maybe_flag_ollie_exception ( )
|
||
|
{
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
if (GetFlag(TENSE) && Script::GetInteger("TurboOllie"))
|
||
|
{
|
||
|
m_tense_time = GetFlagElapsedTime(TENSE);
|
||
|
SetFlagFalse(TENSE);
|
||
|
FLAGEXCEPTION(CRCD(0x8ffefb28, "Ollied"));
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
if (GetFlag(TENSE) && !control_pad.m_x.GetPressed())
|
||
|
{
|
||
|
// Remember the tense time, cause it will be needed when the Jump script command executes.
|
||
|
m_tense_time = GetFlagElapsedTime(TENSE);
|
||
|
|
||
|
SetFlagFalse(TENSE);
|
||
|
|
||
|
control_pad.m_x.ClearRelease();
|
||
|
|
||
|
FLAGEXCEPTION(CRCD(0x8ffefb28, "Ollied"));
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::maybe_skitch ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
if (GetFlag(SKITCHING) || !SKITCH_BUTTON || GetState() != GROUND) return;
|
||
|
|
||
|
// Find the nearest skitch point.
|
||
|
Mth::Vector closest_pos;
|
||
|
float closest_dist = GetPhysicsFloat(CRCD(0x2928f080, "Skitch_Max_Distance"));
|
||
|
CCompositeObject* p_closest = NULL;
|
||
|
|
||
|
for (CSkitchComponent *p_skitch_comp = static_cast< CSkitchComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_SKITCH));
|
||
|
p_skitch_comp;
|
||
|
p_skitch_comp = static_cast< CSkitchComponent* >(p_skitch_comp->GetNextSameType()))
|
||
|
{
|
||
|
if (!p_skitch_comp->CanSkitch()) continue;
|
||
|
|
||
|
CCompositeObject* p_composite_object = p_skitch_comp->GetObject();
|
||
|
if (p_composite_object == GetObject()) continue;
|
||
|
|
||
|
Mth::Vector skitch_point(0.0f, 0.0f, 0.0f);
|
||
|
int skitch_index = p_skitch_comp->GetNearestSkitchPoint(&skitch_point, GetPos());
|
||
|
if (skitch_index == -1) continue;
|
||
|
|
||
|
Mth::Vector line_to = skitch_point - GetPos();
|
||
|
float dist_to = line_to.Length();
|
||
|
if ( dist_to < closest_dist)
|
||
|
{
|
||
|
closest_dist = dist_to;
|
||
|
p_closest = p_composite_object;
|
||
|
closest_pos = skitch_point;
|
||
|
m_skitch_index = skitch_index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (p_closest)
|
||
|
{
|
||
|
// Clear any triggers, so old L1/R1 presses don't affect us.
|
||
|
control_pad.m_L1.ClearTrigger();
|
||
|
control_pad.m_R1.ClearTrigger();
|
||
|
mp_skitch_object = p_closest;
|
||
|
|
||
|
FLAGEXCEPTION(CRCD(0x2f184eb1, "Skitched"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_in_air_physics ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
// Acceleration is re-calculated every frame
|
||
|
// here it is just set to the air gravity
|
||
|
// there are generally no other forces that we would use in the air
|
||
|
|
||
|
Mth::Vector acc(0.0f, get_air_gravity(), 0.0f);
|
||
|
|
||
|
SetFlagFalse(LAST_POLY_WAS_VERT);
|
||
|
|
||
|
// disallow acid drops before this frame's breaking of vert air
|
||
|
if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS) || GetFlag(IN_ACID_DROP) || GetFlag(IS_BAILING))
|
||
|
{
|
||
|
SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// Adjust orientation before we do any movement
|
||
|
// in the air the orientation is independent of the velocity but it will affect the collision checks we do (left/right/forwards/up)
|
||
|
|
||
|
handle_air_rotation(); // rotate left/right (spinning by holding left/right or L1/R1)
|
||
|
|
||
|
handle_air_lean(); // lean forwards backwards (by hold up/down) also handles spine leans
|
||
|
|
||
|
handle_transfer_slerping(); // during acid drops, we slerp to our required orientation
|
||
|
|
||
|
if (GetFlag(IN_RECOVERY) || VERT_RECOVERY_BUTTONS)
|
||
|
{
|
||
|
// no control here, except for the VERT_RECOVERY_BUTTONS, above
|
||
|
handle_air_vert_recovery(); // recovering from going off the end of a vert polygon
|
||
|
}
|
||
|
|
||
|
// Upright if not vert, and not doing a spine transfer
|
||
|
if (!GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS) && !mp_rotate_component->IsApplyingRotation(X) && !mp_rotate_component->IsApplyingRotation(Z))
|
||
|
{
|
||
|
rotate_upright();
|
||
|
}
|
||
|
|
||
|
// end of adjusting orientation in air
|
||
|
////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Apply special friction even in the air
|
||
|
|
||
|
if (m_special_friction_duration != 0.0f)
|
||
|
{
|
||
|
apply_rolling_friction();
|
||
|
}
|
||
|
|
||
|
// end of friction
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// Calculate what velocity to use
|
||
|
// generally this is just the stored m_vel, but if we are doing
|
||
|
// a spine transfer we a dd in m_spine_vel
|
||
|
// (note, we also update the state of the spine transfer here,
|
||
|
// perhaps that would be better done elsewhere)
|
||
|
|
||
|
Mth::Vector vel = GetVel();
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
// this check is only valid if we are not in contact with a moving object
|
||
|
if (vel[Y] < 0.0f && GetPos()[Y] < m_transfer_target_height) // just check if we have dropped below the target height
|
||
|
{
|
||
|
// if (Mth::Abs(mp_state_component->m_spine_vel.Length() - 0.1f) > 1.0f)
|
||
|
// {
|
||
|
// Gfx::AddDebugStar(GetPos(), 24, GREEN, 0);
|
||
|
// }
|
||
|
mp_state_component->m_spine_vel.Normalize(0.1f); // make spine velocity very small, but still there, so camera works
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vel += mp_state_component->m_spine_vel;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
// calculate contact movement
|
||
|
// If the skater is in contact with an object, then his velocity (m_vel) is considered to be local to that object, so here we have to add in the
|
||
|
// movement of the object from the last frame; we also account for rotation here, in the call to mp_movable_contact_component->UpdateContact
|
||
|
|
||
|
Mth::Vector contact_movement;
|
||
|
contact_movement.Set(0.0f, 0.0f, 0.0f);
|
||
|
|
||
|
// if we are in contact with something, then factor in that "movement"
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos()))
|
||
|
{
|
||
|
contact_movement = mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
}
|
||
|
|
||
|
// end of calculating contact movment
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
///////////////////////////////////////////////////////////////
|
||
|
// Update position
|
||
|
|
||
|
// Update position using correct equations of motion (S = ut + 0.5at^2)
|
||
|
GetPos() += vel * m_frame_length + acc * (0.5f * m_frame_length * m_frame_length);
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// add in movement due to contact
|
||
|
GetPos() += contact_movement;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
|
||
|
{
|
||
|
if (GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
Gfx::AddDebugLine(GetPos(), GetOldPos(), RED, 0, 0);
|
||
|
}
|
||
|
else if (GetFlag(VERT_AIR))
|
||
|
{
|
||
|
Gfx::AddDebugLine(GetPos(), GetOldPos(), YELLOW, 0, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Gfx::AddDebugLine(GetPos(), GetOldPos(), BLUE, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// end updating position
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////
|
||
|
// update velocity
|
||
|
//
|
||
|
|
||
|
// Update Velocity in air
|
||
|
GetVel() += acc * m_frame_length;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
//
|
||
|
// end updating velocity
|
||
|
/////////////////////////////////////////////////////////////////
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////
|
||
|
// Handle trying to break VERT
|
||
|
//
|
||
|
|
||
|
if (GetFlag(CAN_BREAK_VERT))
|
||
|
{
|
||
|
if (GetFlag(TRACKING_VERT) && GetFlag(VERT_AIR))
|
||
|
{
|
||
|
// might want to check how long we've been in the air before allowing this
|
||
|
// also don't want to do it if we've been doing tricks and stuff
|
||
|
|
||
|
// first of all, check if there is a collision beneath my feet
|
||
|
m_col_start = GetPos() + GetMatrix()[Y] * 1.0f; // bit above the feet
|
||
|
m_col_end = GetPos() + GetMatrix()[Y] * -23.0f;
|
||
|
if (!get_member_feeler_collision())
|
||
|
{
|
||
|
maybe_break_vert();
|
||
|
|
||
|
// if we did not break vert now, then only allow us to break vert later if we've been tapping the "up" button
|
||
|
// this is indicated by us having RELEASED or Pressed in the last few ticks
|
||
|
if (static_cast< int >(control_pad.m_up.GetReleasedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time"))
|
||
|
&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time")))
|
||
|
{
|
||
|
// "UP" was not pressed any time recently, so don't let us break late
|
||
|
SetFlagFalse(CAN_BREAK_VERT);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Allow us to break vert polys for a while after we ge onto them
|
||
|
// to make it more robust
|
||
|
// Essentially lets CAN_BREAK_VERT flag expire....
|
||
|
if (static_cast< int >(Tmr::ElapsedTime(GetFlagTime(CAN_BREAK_VERT))) > GetPhysicsInt(CRCD(0x26495947, "Skater_Vert_Allow_break_Time")))
|
||
|
{
|
||
|
SetFlagFalse(CAN_BREAK_VERT);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We are flagged as not being able to break vert
|
||
|
// however, we still want to be able to do this at any time on the up journey
|
||
|
// if we try break the spine
|
||
|
if (GetFlag(VERT_AIR) && BREAK_SPINE_BUTTONS && !GetFlag(SPINE_PHYSICS) && GetVel()[Y] > 0.0f)
|
||
|
{
|
||
|
maybe_break_vert();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// End of Handling breaking Vert
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
if (!mp_movable_contact_component->HaveContact())
|
||
|
{
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Now update the tracking position before we do a collision check
|
||
|
// "Tracking" is used when we are "vert" (moving in a vertical plane through the air above a quarterpipe), to track the ground directly
|
||
|
// benath us, and if the QP bends, to move the skater appropiately so he can catch vert air along bent QPs, and round corners.
|
||
|
|
||
|
if (GetFlag(TRACKING_VERT) && GetFlag(VERT_AIR))
|
||
|
{
|
||
|
m_col_start[X] = GetPos()[X];
|
||
|
m_col_start[Y] = m_vert_pos[Y];
|
||
|
m_col_start[Z] = GetPos()[Z];
|
||
|
|
||
|
m_col_end = m_col_start;
|
||
|
|
||
|
m_col_start += m_vert_normal * 30; // Away from face
|
||
|
m_col_end -= m_vert_normal * 30; // into face
|
||
|
|
||
|
// Now see if there is a collision, and track it if so
|
||
|
|
||
|
// First we try an above this position
|
||
|
// raising us up by m_vert_upstep
|
||
|
// which starts at 6 inches, and is halved down until
|
||
|
// less than half an inch, at which point we stop trying to move up
|
||
|
|
||
|
bool collision;
|
||
|
|
||
|
if (m_vert_upstep > 0.5f)
|
||
|
{
|
||
|
m_col_start[Y] += m_vert_upstep;
|
||
|
m_col_end[Y] += m_vert_upstep;
|
||
|
// Only check for vert polys, ignore collision info
|
||
|
collision = get_member_feeler_collision(0, mFD_VERT);
|
||
|
|
||
|
// If we did not find a collision six inches above, then check back at the old height
|
||
|
if (!collision)
|
||
|
{
|
||
|
m_col_start[Y] -= m_vert_upstep;
|
||
|
m_col_end[Y] -= m_vert_upstep;
|
||
|
m_vert_upstep *= 1.0f / 2.0f; // binary search will zoom in on the edge
|
||
|
collision = get_member_feeler_collision(0, mFD_VERT);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
collision = get_member_feeler_collision(0, mFD_VERT);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!collision)
|
||
|
{
|
||
|
// there is no collision, which usually mean we have gone off the end of a qp but might mean the tracking point has drifted off the top
|
||
|
// of the very polygon (it might not be exactly horizontal), so try tracking down up to 30 inches to see if we can find it again
|
||
|
for (int i = 0; i < 10; i++)
|
||
|
{
|
||
|
m_col_start[Y] -= 3.0f;
|
||
|
m_col_end[Y] -= 3.0f;
|
||
|
collision = get_member_feeler_collision(0, mFD_VERT);
|
||
|
if (collision)
|
||
|
{
|
||
|
// need to store new m_vert_pos
|
||
|
// as code below does not generally allow us to go below it
|
||
|
m_vert_pos[Y] = m_feeler.GetPoint()[Y];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// Dot product in the XZ plane is the angle between them
|
||
|
float change_dot = sqrtf(
|
||
|
m_vert_normal[X] * m_feeler.GetNormal()[X] + m_vert_normal[Z] * m_feeler.GetNormal()[Z]
|
||
|
);
|
||
|
|
||
|
// the dot check is a fix for sharp corners, nearly are right angles, TT#438
|
||
|
if (collision && change_dot > 0.02f)
|
||
|
{
|
||
|
// let's just track it simple for now
|
||
|
Mth::Vector track_point = m_feeler.GetPoint();
|
||
|
|
||
|
// This is a bit of a patch
|
||
|
// basically clamp the tracking point at the hihgest level it has reached, that way we can never "slip" down a slope
|
||
|
// which the rest of the physics makes you do, for reasons best explained with a diagram
|
||
|
if (m_vert_pos[Y] > track_point[Y])
|
||
|
{
|
||
|
track_point[Y] = m_vert_pos[Y];
|
||
|
}
|
||
|
|
||
|
// keep vert pos updated (only used for height)
|
||
|
m_vert_pos = track_point;
|
||
|
|
||
|
GetPos()[X] = track_point[X];
|
||
|
GetPos()[Z] = track_point[Z];
|
||
|
m_vert_normal = m_feeler.GetNormal();
|
||
|
|
||
|
// offset the jumper away from the plane
|
||
|
GetPos() += m_vert_normal * (GetPhysicsFloat(CRCD(0x78384871, "Physics_Vert_Push_Out")));
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// The normal we might be tracking might not be vert, so need to adjust it so it is fully horizontal
|
||
|
// it should never be fully horizontal (which would cause it to implode
|
||
|
Mth::Vector flat_normal = m_vert_normal;
|
||
|
flat_normal[Y] = 0.0f;
|
||
|
flat_normal.Normalize();
|
||
|
|
||
|
// adjust the orientation to match the plane
|
||
|
new_normal(flat_normal);
|
||
|
|
||
|
// now velocity
|
||
|
GetVel().RotateToPlane(flat_normal);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// End of updating tracking
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
///////////////////////////////////////////////////////
|
||
|
// Adjust the normal while in the air
|
||
|
//
|
||
|
|
||
|
// When on an air poly, we have the usual drifting of the UP vector to smooth out changes in the normal
|
||
|
// (note, we can be in VERT_AIR, but also SPINE_PHYSICS, in whcih case the orientation is controled by the "lean" routine
|
||
|
// which is leaning the skater forward, so he comes down on the opposing face)
|
||
|
if (GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS)
|
||
|
&& !mp_rotate_component->IsApplyingRotation(X) && !mp_rotate_component->IsApplyingRotation(Z))
|
||
|
{
|
||
|
adjust_normal();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// otherwise, the matrix is same for display and physics
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// End of adjusting normal
|
||
|
//////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////
|
||
|
// Handle any collisions resultant from our movement
|
||
|
// remember at this point m_pos is the new position
|
||
|
// and we might have gone through a wall
|
||
|
|
||
|
// here again, we are factoring in the contact_movement to the start position
|
||
|
// We check if there is anything in font of us
|
||
|
bool snapped_up = handle_forward_collision_in_air(GetOldPos() + contact_movement);
|
||
|
|
||
|
// If we are now in the WALL (Wallride) state, then just return
|
||
|
if (GetState() == WALL || GetState() == WALLPLANT)
|
||
|
{
|
||
|
// Maybe call DoWallPhysics() to avoid a glitch? Maybe not, cos GetOldPos() will be wrong ?
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// check for actual movement collision (along the full line of travel)
|
||
|
// note we factor in the contact_movement here which effectivly ignores it
|
||
|
// so contact movement could possibly drag us through a solid object
|
||
|
// if this is a problem, then we need to add another collision check, just for the contact movement
|
||
|
m_col_start = GetOldPos() + contact_movement;
|
||
|
m_col_end = GetPos();
|
||
|
|
||
|
if (!snapped_up && get_member_feeler_collision())
|
||
|
{
|
||
|
bool hit_vertical = m_feeler.GetNormal()[Y] < 0.1f;
|
||
|
|
||
|
if (check_for_air_snap_up(GetOldPos() + contact_movement) && (GetVel()[Y] > 10.0f || hit_vertical) && mp_movable_contact_component->GetTimeSinceLastLostContact() > 500)
|
||
|
{
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
// set our position (the point of contact) to be that point we just found
|
||
|
GetPos() = m_feeler.GetPoint();
|
||
|
|
||
|
// Move point up one inch to avoid dropping through geometry with something below it (like canada blades, and park editor stuff)
|
||
|
GetPos() += normal;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
if (m_col_flag_skatable)
|
||
|
{
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// handle landing on the ground
|
||
|
// Collided with a skatable face!! yay, let's stick to it!
|
||
|
|
||
|
SetState(GROUND);
|
||
|
check_movable_contact();
|
||
|
|
||
|
// Used by the LandedFromVert script command.
|
||
|
// landing from a spine transfer is considered to be the same as landing from vert
|
||
|
if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
// Note: Not setting it to false if the VERT_AIR flag is not set.
|
||
|
// This is because sometimes Scott wants to force the mLandedFromVert to be on from in script. Don't want landing to override that.
|
||
|
// mLandedFromVert never gets reset by the C-code, only by the ResetLandedFromVert script command.
|
||
|
mLandedFromVert = true;
|
||
|
|
||
|
// This flag however cannot be cleared by script
|
||
|
// Added this flag for use by ClearPanel_Landed, because mLandedFromVert is false at that point even if landing from vert,
|
||
|
// due to being cleared by script.
|
||
|
m_true_landed_from_vert = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_true_landed_from_vert = false;
|
||
|
}
|
||
|
|
||
|
mLandedFromSpine = GetFlag(SPINE_PHYSICS);
|
||
|
|
||
|
set_terrain(m_feeler.GetTerrain());
|
||
|
|
||
|
mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
|
||
|
|
||
|
if (!mp_trick_component->GraffitiTrickStarted())
|
||
|
{
|
||
|
// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
|
||
|
mp_trick_component->SetGraffitiTrickStarted(false);
|
||
|
}
|
||
|
|
||
|
set_last_ground_feeler(m_feeler);
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
|
||
|
|
||
|
// Now, we landed, and triggered an event, which might have reset us, so we should possibly abort here if we were restarted
|
||
|
if (mp_physics_control_component->HaveBeenReset())
|
||
|
{
|
||
|
// K: I added this here to be consistent with the above change on 7 Mar (see above comment)
|
||
|
// Need it though?
|
||
|
FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (!m_true_landed_from_vert // not if coming down off vert
|
||
|
&& !mLandedFromVert // and not pretending to (like jumping out of a lip trick)
|
||
|
&& GetVel()[X] == 0.0f
|
||
|
&& GetVel()[Z] == 0.0f
|
||
|
&& control_pad.m_down.GetPressed())
|
||
|
{
|
||
|
// we had just jumped straight up, and are braking, and not on vert
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
GetVel().ProjectToPlane(m_feeler.GetNormal()); // kill vel that is perpendicular to normal
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// special landing from transfers to prevent speed loss
|
||
|
|
||
|
Mth::Vector landing_vel = GetVel();
|
||
|
|
||
|
// rotate all velocity to the facing direction
|
||
|
GetVel().RotateToNormal(m_transfer_goal_facing);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// now, rotate into the plane instead of projecting (actually, m_transfer_goal_facing should already be in the plane)
|
||
|
GetVel().RotateToPlane(m_feeler.GetNormal());
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// test what velocity we will have once the ground physics removes sideways velocity
|
||
|
Mth::Vector test_vel = GetVel();
|
||
|
remove_sideways_velocity(test_vel);
|
||
|
|
||
|
// if we'd be moving upwards (this can occur because sometimes an acid drop's goal facing is along a vert, not down it; and thus,
|
||
|
// a slight upwards rotation means that the skater will land pointing up and immediately hop off the vert; this looks very
|
||
|
// bizzare, so we prevent it here)
|
||
|
if (test_vel[Y] > 0.0f)
|
||
|
{
|
||
|
// use a standard landing instead
|
||
|
GetVel() = landing_vel;
|
||
|
GetVel().ProjectToPlane(m_feeler.GetNormal());
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
if (GetVel().LengthSqr() < Mth::Sqr(Script::GetFloat(CRCD(0x59484878, "Physics_Acid_Drop_Min_Land_Speed"))))
|
||
|
{
|
||
|
GetVel().Normalize(Script::GetFloat(CRCD(0x59484878, "Physics_Acid_Drop_Min_Land_Speed")));
|
||
|
}
|
||
|
}
|
||
|
GetVel().ZeroIfShorterThan(10.0f);
|
||
|
}
|
||
|
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
|
||
|
m_display_normal = m_feeler.GetNormal();
|
||
|
m_current_normal = m_display_normal;
|
||
|
m_last_display_normal = m_display_normal;
|
||
|
|
||
|
GetMatrix()[Y] = m_display_normal;
|
||
|
GetMatrix().OrthoNormalizeAbout(Y);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// Flagging the exceptions needs to be the last thing done, as the script for the
|
||
|
// excpetion is executed immediately, and might need some of the above values
|
||
|
// Like, PitchGreaterThan requires m_last_display_matrix to be correct (as calculated above)
|
||
|
|
||
|
FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
|
||
|
|
||
|
return;
|
||
|
|
||
|
//
|
||
|
// end of handling landing
|
||
|
//////////////////////////////////////////////////////////////
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// it's a wall, bounce off it
|
||
|
GetVel().RotateToPlane(normal);
|
||
|
DUMP_VELOCITY;
|
||
|
GetMatrix()[Z].RotateToPlane(normal);
|
||
|
GetMatrix().OrthoNormalizeAbout(Z);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// Bit of a patach here to move the skater away from the wall
|
||
|
GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// no acid drops after an in-air collision
|
||
|
SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No forward collision was found, so nothing to do
|
||
|
|
||
|
// so do another check to push me away from walls, will also need a wallride check here
|
||
|
#ifdef STICKY_WALLRIDES
|
||
|
if (!GetFlag(VERT_AIR))
|
||
|
{
|
||
|
// check if we are too close to a wall, and pop us out and away
|
||
|
check_side_collisions();
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Push skater down from any roof he might now be under
|
||
|
if (handle_upward_collision_in_air())
|
||
|
{
|
||
|
// no acid drops after an in-air collision
|
||
|
SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// End of handling collisions
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// If we've triggered a jump, then do the jump (note, same as jump from ground for now, might want to make it different)
|
||
|
maybe_flag_ollie_exception();
|
||
|
|
||
|
// if in standard vanilla air, check for acid drop
|
||
|
if (!GetFlag(AIR_ACID_DROP_DISALLOWED) && BREAK_SPINE_BUTTONS)
|
||
|
{
|
||
|
bool count_as_skate_off_edge = m_went_airborne_time > m_last_jump_time_stamp
|
||
|
&& Tmr::ElapsedTime(m_went_airborne_time) < 250;
|
||
|
|
||
|
SAcidDropData acid_drop_data;
|
||
|
if (maybe_acid_drop(count_as_skate_off_edge, GetPos(), GetOldPos(), GetVel(), acid_drop_data))
|
||
|
{
|
||
|
enter_acid_drop(acid_drop_data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_air_rotation ( )
|
||
|
{
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
// Part 1 - Decide on what spin to do, based on the controls
|
||
|
|
||
|
if (mNoSpin) return;
|
||
|
|
||
|
// IF doing a spine transfer, then no auto turning....
|
||
|
if (GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
|
||
|
int time = 0;
|
||
|
float rot = 0.0f;
|
||
|
float no_time = 0.0f;
|
||
|
float ramp_time = 0.0f;
|
||
|
|
||
|
// if (!USE_BIKE_PHYSICS)
|
||
|
// {
|
||
|
if (m_spin_taps)
|
||
|
{
|
||
|
if (control_pad.m_L1.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_L1.ClearTrigger();
|
||
|
if (!control_pad.m_R1.GetPressed()) // ignore it if R1 is pressed
|
||
|
{
|
||
|
m_tap_turns += Mth::PI;
|
||
|
}
|
||
|
}
|
||
|
if (control_pad.m_R1.GetTriggered())
|
||
|
{
|
||
|
control_pad.m_R1.ClearTrigger();
|
||
|
if (!control_pad.m_L1.GetPressed()) // ignore it if L1 is pressed
|
||
|
{
|
||
|
m_tap_turns -= Mth::PI;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_tap_turns != 0.0f)
|
||
|
{
|
||
|
time = 1;
|
||
|
float turn_amount = Mth::Sgn(m_tap_turns) * GetSkater()->GetScriptedStat(CRCD(0xf7a2acc1, "Physics_air_tap_turn_speed_stat"));
|
||
|
if (Mth::Abs(turn_amount * m_frame_length) > Mth::Abs(m_tap_turns))
|
||
|
{
|
||
|
m_tap_turns = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_tap_turns -= turn_amount * m_frame_length;
|
||
|
}
|
||
|
rot = turn_amount;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (control_pad.m_L1.GetPressed() && !control_pad.m_R1.GetPressed())
|
||
|
{
|
||
|
rot = GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));
|
||
|
time = 1;
|
||
|
if (control_pad.m_L1.GetPressedTime() > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
|
||
|
{
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_R1.GetPressed() && !control_pad.m_L1.GetPressed())
|
||
|
{
|
||
|
rot = -GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));
|
||
|
time = 1;
|
||
|
if (control_pad.m_R1.GetPressedTime() > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
|
||
|
{
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// }
|
||
|
|
||
|
// if just transitioned from walking, ignore Left/Right for rotation purposes
|
||
|
if (!GetFlag(NO_ORIENTATION_CONTROL))
|
||
|
{
|
||
|
// if time is set, then we just ignore the Left/Right button and take no account of the spinning ramp
|
||
|
if (!time)
|
||
|
{
|
||
|
if (control_pad.m_left.GetPressed())
|
||
|
{
|
||
|
rot = GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));
|
||
|
time = control_pad.m_left.GetPressedTime();
|
||
|
if (time > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
|
||
|
{
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_right.GetPressed())
|
||
|
{
|
||
|
rot = -GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));
|
||
|
time = control_pad.m_right.GetPressedTime();
|
||
|
if (time > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
|
||
|
{
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
no_time = GetPhysicsFloat(CRCD(0xa092b2be, "Physics_Air_No_Rotate_Time"));
|
||
|
ramp_time = GetPhysicsFloat(CRCD(0x5d756349, "Physics_Air_Ramp_Rotate_Time"));
|
||
|
|
||
|
// if just tapped, then no leaning for a while
|
||
|
if (time <= no_time)
|
||
|
{
|
||
|
rot = 0;
|
||
|
}
|
||
|
|
||
|
// if tapped enough, then ramp up to full speed over a small amount of time
|
||
|
if (time < ramp_time)
|
||
|
{
|
||
|
rot = rot * (time - no_time) / (ramp_time - no_time);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we are not rotating by hand, then might want to auto-turn
|
||
|
if (!rot && GetFlag(VERT_AIR) && GetFlag(AUTOTURN))
|
||
|
{
|
||
|
float angle_from_vert = acosf(Mth::Clamp(GetMatrix()[Z][Y], -1.0f, 1.0f));
|
||
|
|
||
|
if (angle_from_vert < Mth::DegToRad(GetPhysicsFloat(CRCD(0x61224f2e, "skater_autoturn_vert_angle"))))
|
||
|
{
|
||
|
// If pointing more or less straight up, then don't auto-turn
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float angle = Mth::GetAngleAbout(GetMatrix()[Z], m_fall_line, GetMatrix()[Y]);
|
||
|
|
||
|
rot = Mth::Sgn(angle) * GetPhysicsFloat(CRCD(0x9db33213, "Skater_autoturn_speed"));
|
||
|
if (Mth::Abs(rot * m_frame_length) > Mth::Abs(angle))
|
||
|
{
|
||
|
// just about done, so just turn the last bit of angle left
|
||
|
rot = angle / m_frame_length;
|
||
|
SetFlagFalse(AUTOTURN);
|
||
|
}
|
||
|
#if 0 // Dan: removed as this code has no effect
|
||
|
time = 1; // frig, we really don't want to start auto turn immediately
|
||
|
ramp_time = 0;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// End of Part 1
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if (rot == 0.0f) return;
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
// Part 2 - Do the actual rotation
|
||
|
|
||
|
mYAngleIncreased = rot > 0.0f;
|
||
|
|
||
|
rot *= m_frame_length;
|
||
|
GetMatrix().RotateYLocal(rot);
|
||
|
m_lerping_display_matrix.RotateYLocal(rot);
|
||
|
|
||
|
// End of Part 2
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
// Part 3 - tracking rotation for the purposes of score (Spin multipliers)
|
||
|
// Keep track of the total angle spun through.
|
||
|
|
||
|
mp_trick_component->mTallyAngles += Mth::RadToDeg(rot);
|
||
|
|
||
|
// Set the spin.
|
||
|
if (GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
// If in vert air, only count the spin if it is at least 360, because getting 180 is too easy.
|
||
|
if (Mth::Abs(mp_trick_component->mTallyAngles) >= 360.0f - GetPhysicsFloat(CRCD(0x50c5cc2f, "spin_count_slop")) + 0.1f)
|
||
|
{
|
||
|
mp_score_component->GetScore()->UpdateSpin(mp_trick_component->mTallyAngles);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mp_score_component->GetScore()->UpdateSpin(mp_trick_component->mTallyAngles);
|
||
|
}
|
||
|
|
||
|
// End of Part 3
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_air_lean ( )
|
||
|
{
|
||
|
// Handle leaning formward and backwards in the air
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
// Spine specific lean, should be seperated out for spine physics
|
||
|
// we automatically rotate forward but don't let skaters's up vector to go past horizontal
|
||
|
|
||
|
// no leaning during transfers; we slerp instead
|
||
|
if (GetFlag(SPINE_PHYSICS) || GetFlag(NO_ORIENTATION_CONTROL)) return;
|
||
|
|
||
|
#if 0 // old transfer code
|
||
|
if (GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
if (GetMatrix()[Y][Y] > -0.05f) // if y component of up vector (the Y vector) is +ve, then we are heads up
|
||
|
{
|
||
|
float rot = GetSkater()->GetScriptedStat(CRCD(0x110a1742, "Physics_spine_lean_stat"));
|
||
|
|
||
|
GetMatrix().Rotate(m_spine_rotate_axis, -rot*m_frame_length);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
// 1 - Control Logic
|
||
|
//
|
||
|
|
||
|
// don't even try to lean if in VERT_AIR
|
||
|
if (GetFlag(VERT_AIR)) return;
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
// Don't want to lean if "grab" or "kick" pressed, as we could be doing some kind of grab trick
|
||
|
if (control_pad.m_circle.GetPressed() || control_pad.m_square.GetPressed()) return;
|
||
|
|
||
|
float rot = 0.0f;
|
||
|
int time = 0;
|
||
|
|
||
|
if (control_pad.m_up.GetPressed())
|
||
|
{
|
||
|
rot = GetSkater()->GetScriptedStat(CRCD(0xcbfdd841, "Physics_air_lean_stat"));
|
||
|
time = control_pad.m_up.GetPressedTime();
|
||
|
}
|
||
|
|
||
|
if (control_pad.m_down.GetPressed())
|
||
|
{
|
||
|
rot = -GetSkater()->GetScriptedStat(CRCD(0xcbfdd841, "Physics_air_lean_stat"));
|
||
|
time = control_pad.m_down.GetPressedTime();
|
||
|
}
|
||
|
|
||
|
float no_time = GetPhysicsFloat(CRCD(0x5f70ef46, "Physics_Air_No_Lean_Time"));
|
||
|
float ramp_time = GetPhysicsFloat(CRCD(0x12cd2d14, "Physics_Air_Ramp_Lean_Time"));
|
||
|
|
||
|
// if just tapped, then no rotation for a while
|
||
|
if (time <= no_time) return;
|
||
|
|
||
|
// if tapped enough, then ramp up to full speed over a small amount of time
|
||
|
if (time < ramp_time)
|
||
|
{
|
||
|
rot = rot * (time - no_time) / (ramp_time - no_time);
|
||
|
}
|
||
|
|
||
|
// end of control logic
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
// air lean physics
|
||
|
|
||
|
if (rot == 0.0f) return;
|
||
|
|
||
|
GetMatrix().RotateXLocal(rot*m_frame_length);
|
||
|
m_lerping_display_matrix.RotateXLocal(rot*m_frame_length);
|
||
|
|
||
|
// end of air lean physics
|
||
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_transfer_slerping ( )
|
||
|
{
|
||
|
// during transfers, we slerp to our required orientation
|
||
|
if (!GetFlag(SPINE_PHYSICS)) return;
|
||
|
|
||
|
// we can't simply use the slerp's result vector at the skater's vector, because we need to allow the skater to rotate; instead, we apply
|
||
|
// the change in the result vector each frame
|
||
|
|
||
|
// increment the timer
|
||
|
m_transfer_slerp_timer += m_frame_length;
|
||
|
|
||
|
// calculate this frame's slerp result
|
||
|
Mth::Matrix transfer_slerp_current_matrix;
|
||
|
m_transfer_slerper.getMatrix(
|
||
|
&transfer_slerp_current_matrix,
|
||
|
Mth::SmoothStep(Mth::ClampMax(m_transfer_slerp_timer / m_transfer_slerp_duration, 1.0f))
|
||
|
);
|
||
|
|
||
|
// invert the slerp result vector from last frame
|
||
|
Mth::Matrix inverse_transfer_slerp_previous_matrix = m_transfer_slerp_previous_matrix;
|
||
|
inverse_transfer_slerp_previous_matrix.InvertUniform();
|
||
|
|
||
|
// calculate the change between the frames
|
||
|
Mth::Matrix transfer_slerp_delta_matrix = inverse_transfer_slerp_previous_matrix * transfer_slerp_current_matrix;
|
||
|
|
||
|
// apply the change to the skater's matrix
|
||
|
GetMatrix() *= transfer_slerp_delta_matrix;
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// store this frame's matrix
|
||
|
m_transfer_slerp_previous_matrix = transfer_slerp_current_matrix;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_air_vert_recovery ( )
|
||
|
{
|
||
|
// recovering from going off the end of a vert polygon
|
||
|
|
||
|
// don't want to recover during a transfer
|
||
|
if (GetFlag(SPINE_PHYSICS)) return;
|
||
|
|
||
|
if (GetVel()[Y] > 0.0f && mp_movable_contact_component->GetContact()) return;
|
||
|
|
||
|
// right, we are in vert, but no longer tracking, so perhaps we are over flat ground?
|
||
|
CFeeler feeler;
|
||
|
feeler.m_start = GetPos();
|
||
|
feeler.m_end = GetPos();
|
||
|
feeler.m_end[Y] -= 500.0f;
|
||
|
|
||
|
// can't find ground
|
||
|
if (!feeler.GetCollision()) return;
|
||
|
|
||
|
// still over vert poly
|
||
|
if (feeler.GetFlags() & mFD_VERT) return;
|
||
|
|
||
|
// still over steep ground
|
||
|
if (feeler.GetNormal()[Y] < 0.2f) return;
|
||
|
|
||
|
if (GetFlag(VERT_AIR))
|
||
|
{
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
}
|
||
|
|
||
|
SetFlagTrue(IN_RECOVERY);
|
||
|
|
||
|
// we want to smoothly rotate the skater in the plane formed by the up vector, and the skater's up vector
|
||
|
|
||
|
// got sufficently far up
|
||
|
if (GetMatrix()[Y][Y] > 0.9f) return;
|
||
|
|
||
|
float rot = GetSkater()->GetScriptedStat(CRCD(0xe7116a96,"Physics_recover_rate_stat"));
|
||
|
|
||
|
// need to rotate the Y vector in the plane formed by the XZ velocity and the (0,1,0) up vector
|
||
|
// (or rather, rotate it about the vector prependicular to this
|
||
|
|
||
|
// get a vector perpendicular to the plane containing m_matrix[Z] and the world up
|
||
|
Mth::Vector forward = GetMatrix()[Y];
|
||
|
forward[Y] = 0.0f;
|
||
|
forward.Normalize();
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector world_up(0.0f,1.0f,0.0f);
|
||
|
// Mth::Vector side = Mth::CrossProduct(forward,world_up);
|
||
|
#else
|
||
|
Mth::Vector side(-forward[Z], 0.0f, forward[X], 0.0f);
|
||
|
#endif
|
||
|
|
||
|
GetMatrix().Rotate(side, rot * m_frame_length);
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::rotate_upright ( )
|
||
|
{
|
||
|
// Attempt to upright player (sideways) while going through air
|
||
|
//
|
||
|
// Cannot just check angle between mUp and world_up as that would include
|
||
|
// tilting forward/backward when character is already uprighted sideways
|
||
|
// (and tilting forward/backward should solely be under player control).
|
||
|
// Thus, should rotate about mFront only.
|
||
|
//
|
||
|
// Then, basically what we want to do is:
|
||
|
// 1) Rotate about mFront so that mUp is in the plane of world_up and mUp.
|
||
|
// 2) Pick one of CW/CCW to rotate by, such that angle between mUp and
|
||
|
// world_up is <= 90 degrees.
|
||
|
//
|
||
|
// As we can be off by at most 180 degrees, rotating by 5 degrees would
|
||
|
// upright us in 180/5=36 frames. For 45 degrees off (more likely), we
|
||
|
// would be uprighted in 45/5=9 frames.
|
||
|
//
|
||
|
// Your average jump is probably around 30 frames long, so try 1 degree?
|
||
|
|
||
|
// If the skater's Z vector is nearly straight up or down, then
|
||
|
// uprighting sideways will not look good
|
||
|
// (On XBox, some innacuracy in Z causes the skater to rotate oddly during "drop-in" situations)
|
||
|
// so, here we test for this case, and just return if so
|
||
|
if (Mth::Abs(GetMatrix()[Z][Y]) > 0.95f) return;
|
||
|
|
||
|
// get a vector perpendicular to the plane containing m_matrix[Z] and the world up
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector v;
|
||
|
// Mth::Vector world_up;
|
||
|
// world_up.Set(0.0f,1.0f,0.0f);
|
||
|
// v = Mth::CrossProduct(GetMatrix()[Z],world_up);
|
||
|
#else
|
||
|
Mth::Vector v(-GetMatrix()[Z][Z], 0.0f, GetMatrix()[Z][X], 0.0f);
|
||
|
#endif
|
||
|
|
||
|
// Then get the dot product of the up vector with this
|
||
|
// if up vector is in the same plane, then this will be zero
|
||
|
float dot = Mth::DotProduct(GetMatrix()[Y], v);
|
||
|
if (dot > 0.02f * m_frame_length * 60.0f) // prevent wobbling
|
||
|
{
|
||
|
float rot = Mth::DegToRad(Script::GetFloat(CRCD(0xabd57877, "skater_upright_sideways_speed")));
|
||
|
GetMatrix().RotateZLocal(rot * m_frame_length);
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
else if (dot < -0.02f * m_frame_length * 60.0f)
|
||
|
{
|
||
|
float rot = Mth::DegToRad(Script::GetFloat(CRCD(0xabd57877, "skater_upright_sideways_speed")));
|
||
|
GetMatrix().RotateZLocal(-rot * m_frame_length);
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::handle_forward_collision_in_air ( const Mth::Vector &start_pos )
|
||
|
{
|
||
|
// return true if there was a collision, but we snapped up over it
|
||
|
|
||
|
Mth::Vector forward = GetPos() - start_pos;
|
||
|
forward.Normalize();
|
||
|
|
||
|
Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height"));
|
||
|
|
||
|
m_col_start = start_pos + up_offset;
|
||
|
m_col_end = GetPos() + up_offset + forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
// we have hit something going forward
|
||
|
// either it is a wall, and we need to bounce off it
|
||
|
// or it is a floor, and we need to stick to it.
|
||
|
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
float dot = Mth::DotProduct(normal, m_current_normal);
|
||
|
if (!m_col_flag_skatable || (dot < 0.8f && normal[Y] < 0.5f))
|
||
|
{
|
||
|
|
||
|
// at this point we have hit something that is not skatable and is fairly vertical
|
||
|
// so we are considering if we want to bounce off it
|
||
|
// one possibility is that it is a low ledge
|
||
|
//
|
||
|
// so, we check for snapping up, and if we do snap up, and hit_vertical is true, then we don't bounce off the wall
|
||
|
|
||
|
/*
|
||
|
// View from side
|
||
|
/
|
||
|
/ <---------- Movement of skater
|
||
|
/
|
||
|
/
|
||
|
---------------+ /
|
||
|
| /
|
||
|
| /
|
||
|
|/
|
||
|
/ <------------- point of collision
|
||
|
/|
|
||
|
/ |
|
||
|
/ |
|
||
|
/ |
|
||
|
/ +--------------- <-------------- The ground
|
||
|
/
|
||
|
/
|
||
|
/
|
||
|
/
|
||
|
/ <------------ where we end up
|
||
|
*/
|
||
|
|
||
|
// Need to store m_feeler, as it is corrupted by CheckForAirSnapUp
|
||
|
CFeeler tmp_feeler = m_feeler;
|
||
|
|
||
|
bool hit_vertical = m_feeler.GetNormal()[Y] < 0.1f;
|
||
|
|
||
|
// Since this collision had the forward vector added into it, we need to temporarily add in the forward vector again
|
||
|
// so we can check for snapping up; note this will result in the skater snapping forward by the length of the forward vector (currently 10")
|
||
|
// whenever we snap up in this particular check
|
||
|
Mth::Vector tmp = GetPos();
|
||
|
GetPos() = m_col_end - up_offset;
|
||
|
if (check_for_air_snap_up(start_pos))
|
||
|
{
|
||
|
// we snapped up, so if we are either moving upward or the first collision was near vertical, then we return true, allowing us to continue
|
||
|
// if we were moving upwards, then we would continue into the air
|
||
|
// if we were moving down, but the collision was near vert, then we do not want to bonk off the surface, and instead we let
|
||
|
// the landing happen in the next frame
|
||
|
if (GetVel()[Y] > 10.0f || hit_vertical) return true;
|
||
|
}
|
||
|
|
||
|
GetPos() = tmp;
|
||
|
DUMP_POSITION;
|
||
|
m_feeler = tmp_feeler;
|
||
|
|
||
|
#ifdef SNAP_OVER_THIN_WALLS
|
||
|
// we have hit a wall, and we can't snap up over i; see if we can just move up and go over it....
|
||
|
|
||
|
// only do this if we are roughly vertical and going up and the wall is very verticle
|
||
|
if (GetMatrix()[Y][Y] > 0.5f // skater is upright
|
||
|
&& GetVel()[Y] > 0.0f // skater travelling upwards
|
||
|
&& normal[Y] < 0.01f) // thing we hit is not far off vert
|
||
|
{
|
||
|
|
||
|
float snap_Y = GetPhysicsFloat(CRCD(0x786b3272, "Physics_Air_Snap_Up"));
|
||
|
tmp_feeler.m_start[Y] += snap_Y;
|
||
|
tmp_feeler.m_end[Y] += snap_Y;
|
||
|
|
||
|
if (!tmp_feeler.GetCollision())
|
||
|
{
|
||
|
while (snap_Y > 4.0f)
|
||
|
{
|
||
|
// move down until we get a collision
|
||
|
// so as to minimize the amount we snap up
|
||
|
tmp_feeler.m_start[Y] -= 2.0f;
|
||
|
tmp_feeler.m_end[Y] -= 2.0f;
|
||
|
if (tmp_feeler.GetCollision()) break;
|
||
|
snap_Y -= 2.0f;
|
||
|
}
|
||
|
GetPos()[Y] += snap_Y;
|
||
|
DUMP_POSITION;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// If we are on a vert poly, then just slide off the wall
|
||
|
// just kill velocity perpendicular to the wall
|
||
|
if (GetFlag(VERT_AIR))
|
||
|
{
|
||
|
GetPos() = m_feeler.GetPoint() - up_offset;
|
||
|
GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
if (!GetFlag(SPINE_PHYSICS))
|
||
|
{
|
||
|
GetVel().ProjectToPlane(normal);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetVel().RotateToPlane(m_feeler.GetNormal());
|
||
|
}
|
||
|
DUMP_VELOCITY;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// maybe wall plant
|
||
|
if (check_for_wallplant()) return false;
|
||
|
|
||
|
// maybe wall ride
|
||
|
if (check_for_wallride()) return false;
|
||
|
|
||
|
// no acid drops after a in-air collision
|
||
|
SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return false;
|
||
|
|
||
|
// only play bonk sound for things that are near vert
|
||
|
if (m_feeler.GetNormal().GetY() < 0.05f)
|
||
|
{
|
||
|
mp_sound_component->PlayBonkSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_feeler.GetTerrain());
|
||
|
}
|
||
|
|
||
|
// it's a wall, bounce off it
|
||
|
|
||
|
float vel_y = 0.0f;
|
||
|
|
||
|
// ignore y vel if the polygon is not too much upside down
|
||
|
if (normal[Y] > -0.1f)
|
||
|
{
|
||
|
vel_y = GetVel()[Y]; // remember old Y vel
|
||
|
GetVel()[Y] = 0.0f; // kill Y vel
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
GetVel().ProjectToPlane(normal); // project X and Z to plane of collision poly
|
||
|
GetVel() += normal * (GetVel().Length() * (1.0f / 10.0f)); // 10% tiny push away.....
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
if (normal[Y] > -0.1f)
|
||
|
{
|
||
|
GetVel()[Y] = vel_y; // restore old Y vel, so it's impossible to jump higher by jumping against a wall
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
float Z_Y = GetMatrix()[Z][Y];
|
||
|
GetMatrix()[Z][Y] = 0.0f;
|
||
|
GetMatrix()[Z].RotateToPlane(normal);
|
||
|
GetMatrix()[Z][Y] = Z_Y;
|
||
|
GetMatrix()[Z] += (1.0f / 20.0f) * normal;
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
GetMatrix()[X].Normalize();
|
||
|
GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
|
||
|
GetMatrix()[Y].Normalize();
|
||
|
|
||
|
// Bit of a patach here to move the skater away from the wall
|
||
|
GetPos() = m_feeler.GetPoint() - up_offset;
|
||
|
GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// if the thing we have collided with is a movable object, then add in the last movement to move us away
|
||
|
if (m_feeler.IsMovableCollision())
|
||
|
{
|
||
|
Mth::Vector obj_vel = m_feeler.GetMovingObject()->GetVel();
|
||
|
|
||
|
// if object is moving in same direction as face normal
|
||
|
if (Mth::DotProduct(obj_vel, m_feeler.GetNormal()) > 0.0f)
|
||
|
{
|
||
|
// then add in the velocity, just to be on the safe side
|
||
|
GetPos() += obj_vel * m_frame_length * 2.0f; // 2.0f is a safety factor
|
||
|
DUMP_POSITION;
|
||
|
// and move with the thing we just hit
|
||
|
GetVel() += obj_vel;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::check_for_air_snap_up ( const Mth::Vector& start_pos )
|
||
|
{
|
||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// We moved from GetOldPos() to m_pos
|
||
|
// and some collision was detected
|
||
|
// either along this line, or along the "forward" line, which is 8 inches above
|
||
|
// regardless, we want to see if the new m_pos (considered bad)
|
||
|
// can be snapped upwards to a surface, where the normal is in the same direction as
|
||
|
// the current movement
|
||
|
// and check to see if there is a clear line to this position
|
||
|
// currently in physics.q: Physics_Air_Snap_Up = 15
|
||
|
//
|
||
|
// Note, we ignore if the skater is going up or down, the calling code has to
|
||
|
// handle that (for the spcial case of hitting a wall coming down, when we can snap over it)
|
||
|
|
||
|
// don't do it if bailing, to avoid snapping through loops
|
||
|
if (GetFlag(IS_BAILING)) return false;
|
||
|
|
||
|
CFeeler feeler;
|
||
|
|
||
|
float up_len = GetPhysicsFloat(CRCD(0x786b3272,"Physics_Air_Snap_Up"));
|
||
|
|
||
|
feeler.m_start = GetPos();
|
||
|
feeler.m_start[Y] += up_len;
|
||
|
feeler.m_end = GetPos();
|
||
|
|
||
|
// Since the new pos might actually be ABOVE the ledge (i.e., we might have just clipped though the corner, and not be inside it)
|
||
|
// then we need to check all the way down to the previous height, but only if moving upwards
|
||
|
if (start_pos[Y] < GetPos()[Y])
|
||
|
{
|
||
|
feeler.m_end[Y] = start_pos[Y];
|
||
|
}
|
||
|
|
||
|
// if the start pos, plus the snap up distance was above the top of the collision line, then extend the collision line up to that
|
||
|
if (start_pos[Y] + up_len > feeler.m_start[Y])
|
||
|
{
|
||
|
feeler.m_start[Y] = start_pos[Y] + up_len;
|
||
|
}
|
||
|
|
||
|
// set up the feeler, now check for collision
|
||
|
if (feeler.GetCollision())
|
||
|
{
|
||
|
// Collision, possibly something we can snap up to
|
||
|
|
||
|
// movement vector is in same direction as face normal, so we are moving away from it
|
||
|
// usually this implies we are moving up, away from the horizontal face at the top of a wall, or a curb
|
||
|
if (feeler.GetNormal().GetY() > 0.5f) // not if too vertical
|
||
|
{
|
||
|
// Okay, we can snap up, so all we have to do is see if the line is clear
|
||
|
Mth::Vector snap_point = feeler.GetPoint() + feeler.GetNormal();
|
||
|
|
||
|
feeler.m_start = start_pos;
|
||
|
feeler.m_start[Y] += up_len;
|
||
|
feeler.m_end = snap_point;
|
||
|
feeler.m_end[Y] += up_len;
|
||
|
|
||
|
if (!feeler.GetCollision())
|
||
|
{
|
||
|
// No collision along upper line,so we consider it safe to move
|
||
|
// note, this will generally result in passing through the corner of some geometry (generally like the top of a wall, or a curb)
|
||
|
GetPos() = snap_point; // set to snap point
|
||
|
GetPos()[Y] += 0.1f; // offset up a little, so we are outside the plane
|
||
|
DUMP_POSITION;
|
||
|
return true; // return true, indicating we snapped up, so carry on in air
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// was a collision when trying to move to new position, so don't move
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// too vert, so don't allow snap (probably on a QP)
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No collision, so don't do anything, probably deep within a wall, or off the level.
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::handle_upward_collision_in_air ( )
|
||
|
{
|
||
|
// return true if there was a collision, but we snapped up over it
|
||
|
|
||
|
// if we are upside down, then return false
|
||
|
if (GetMatrix()[Y][Y] < 0.0f) return false;
|
||
|
|
||
|
float head_height = GetPhysicsFloat(CRCD(0x542cf0c7, "Skater_default_head_height"));
|
||
|
|
||
|
// ignore head collisions for a duration after wallplants
|
||
|
if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) <= static_cast< Tmr::Time >(Script::GetInteger(CRCD(0x2757ed2c, "Physics_Ignore_Ceilings_After_Wallplant_Duration"))))
|
||
|
{
|
||
|
head_height = 6.0f;
|
||
|
}
|
||
|
|
||
|
Mth::Vector up_offset = GetMatrix()[Y] * head_height;
|
||
|
|
||
|
m_col_start = GetPos();
|
||
|
|
||
|
m_col_end = GetPos();
|
||
|
m_col_end += up_offset;
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
Mth::Vector ceiling_normal = m_feeler.GetNormal();
|
||
|
|
||
|
// if it's not at least tilted a bit downwards, then ignore it and return false
|
||
|
if (ceiling_normal[Y] > -0.1f) return false;
|
||
|
|
||
|
// get the vector we need to push down
|
||
|
Mth::Vector push_down = m_feeler.GetPoint() - m_col_end;
|
||
|
|
||
|
// only push down away from the ceiling (otherwise we would push back along the line of travel, jerky)
|
||
|
push_down.ProjectToNormal(ceiling_normal);
|
||
|
|
||
|
// push down as far as we can go, so check for collisions
|
||
|
m_col_start = GetPos();
|
||
|
m_col_end = GetPos() + push_down;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
GetPos() = m_feeler.GetPoint() + 0.001f * m_feeler.GetNormal();
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetPos() = m_col_end;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
GetVel().ProjectToPlane(ceiling_normal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
return true; // we have had a collision, so return true
|
||
|
}
|
||
|
|
||
|
return false; // no collision found, return false
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::handle_upward_collision_in_wallride ( )
|
||
|
{
|
||
|
m_col_start = GetPos() + 18.0f * mWallNormal;
|
||
|
|
||
|
m_col_end = m_col_start;
|
||
|
m_col_end[Y] += GetPhysicsFloat(CRCD(0x542cf0c7, "Skater_default_head_height"));
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
Mth::Vector ceiling_normal = m_feeler.GetNormal();
|
||
|
|
||
|
// if it's not at least tilted a bit downwards, then ignore it and return false
|
||
|
if (ceiling_normal[Y] > -0.1f) return false;
|
||
|
|
||
|
// get the vector we need to push down
|
||
|
Mth::Vector push_down = m_feeler.GetPoint() - m_col_end;
|
||
|
|
||
|
// only push down away from the ceiling (otherwise we would push back along the line of travel, jerky)
|
||
|
push_down.ProjectToNormal(ceiling_normal);
|
||
|
|
||
|
// push down as far as we can go, so check for collisions
|
||
|
m_col_start = GetPos();
|
||
|
m_col_end = GetPos() + push_down;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
GetPos() = m_feeler.GetPoint() + m_feeler.GetNormal();
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetPos() = m_col_end;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
GetVel().ProjectToPlane(ceiling_normal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
return true; // we have had a collision, so return true
|
||
|
}
|
||
|
|
||
|
return false; // no collision found, return false
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_wallride_physics ( )
|
||
|
{
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(IN_RECOVERY);
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
// Wallriding will cancel any memory of what rail we were on, so the next one seems fresh
|
||
|
mp_rail_node = NULL;
|
||
|
m_last_rail_trigger_node_name = 0;
|
||
|
|
||
|
|
||
|
|
||
|
// Keep updating the time, cos it needs to be the time that the last wall ride finished.
|
||
|
mWallrideTime = Tmr::GetTime();
|
||
|
|
||
|
// set wallride movement to nothing
|
||
|
Mth::Vector m_movement(0.0f, 0.0f, 0.0f);
|
||
|
|
||
|
// if we are in contact with something, then factor in that "movement"
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos()))
|
||
|
{
|
||
|
m_movement = mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
if (mp_movable_contact_component->GetContact()->IsRotated())
|
||
|
{
|
||
|
GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Mick: changed to include gravity
|
||
|
// note this is the way THPS2 worked
|
||
|
// the physics might not be intuitive, but it works
|
||
|
// also note we are intentionally not removing sideways components of velocity, so we get to drift up a bit
|
||
|
// after we hit the wall, allowing us to to the "claw" trick (Jump-WallRide-Jump-Grind)
|
||
|
|
||
|
Mth::Vector acc(0.0f, GetPhysicsFloat(CRCD(0xc617caf, "Wall_Ride_Gravity")), 0.0f);
|
||
|
GetPos() += GetVel() * m_frame_length + acc * (0.5f * m_frame_length * m_frame_length);
|
||
|
GetPos() += m_movement;
|
||
|
DUMP_POSITION;
|
||
|
GetVel() += acc * m_frame_length;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector Down(0.0f,-1.0f,0.0f);
|
||
|
// Mth::Vector Horiz=Mth::CrossProduct(mWallNormal,Down);
|
||
|
#else
|
||
|
Mth::Vector Horiz(mWallNormal[Z], 0.0f, -mWallNormal[X], 0.0f);
|
||
|
#endif
|
||
|
Horiz.Normalize();
|
||
|
|
||
|
// Down is a unit vector pointing down along the plane of the wall.
|
||
|
Mth::Vector Down = Mth::CrossProduct(Horiz, mWallNormal);
|
||
|
|
||
|
float Theta = GetPhysicsFloat(CRCD(0x64a48a64, "Wall_Ride_Turn_Speed"));
|
||
|
|
||
|
// Check if Theta is bigger than the angle the skater's z is already making with the Down vector.
|
||
|
// If Theta is bigger, don't rotate by it, otherwise the skater will turn beyond the vertical.
|
||
|
float zdot = Mth::DotProduct(GetMatrix()[Z], Down);
|
||
|
if (zdot > 0.68f || (zdot > 0.0f && zdot > cosf(Theta)))
|
||
|
{
|
||
|
// Pointing pretty much straight down, so don't turn.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Choose which way to turn.
|
||
|
// Need to turn one way or the other depending on whether the skater's z axis is on the left or right side of the down vector.
|
||
|
// This is the same as whether the skater's x axis is pointing up or down, so just check the sign of the dot product of x with down.
|
||
|
float xdot = Mth::DotProduct(GetMatrix()[X], Down);
|
||
|
if (xdot <= 0.0f)
|
||
|
{
|
||
|
Theta = -Theta;
|
||
|
}
|
||
|
|
||
|
// Not strictly required for NGPS, fixes a problem on NGC. However, given m_vel is used only
|
||
|
// as a three-element vector, not a bad idea to set this for all platforms.
|
||
|
GetVel()[W] = 1.0f;
|
||
|
|
||
|
// Rotate both the skater and the skater's velocity by Theta about the wall normal axis.
|
||
|
GetVel().Rotate(mWallNormal, Theta);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// set skater to face in the same direction as velocity
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[Y] = mWallNormal;
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
|
||
|
GetMatrix()[X].Normalize();
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
// Forward collision check
|
||
|
Mth::Vector forward = GetPos() - GetOldPos();
|
||
|
forward.Normalize();
|
||
|
|
||
|
m_col_start = GetOldPos();
|
||
|
|
||
|
m_col_end = GetPos();
|
||
|
m_col_end += forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
// we have hit something going forward
|
||
|
// either it is a wall, and we need to bounce off it or it is a steep QP, and we need to stick to it.
|
||
|
// (note, with the slow normal changing, then it is possible that we might even collide with the poly that we are on)
|
||
|
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
float dot = Mth::DotProduct(normal, mWallNormal);
|
||
|
|
||
|
GetPos() = m_feeler.GetPoint() + normal;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// For fairly shallow curves, the dot between two normals will be pretty large
|
||
|
// it's very important here to distinguish between a tight curve (like in a narrow QP) and a direct hit with a wall.
|
||
|
|
||
|
if (!m_col_flag_wallable || Mth::Abs(dot) < 0.01f)
|
||
|
{
|
||
|
if (normal[Y] > 0.9f)
|
||
|
{
|
||
|
// If the new poly is the ground, pop upright and set the landed exception.
|
||
|
GetPos() = m_feeler.GetPoint();
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Set every orientation type thing I can think of so as to
|
||
|
// pop his orientation to that of the ground.
|
||
|
GetMatrix()[Z] = GetVel();
|
||
|
GetMatrix()[Z].ProjectToPlane(normal); // Mick: project forward vel to ground
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
GetMatrix()[Y] = normal;
|
||
|
GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y],GetMatrix()[Z]);
|
||
|
GetMatrix()[X].Normalize();
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
GetSkaterMatrixQueriesComponentFromObject(GetObject())->ResetLatestMatrix();
|
||
|
|
||
|
m_last_display_normal = GetMatrix()[Y];
|
||
|
m_display_normal = GetMatrix()[Y];
|
||
|
m_current_normal = GetMatrix()[Y];
|
||
|
|
||
|
// check for sticking to rail first; use the "override_air" to stick to rail)
|
||
|
maybe_stick_to_rail(true);
|
||
|
if (GetState() != RAIL)
|
||
|
{
|
||
|
SetState(GROUND);
|
||
|
FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
|
||
|
GetPos() += 18.0f * mWallNormal;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
SetState(AIR);
|
||
|
GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
|
||
|
FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mWallNormal = normal; // update the normal to the new one...
|
||
|
new_normal(mWallNormal);
|
||
|
ResetLerpingMatrix();
|
||
|
GetVel().RotateToPlane(mWallNormal); // make velocity go along it
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (handle_upward_collision_in_wallride())
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
|
||
|
GetPos() += 18.0f * mWallNormal;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
SetState(AIR);
|
||
|
GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
|
||
|
FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
|
||
|
}
|
||
|
|
||
|
// Downwards collision check (down in direction of skater's y)
|
||
|
|
||
|
m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up"));
|
||
|
m_col_start += GetPos();
|
||
|
|
||
|
m_col_end = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0x438ba168, "Wall_Ride_Down_Collision_Check_Length"));
|
||
|
m_col_end += GetPos();
|
||
|
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
mWallNormal=m_feeler.GetNormal();
|
||
|
|
||
|
// Snap position to wall.
|
||
|
GetPos() = m_feeler.GetPoint();
|
||
|
// but one inch out from it
|
||
|
GetPos() += mWallNormal;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Rotate velocity to plane.
|
||
|
GetVel().RotateToPlane(mWallNormal);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// Snap skater's orientation to the plane.
|
||
|
new_normal(mWallNormal);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
if (!m_col_flag_wallable)
|
||
|
{
|
||
|
GetVel() += mWallNormal * 100.0f; // boost away from the wall
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
ResetLerpingMatrix();
|
||
|
SetState(AIR);
|
||
|
|
||
|
GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
|
||
|
FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
|
||
|
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// handle wallride transition from one object to the next
|
||
|
if (m_feeler.GetSector() != m_last_ground_feeler.GetSector())
|
||
|
{
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
}
|
||
|
if (!mp_trick_component->GraffitiTrickStarted())
|
||
|
{
|
||
|
// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
|
||
|
mp_trick_component->SetGraffitiTrickStarted(false);
|
||
|
}
|
||
|
|
||
|
set_last_ground_feeler(m_feeler);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
ResetLerpingMatrix();
|
||
|
GetPos() += 18.0f * mWallNormal;
|
||
|
DUMP_POSITION;
|
||
|
SetState(AIR);
|
||
|
GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
|
||
|
FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
}
|
||
|
|
||
|
// look for head collisions
|
||
|
|
||
|
|
||
|
// This was cut-and-past'd from DoOnGroundPhysics
|
||
|
handle_tensing();
|
||
|
|
||
|
if (maybe_flag_ollie_exception())
|
||
|
{
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
|
||
|
GetVel() += GetPhysicsFloat(CRCD(0x349d02d4, "Wall_Ride_Jump_Out_Speed")) * mWallNormal;
|
||
|
GetVel()[Y] += GetPhysicsFloat(CRCD(0x5a9de35a, "Wall_Ride_Jump_Up_Speed"));
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// Pop the skater upright again immediately.
|
||
|
GetMatrix()[Y].Set(0.0f, 1.0f, 0.0);
|
||
|
GetMatrix().OrthoNormalizeAbout(Y);
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// and immediately adjust the normal, to prevent single frame snaps when walliing and the 90 degree swoop when wallride to grind
|
||
|
m_normal_lerp = 0.0f;
|
||
|
m_display_normal = GetMatrix()[Y];
|
||
|
m_current_normal = m_display_normal;
|
||
|
m_last_display_normal = m_display_normal;
|
||
|
}
|
||
|
|
||
|
// if we've already started doing a trick, then start remembering our trick chain
|
||
|
// GJ: It's possible to do a wallride while mNumTricksInCombo == 0, for some reason
|
||
|
mp_trick_component->TrickOffObject(m_last_ground_feeler.GetNodeChecksum());
|
||
|
|
||
|
#ifdef __NOPT_ASSERT__
|
||
|
if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
|
||
|
{
|
||
|
Gfx::AddDebugLine(GetPos() + m_current_normal, GetOldPos() + m_current_normal, PURPLE, 0, 0);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_wallplant_physics ( )
|
||
|
{
|
||
|
// check if the wallplant duration is up
|
||
|
if (Tmr::ElapsedTime(m_state_change_timestamp) > Script::GetFloat(CRCD(0xa06e446b, "Physics_Wallplant_Duration")))
|
||
|
{
|
||
|
SetState(AIR);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Wallplanting will cancel any memory of what rail we were on, so the next one seems fresh
|
||
|
mp_rail_node = NULL;
|
||
|
m_last_rail_trigger_node_name = 0;
|
||
|
|
||
|
|
||
|
// during a wallplant, our velocity is set to the exit velocity, but we simply ignore it
|
||
|
|
||
|
// the only movement comes from our moving contact; we assume that this movement does not lead to collisions
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos()))
|
||
|
{
|
||
|
GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
DUMP_POSITION;
|
||
|
if (mp_movable_contact_component->GetContact()->IsRotated())
|
||
|
{
|
||
|
GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::maybe_stick_to_rail ( bool override_air )
|
||
|
{
|
||
|
// Only alow them to grind if we are in the air, that way we will avoid nasty problem with snapping through geometry onto rails
|
||
|
if (GetState() != AIR && !override_air) return;
|
||
|
|
||
|
// K: Don't allow a grind trick if bailing, otherwise you can late-trick into a liptrick, bail, get snapped into a grind, & hence bail again.
|
||
|
if (GetFlag(IS_BAILING)) return;
|
||
|
|
||
|
// K: No grinds or lip tricks are allowed if this flag is set. This is switched on and off by the NoGrind and AllowGrind script commands.
|
||
|
// These are used when jumping out of a lip trick to disallow going straight into a grind.
|
||
|
if (mNoRailTricks) return;
|
||
|
|
||
|
if (!mp_input_component->GetControlPad().m_triangle.GetPressed())
|
||
|
{
|
||
|
// Mick: not sure why this was removed
|
||
|
// SetFlagTrue(CAN_RERAIL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// don't grind for a short duration after a wallplant
|
||
|
if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < static_cast< Tmr::Time >(Script::GetInteger(CRCD(0x96dca7dc,"Physics_Wallplant_Disallow_Grind_Duration")))) return;
|
||
|
|
||
|
Mth::Vector a = GetOldPos();
|
||
|
Mth::Vector b = GetPos();
|
||
|
|
||
|
// if we were on a rail recently, and in the park editor, then don't let us snap to rails that are very perpendicular to us for a while
|
||
|
float min_dot;
|
||
|
if (Ed::CParkEditor::Instance()->UsingCustomPark() && Tmr::ElapsedTime(m_rail_time) < m_rerail_time)
|
||
|
{
|
||
|
// Should only do this if we've recently been on a rail
|
||
|
min_dot = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle"))));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
min_dot = 1.0f;
|
||
|
}
|
||
|
|
||
|
// eventually we should get the rail manager base on whatever rail we are on
|
||
|
CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager();
|
||
|
|
||
|
// first check the world rail manager (rails that do not move)
|
||
|
CRailNode* pNode;
|
||
|
Mth::Vector rail_pos;
|
||
|
if (p_rail_man->StickToRail(a, b, &rail_pos, &pNode, NULL, min_dot))
|
||
|
{
|
||
|
TrackingLine(2,GetOldPos(), GetPos()); // 2 = stick to rail
|
||
|
|
||
|
mp_movable_contact_component->LoseAnyContact();
|
||
|
if (will_take_rail(pNode, p_rail_man))
|
||
|
{
|
||
|
got_rail(rail_pos, pNode, p_rail_man);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// iterate through all rail manager components, starting with the first one
|
||
|
for (CRailManagerComponent* p_railmanager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
|
||
|
p_railmanager_component;
|
||
|
p_railmanager_component = static_cast< CRailManagerComponent* >(p_railmanager_component->GetNextSameType()))
|
||
|
{
|
||
|
CRailManager* p_rail_man = p_railmanager_component->GetRailManager();
|
||
|
|
||
|
Mth::Matrix total_mat = p_railmanager_component->UpdateRailManager();
|
||
|
Mth::Matrix inv = total_mat;
|
||
|
inv.Invert();
|
||
|
|
||
|
a[W] = 1.0f;
|
||
|
b[W] = 1.0f;
|
||
|
|
||
|
// Transform a,b into the space of the object
|
||
|
Mth::Vector local_a = inv.Transform(a);
|
||
|
Mth::Vector local_b = inv.Transform(b);
|
||
|
|
||
|
if (p_rail_man->StickToRail(local_a, local_b, &rail_pos, &pNode, NULL, min_dot))
|
||
|
{
|
||
|
// transform from object space to world space
|
||
|
rail_pos[W] = 1.0f;
|
||
|
rail_pos = total_mat.Transform(rail_pos);
|
||
|
if (will_take_rail(pNode, p_rail_man))
|
||
|
{
|
||
|
got_rail(rail_pos, pNode, p_rail_man);
|
||
|
mp_movable_contact_component->ObtainContact(p_railmanager_component->GetObject());
|
||
|
|
||
|
GetVel() -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::will_take_rail ( const CRailNode* pNode, CRailManager* p_rail_man, bool from_walk )
|
||
|
{
|
||
|
// no grinding if we're in an acid drop which was generated from an ollie out of a grind; prevents acid-grind-acid-grind repetition
|
||
|
if (GetState() == AIR && GetFlag(IN_ACID_DROP) && GetFlag(OLLIED_FROM_RAIL)
|
||
|
&& (mp_rail_node == pNode || mp_rail_node == pNode->GetNextLink() || mp_rail_node == pNode->GetPrevLink()))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// if it's a different rail, then allow rerailing
|
||
|
if (!mp_rail_node // if there was no last rail
|
||
|
|
||
|
// Dan: removed this line to prevent occasional rerailing when you grind off the end of a car; why was it here in the first place?
|
||
|
// || (mp_rail_man && mp_rail_man->IsMoving() && !mp_movable_contact_component->HaveContact()) // or last rail was movable, and we've lost contact
|
||
|
|
||
|
|| mp_rail_node != pNode // or not same segment
|
||
|
&& mp_rail_node != pNode->GetNextLink() // or the one before
|
||
|
&& mp_rail_node != pNode->GetPrevLink()) // of the one after
|
||
|
{
|
||
|
if (mp_rail_node && Ed::CParkEditor::Instance()->UsingCustomPark())
|
||
|
{
|
||
|
Dbg_Assert(mp_rail_man);
|
||
|
|
||
|
// in park editor rail must also not share end points to be considered different
|
||
|
Mth::Vector a = mp_rail_man->GetPos(mp_rail_node) - mp_rail_man->GetPos(pNode);
|
||
|
Mth::Vector b = mp_rail_man->GetPos(mp_rail_node) - mp_rail_man->GetPos(pNode->GetNextLink());
|
||
|
Mth::Vector c = mp_rail_man->GetPos(mp_rail_node->GetNextLink()) - mp_rail_man->GetPos(pNode);
|
||
|
Mth::Vector d = mp_rail_man->GetPos(mp_rail_node->GetNextLink()) - mp_rail_man->GetPos(pNode->GetNextLink());
|
||
|
|
||
|
if (Mth::Abs(a.Length()) > 6.0f
|
||
|
&& Mth::Abs(b.Length()) > 6.0f
|
||
|
&& Mth::Abs(c.Length()) > 6.0f
|
||
|
&& Mth::Abs(d.Length()) > 6.0f)
|
||
|
{
|
||
|
SetFlagTrue(CAN_RERAIL);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagTrue(CAN_RERAIL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (GetFlag(CAN_RERAIL) || Tmr::ElapsedTime(m_rail_time) > m_rerail_time)
|
||
|
&& (from_walk
|
||
|
|| (GetState() != RAIL // not already on a rail
|
||
|
&& (!GetFlag(TRACKING_VERT) || GetVel()[Y] > 0.0f))); // must be not vert, or going up
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::got_rail ( const Mth::Vector& rail_pos, const CRailNode* pNode, CRailManager* p_rail_man, bool no_lip_tricks, bool from_walk )
|
||
|
{
|
||
|
// got a point on rail, with start node number from a particular rail manager
|
||
|
// use this info to start grinding or lipping on the rail
|
||
|
|
||
|
CControlPad& control_pad = mp_input_component->GetControlPad();
|
||
|
|
||
|
CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_flip_and_rotate_component);
|
||
|
p_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters();
|
||
|
|
||
|
|
||
|
// Mick - landed on a rail
|
||
|
// if it's a "new" rail, then tell the robot detector about it
|
||
|
if (mp_rail_node != pNode)
|
||
|
{
|
||
|
int rail_index = p_rail_man->GetNodeIndex(pNode);
|
||
|
mp_score_component->GetScore()->UpdateRobotDetection(rail_index);
|
||
|
}
|
||
|
|
||
|
mp_rail_node = pNode;
|
||
|
mp_rail_man = p_rail_man;
|
||
|
|
||
|
// handle single node rails
|
||
|
if (!pNode->GetPrevLink() && !pNode->GetNextLink())
|
||
|
{
|
||
|
// for a single node rail, we apply in a single frame all the effects of entering and exiting the rail state;
|
||
|
|
||
|
/////////////////////////////////////////////////////
|
||
|
// Emulate entering the rail state (with horizontal dir direction)
|
||
|
|
||
|
// check for collision in moving from m_pos to rail_pos
|
||
|
|
||
|
m_col_start = GetPos();
|
||
|
m_col_end = rail_pos;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
// check distance from the rail to the collision point
|
||
|
if ((rail_pos - m_feeler.GetPoint()).LengthSqr() > 6.0f * 6.0f)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check first if we are not moving much in the XY and if not, then se the XZ velocity to the matrix[Z], so we always go forward
|
||
|
if (Mth::Abs(GetVel()[X]) < 0.01f && Mth::Abs(GetVel()[Z]) < 0.01f)
|
||
|
{
|
||
|
GetVel()[X] = GetMatrix()[Z][X];
|
||
|
GetVel()[Z] = GetMatrix()[Z][Z];
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
if (!from_walk && GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z] > 10.0f * 10.0f)
|
||
|
{
|
||
|
GetVel().RotateToPlane(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
// rail direction is taken to always simply be along our horizontal velocity, rotated up
|
||
|
Mth::Vector dir = GetVel();
|
||
|
dir[Y] = 0.0f;
|
||
|
dir.Normalize();
|
||
|
float angle = Mth::DegToRad(Script::GetFloat(CRCD(0xbb357ecb,"Physics_Point_Rail_Kick_Upward_Angle")));
|
||
|
float c = cosf(angle);
|
||
|
float s = sinf(angle);
|
||
|
Mth::Vector boost_dir(c * dir[X], s, c * dir[Z]);
|
||
|
|
||
|
// get the rail node name
|
||
|
Script::CArray* pNodeArray = mp_rail_man->GetNodeArray();
|
||
|
Script::CStruct* pNode = pNodeArray->GetStructure(mp_rail_node->GetNode());
|
||
|
pNode->GetChecksum(CRCD(0xa1dc81f9, "Name"), &m_last_rail_node_name, Script::ASSERT);
|
||
|
|
||
|
mp_trick_component->TrickOffObject(m_last_rail_node_name);
|
||
|
|
||
|
// Now we want to see if the rail has a trigger, and if it does, trigger it....
|
||
|
|
||
|
uint32 trigger_script = 0;
|
||
|
|
||
|
// no need to call maybe_trip_rail_trigger for a single node rail
|
||
|
if (pNode->GetChecksum(CRCD(0x2ca8a299, "TriggerScript"), &trigger_script))
|
||
|
{
|
||
|
mp_trigger_component->TripTrigger(
|
||
|
TRIGGER_LAND_ON,
|
||
|
m_last_rail_node_name,
|
||
|
pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")) ? NULL : pNodeArray,
|
||
|
mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
|
||
|
);
|
||
|
}
|
||
|
|
||
|
GetPos() = rail_pos;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Now we'v got onto the rail, we need to:
|
||
|
// 1) kill velocity perpendicular to the rail
|
||
|
// 2) add a speed boost in the direction we are going.
|
||
|
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
|
||
|
// if we are transitioning from wall to rail, then snap him upright
|
||
|
if (GetState() == WALL)
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
SetState(RAIL);
|
||
|
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
|
||
|
|
||
|
float old_y = GetVel()[Y];
|
||
|
GetVel().ProjectToNormal(dir); // kill perp velocity
|
||
|
if (from_walk && Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f)
|
||
|
{
|
||
|
if (GetVel().LengthSqr() < Mth::Sqr(1.1f * mp_walk_component->s_get_param(CRCD(0x896c8888, "jump_adjust_speed"))))
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
dir = GetMatrix()[Z];
|
||
|
dir[Y] = 0.0f;
|
||
|
dir.Normalize();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
GetVel()[Y] = old_y; // except for Y
|
||
|
|
||
|
GetVel() += dir * GetPhysicsFloat(CRCD(0xa3ef4833, "Point_Rail_Speed_Boost")); // add speed boost
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
|
||
|
// (Mick) Set m_rail_time, otherwise there is a single frame where it is invalid
|
||
|
// and this allows us to immediately re-rail and hence do the "insta-bail", since the triangle button will be held down
|
||
|
m_rail_time = Tmr::GetTime();
|
||
|
|
||
|
/////////////////////////////////////////////////////
|
||
|
// Emulate effects of rail state (with boost_dir direction)
|
||
|
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(IN_RECOVERY);
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
// set default rerail time
|
||
|
m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0x2b4645ad, "Rail_Minimum_Rerail_Time")));
|
||
|
// and dissalow any overriding of this
|
||
|
SetFlagFalse(CAN_RERAIL); // dont allow rerailing after coming off a segment
|
||
|
|
||
|
GetVel().RotateToNormal(boost_dir);
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
/////////////////////////////////////////////////////
|
||
|
// Emulate exiting the rail state
|
||
|
|
||
|
// no need to call maybe_trip_rail_trigger for a single node rail
|
||
|
if (trigger_script)
|
||
|
{
|
||
|
mp_trigger_component->TripTrigger(
|
||
|
TRIGGER_SKATE_OFF,
|
||
|
m_last_rail_node_name,
|
||
|
pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")) ? NULL : pNodeArray,
|
||
|
mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
|
||
|
);
|
||
|
}
|
||
|
|
||
|
SetState(AIR);
|
||
|
GetPos()[Y] += 1.0f;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
/////////////////////////////////////////////////////
|
||
|
// Do extra point rail logic
|
||
|
|
||
|
// trigger the appropriate script
|
||
|
GetObject()->SelfEvent(CRCD(0xb8048f1d, "PointRail"));
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Check for lip trick
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
float LipAllowSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x8157c5d9, "LipAllowAngle"))));
|
||
|
if (pNode->GetFlag(LIP_OVERRIDE))
|
||
|
{
|
||
|
LipAllowSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xf3b6f95e, "LipAllowAngle_Override"))));
|
||
|
}
|
||
|
float HorizSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xe2a85fbb, "LipPlayerHorizontalAngle"))));
|
||
|
float VertCos = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x61079462, "LipRampVertAngle"))));
|
||
|
|
||
|
if ((mAllowLipNoGrind // This flag makes lip tricks always happen, hooray!
|
||
|
|| (
|
||
|
GetVel()[Y] > 0.0f // going upwards
|
||
|
&& Mth::Abs(GetMatrix()[Y][Y]) < HorizSine // fairly horizontal skater
|
||
|
&& GetMatrix()[Z][Y] > 0.0f // Facing fairly up in the air
|
||
|
&& Mth::Abs(m_current_normal[Y]) < VertCos // last poly we were on was near vert
|
||
|
&& Mth::Abs(GetMatrix()[X][Y]) < LipAllowSine // skater pointing fairly straight up
|
||
|
)) && !no_lip_tricks)
|
||
|
{
|
||
|
// Reset a bunch of stuff.
|
||
|
GetVel().Set(0.0f, 0.0f, 0.0f);
|
||
|
DUMP_VELOCITY;
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
SetFlagFalse(LAST_POLY_WAS_VERT);
|
||
|
SetFlagFalse(CAN_BREAK_VERT);
|
||
|
// SetFlagFalse(LEAN);
|
||
|
|
||
|
// Make sure any other balance trick is stopped.
|
||
|
mp_balance_trick_component->stop_balance_trick();
|
||
|
|
||
|
m_pre_lip_pos = GetPos();
|
||
|
|
||
|
// Into the lip state
|
||
|
SetState(LIP);
|
||
|
|
||
|
// Snap the position
|
||
|
GetPos() = rail_pos;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Pop the skater horizontal
|
||
|
Mth::Vector u = m_current_normal;
|
||
|
u[Y] = 0.0f;
|
||
|
u.Normalize();
|
||
|
GetMatrix()[Y] = u;
|
||
|
|
||
|
GetMatrix()[Z].Set(0.0f, 1.0f, 0.0f);
|
||
|
|
||
|
#if 0 // old code
|
||
|
// GetMatrix()[X] = Mth::CrossProduct(m_matrix[Y],m_matrix[Z]);
|
||
|
// GetMatrix()[X].Normalize();
|
||
|
#else
|
||
|
GetMatrix()[X].Set(
|
||
|
-GetMatrix()[Y][Z],
|
||
|
0.0f,
|
||
|
GetMatrix()[Y][X],
|
||
|
0.0f
|
||
|
);
|
||
|
#endif
|
||
|
|
||
|
ResetLerpingMatrix();
|
||
|
|
||
|
// Update the "Require_Lip" flag so lip only gaps don't need to wait on frame
|
||
|
GetSkaterGapComponentFromObject(GetObject())->UpdateCancelRequire(0,REQUIRE_LIP);
|
||
|
|
||
|
|
||
|
// Trigger the lip on event.
|
||
|
maybe_trip_rail_trigger(TRIGGER_LIP_ON);
|
||
|
|
||
|
mp_trick_component->mUseSpecialTrickText = false;
|
||
|
|
||
|
if (!GetObject()->GetScript())
|
||
|
{
|
||
|
GetObject()->SetScript(new Script::CScript);
|
||
|
}
|
||
|
|
||
|
// Run the lip script.
|
||
|
GetObject()->GetScript()->SetScript(CRCD(0x1647cf96, "LipTrick"), NULL, GetObject());
|
||
|
GetObject()->GetScript()->Update();
|
||
|
|
||
|
// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
|
||
|
mp_state_component->SetDoingTrick(true);
|
||
|
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// no lip trick, so we rail
|
||
|
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
|
||
|
const CRailNode* pStart = mp_rail_node;
|
||
|
const CRailNode* pEnd = pStart->GetNextLink();
|
||
|
|
||
|
Mth::Vector dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
|
||
|
dir.Normalize();
|
||
|
|
||
|
// Now, we've get a rail
|
||
|
|
||
|
// check for collision in moving from m_pos to rail_pos
|
||
|
|
||
|
m_col_start = GetPos();
|
||
|
m_col_end = rail_pos;
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
// check distance from the rail to the collision point
|
||
|
if ((rail_pos - m_feeler.GetPoint()).LengthSqr() > 6.0f * 6.0f)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (GetFlag(IN_ACID_DROP))
|
||
|
{
|
||
|
MESSAGE("GRINDING FROM ACID DROP");
|
||
|
DUMPV(acid_hold);
|
||
|
DUMP_VELOCITY;
|
||
|
GetVel() += acid_hold;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
// we need to check if this is the end of the rail and we are going to come off it next frame, then we don't want to get on it....
|
||
|
|
||
|
// check first if we are not moving much in the XY and if not, then se the XZ velocity to the matrix[Z], so we always go forward
|
||
|
if (GetVel()[X] == 0.0f && GetVel()[Z] == 0.0f)
|
||
|
{
|
||
|
GetVel()[X] = GetMatrix()[Z][X];
|
||
|
GetVel()[Z] = GetMatrix()[Z][Z];
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////
|
||
|
// Mick: Start of patch
|
||
|
// (For Oil Rig type problem with steep rails)
|
||
|
// if we are moving forward, then rotate velocity onto a horizontal plane
|
||
|
// so we don't seem to be going backwards up steep rails when we jump onto them
|
||
|
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector flat_vel = GetVel();
|
||
|
// flat_vel[Y] = 0;
|
||
|
// if (flat_vel.Length() > 10.0f)
|
||
|
// {
|
||
|
// GetVel().RotateToPlane(Mth::Vector(0.0f,1.0f,0.0f));
|
||
|
//}
|
||
|
#else
|
||
|
if (!from_walk && GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z] > 10.0f * 10.0f)
|
||
|
{
|
||
|
GetVel().RotateToPlane(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Mick: end of patch
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
float dot = Mth::DotProduct(dir, GetVel());
|
||
|
|
||
|
// sign is which way we are going along the rail
|
||
|
float sign;
|
||
|
if (dot == 0.0f)
|
||
|
{
|
||
|
// if the dot product can not determine the direction (pull up from hangs), choose randomly
|
||
|
sign = Mth::Rnd(2) ? 1.0f : -1.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sign = Mth::Sgn(dot);
|
||
|
}
|
||
|
|
||
|
if (sign < 0.0f)
|
||
|
{
|
||
|
// will be going backwards along the rail
|
||
|
|
||
|
// if the rail stick point is this last point, then we don't want to snap to it as we will immediatly fly off,
|
||
|
// and the point might be coincident with a wall or the ground and that will cause problems
|
||
|
if (!pStart->GetPrevLink() && mp_rail_man->GetPos(pStart) == rail_pos) return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// same for going forward along the rail
|
||
|
if (!pEnd->GetNextLink() && mp_rail_man->GetPos(pEnd) == rail_pos) return;
|
||
|
}
|
||
|
|
||
|
// Now we want to see if the rail has a trigger, and if it does, trigger it....
|
||
|
maybe_trip_rail_trigger(TRIGGER_LAND_ON);
|
||
|
|
||
|
GetPos() = rail_pos;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Now we'v got onto the rail, we need to:
|
||
|
// 1) kill velocity perpendicular to the rail
|
||
|
// 2) add a speed boost in the direction we are going.
|
||
|
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
SetFlagFalse(TRACKING_VERT);
|
||
|
|
||
|
// if we are transitioning from wall to rail, then snap him upright
|
||
|
if (GetState() == WALL)
|
||
|
{
|
||
|
new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
|
||
|
ResetLerpingMatrix();
|
||
|
}
|
||
|
|
||
|
SetState(RAIL);
|
||
|
|
||
|
// don't play the rail sound when coming onto a rail from walking
|
||
|
if (Tmr::ElapsedTime(mp_physics_control_component->GetStateSwitchTime()) != 0)
|
||
|
{
|
||
|
// play sound based on pre-rail velocity
|
||
|
mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
|
||
|
}
|
||
|
|
||
|
float old_y = GetVel()[Y];
|
||
|
GetVel().ProjectToNormal(dir); // kill perp velocity
|
||
|
if (from_walk && Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f)
|
||
|
{
|
||
|
if (GetVel().LengthSqr() < Mth::Sqr(1.1f * mp_walk_component->s_get_param(CRCD(0x896c8888, "jump_adjust_speed"))))
|
||
|
{
|
||
|
GetVel().Set();
|
||
|
sign = Mth::Sgn(Mth::DotProduct(dir, GetMatrix()[Z]));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
GetVel()[Y] = old_y; // except for Y
|
||
|
|
||
|
GetVel() += dir * sign * GetPhysicsFloat(CRCD(0x457bb395, "Rail_Speed_Boost")); // add speed boost
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// Ken stuff ...
|
||
|
bool Rail_RightOfRail = false;
|
||
|
bool Rail_Parallel = false;
|
||
|
mRail_Backwards = false;
|
||
|
|
||
|
if (sign < 0.0f)
|
||
|
{
|
||
|
dir = -dir;
|
||
|
}
|
||
|
// dir is now the unit vector pointing along the rail in the direction we will be moving on the rail
|
||
|
|
||
|
// Calculate which side of the rail we're on
|
||
|
#if 0 // old code
|
||
|
// Mth::Vector d = rail_pos - m_last_ground_pos;
|
||
|
// Mth::Vector side_vel(d.GetZ(), 0.0f, -d.GetX());
|
||
|
// if (Mth::DotProduct(dir, side_vel) < 0.0f)
|
||
|
// {
|
||
|
// Rail_RightOfRail = true;
|
||
|
// }
|
||
|
#else
|
||
|
float side_vel_X = rail_pos[Z] - m_last_ground_pos[Z];
|
||
|
float side_vel_Z = -(rail_pos[X] - m_last_ground_pos[X]);
|
||
|
if (dir[X] * side_vel_X + dir[Z] * side_vel_Z < 0.0f)
|
||
|
{
|
||
|
Rail_RightOfRail = true;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
// Bad ledge detection
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Calculate the up and down offsets for collision test.
|
||
|
m_col_start.Set(0.0f, GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up")), 0.0f, 0.0f);
|
||
|
m_col_end.Set(0.0f, -GetPhysicsFloat(CRCD(0x9cd7ed5c, "Rail_Bad_Ledge_Drop_Down_Dist")), 0.0f, 0.0f);
|
||
|
|
||
|
// Add the rail pos, so start and end are now above and below the rail.
|
||
|
m_col_start += rail_pos;
|
||
|
m_col_end += rail_pos;
|
||
|
|
||
|
// Calculate a side offset, using the rail direction rotated 90 degrees.
|
||
|
Mth::Vector Off(dir[Z], 0.0f, -dir[X], 0.0f);
|
||
|
Off *= GetPhysicsFloat(CRCD(0x31669752, "Rail_Bad_Ledge_Side_Dist"));
|
||
|
|
||
|
// Add the offset and do the left collision.
|
||
|
m_col_start += Off;
|
||
|
m_col_end += Off;
|
||
|
bool LeftCollision = get_member_feeler_collision();
|
||
|
|
||
|
// Move across to the other side and do the right collision.
|
||
|
m_col_start -= 2.0f * Off;
|
||
|
m_col_end -= 2.0f * Off;
|
||
|
bool RightCollision = get_member_feeler_collision();
|
||
|
|
||
|
// Bit of logic to get whether it's a bad ledge or not.
|
||
|
mBadLedge = (LeftCollision && !Rail_RightOfRail) || (RightCollision && Rail_RightOfRail);
|
||
|
mLedge = LeftCollision || RightCollision;
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
float RightDot = Mth::DotProduct(dir, GetMatrix()[X]);
|
||
|
float FrontDot = Mth::DotProduct(dir, GetMatrix()[Z]);
|
||
|
|
||
|
if (Mth::Abs(RightDot) < GetPhysicsFloat(CRCD(0xd0688d4e, "Rail_Tolerance")))
|
||
|
{
|
||
|
// The skater's right vector is close to perpendicular to the rail, so the skater must be pointing fairly parallel to it.
|
||
|
Rail_Parallel = true;
|
||
|
|
||
|
// Use the front vector to determine forwards/backwards, since the front vector is fairly parallel to the rail.
|
||
|
if (FrontDot < 0.0f)
|
||
|
{
|
||
|
mRail_Backwards = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (GetFlag(FLIPPED))
|
||
|
{
|
||
|
RightDot = -RightDot;
|
||
|
}
|
||
|
|
||
|
// The skater's right vector is fairly parallel to the rail, so use it to determine forwards/backwards.
|
||
|
if (RightDot < 0.0f)
|
||
|
{
|
||
|
mRail_Backwards = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mp_trick_component->mUseSpecialTrickText = false;
|
||
|
|
||
|
if (!mp_trick_component->TriggerAnyExtraGrindTrick(
|
||
|
Rail_RightOfRail,
|
||
|
Rail_Parallel,
|
||
|
mRail_Backwards,
|
||
|
GetFlag(FLIPPED))
|
||
|
)
|
||
|
{
|
||
|
do_grind_trick(CSkaterPad::sGetDirection(
|
||
|
control_pad.m_up.GetPressed(),
|
||
|
control_pad.m_down.GetPressed(),
|
||
|
control_pad.m_left.GetPressed(),
|
||
|
control_pad.m_right.GetPressed()
|
||
|
), Rail_RightOfRail, Rail_Parallel, mRail_Backwards, GetFlag(FLIPPED));
|
||
|
}
|
||
|
|
||
|
// (Mick) Set m_rail_time, otherwise there is a single frame where it is invalid
|
||
|
// and this allows us to immediately re-rail and hence do the "insta-bail", since the triangle button will be held down
|
||
|
m_rail_time = Tmr::GetTime();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_lip_physics ( )
|
||
|
{
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(IN_RECOVERY);
|
||
|
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos()))
|
||
|
{
|
||
|
GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// this is close to correct, 'cept that but because of rotation, it's not quite right
|
||
|
m_pre_lip_pos += mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
|
||
|
if (mp_movable_contact_component->GetContact()->IsRotated())
|
||
|
{
|
||
|
|
||
|
GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef __USER_DAN__
|
||
|
if (Script::GetInteger(CRCD(0x1a5eab7, "rail_highlights")))
|
||
|
{
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node), mp_rail_man->GetPos(mp_rail_node->GetNextLink()), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(1.0f, 0.0f, 0.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(1.0f, 0.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(0.0f, 1.0f, 0.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(0.0f, 0.0f, 1.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(0.0f, 0.0f, 1.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Update any balance control required.
|
||
|
if (mp_balance_trick_component->mBalanceTrickType == CRCD(0xa549b57b, "Lip"))
|
||
|
{
|
||
|
if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
|
||
|
{
|
||
|
// Here, set the flag. It may seem redundant, but the above line is very likely
|
||
|
// to be hacked by gameshark. They probably won't notice this one, which will
|
||
|
// set the flags as if they had actually enabled the cheat -- which enables us
|
||
|
// to detect that it has been turned on more easily.
|
||
|
Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")));
|
||
|
mp_balance_trick_component->mLip.mManualLean = 0.0f;
|
||
|
mp_balance_trick_component->mLip.mManualLeanDir = 0.0f;
|
||
|
g_CheatsEnabled = true;
|
||
|
}
|
||
|
mp_balance_trick_component->mLip.DoManualPhysics();
|
||
|
}
|
||
|
|
||
|
handle_tensing();
|
||
|
if (maybe_flag_ollie_exception())
|
||
|
{
|
||
|
// Trigger the lip jump event.
|
||
|
maybe_trip_rail_trigger(TRIGGER_LIP_JUMP);
|
||
|
}
|
||
|
|
||
|
// lip tricks are very rail-like when determining which world sector you've just tricked off of
|
||
|
if (mp_rail_node)
|
||
|
{
|
||
|
if (mp_rail_man->GetNodeArray())
|
||
|
{
|
||
|
const CRailNode* pRail = mp_rail_node;
|
||
|
Script::CArray* pNodeArray = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
|
||
|
Script::CStruct* pNode = pNodeArray->GetStructure(pRail->GetNode());
|
||
|
pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &m_last_rail_node_name, Script::ASSERT);
|
||
|
mp_trick_component->TrickOffObject(m_last_rail_node_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_lip_time = Tmr::GetTime();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_rail_physics ( )
|
||
|
{
|
||
|
SetFlagFalse(SPINE_PHYSICS);
|
||
|
SetFlagFalse(IN_ACID_DROP);
|
||
|
SetFlagFalse(IN_RECOVERY);
|
||
|
SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
|
||
|
|
||
|
// First of all we apply the movement due to contact; best to do it first, as then we will be on the rail we are moving along
|
||
|
|
||
|
if (mp_movable_contact_component->HaveContact())
|
||
|
{
|
||
|
// need to update the transform of the rail manager
|
||
|
// no need to do this
|
||
|
CRailManagerComponent* p_rail_manager_component = GetRailManagerComponentFromObject(mp_movable_contact_component->GetContact()->GetObject());
|
||
|
Dbg_Assert(p_rail_manager_component);
|
||
|
|
||
|
p_rail_manager_component->UpdateRailManager();
|
||
|
|
||
|
if (mp_movable_contact_component->UpdateContact(GetPos()))
|
||
|
{
|
||
|
|
||
|
GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
|
||
|
DUMP_POSITION;
|
||
|
if (mp_movable_contact_component->GetContact()->IsRotated())
|
||
|
{
|
||
|
GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set default rerail time
|
||
|
m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0x2b4645ad, "Rail_minimum_rerail_time")));
|
||
|
// and dissalow any overriding of this
|
||
|
SetFlagFalse(CAN_RERAIL); // dont allow rerailing after coming off a segment
|
||
|
|
||
|
|
||
|
if (maybe_flag_ollie_exception())
|
||
|
{
|
||
|
maybe_trip_rail_trigger(TRIGGER_JUMP_OFF);
|
||
|
|
||
|
m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0xbf35053, "Rail_jump_rerail_time")));
|
||
|
|
||
|
SetFlagTrue(OLLIED_FROM_RAIL);
|
||
|
|
||
|
// when we jump off a rail, then raise him up one inch, so we don't collide with the top of a fence or something
|
||
|
GetPos()[Y] += 1.0f; // up one inch......
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
switch (mp_balance_trick_component->mBalanceTrickType)
|
||
|
{
|
||
|
case 0:
|
||
|
case 0x0ac90769: // NoseManual
|
||
|
case 0xef24413b: // Manual
|
||
|
case 0xa549b57b: // Lip
|
||
|
break;
|
||
|
|
||
|
case 0x255ed86f: // Grind
|
||
|
case 0x8d10119d: // Slide
|
||
|
mp_balance_trick_component->mGrind.DoManualPhysics();
|
||
|
if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
|
||
|
{
|
||
|
// Here, set the flag. It may seem redundant, but the above line is very likely
|
||
|
// to be hacked by gameshark. They probably won't notice this one, which will
|
||
|
// set the flags as if they had actually enabled the cheat -- which enables us
|
||
|
// to detect that it has been turned on more easily.
|
||
|
Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")));
|
||
|
mp_balance_trick_component->mGrind.mManualLean = 0.0f;
|
||
|
mp_balance_trick_component->mGrind.mManualLeanDir = 0.0f;
|
||
|
g_CheatsEnabled = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Dbg_Assert(false);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Get the rail segments
|
||
|
|
||
|
const CRailNode* pStart = mp_rail_node;
|
||
|
const CRailNode* pEnd = pStart->GetNextLink();
|
||
|
|
||
|
const CRailNode* pFrom = pStart;
|
||
|
const CRailNode* pOnto = NULL;
|
||
|
|
||
|
#ifdef __USER_DAN__
|
||
|
if (Script::GetInteger(CRCD(0x1a5eab7, "rail_highlights")))
|
||
|
{
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(pStart), mp_rail_man->GetPos(pEnd), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(1.0f, 0.0f, 0.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(1.0f, 0.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(0.0f, 1.0f, 0.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(0.0f, 0.0f, 1.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(0.0f, 0.0f, 1.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
Mth::Vector dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
|
||
|
float segment_length = dir.Length();
|
||
|
dir *= 1.0f / segment_length;
|
||
|
|
||
|
// sign is which way we are going along the rail
|
||
|
float old_sign = Mth::Sgn(Mth::DotProduct(dir, GetVel()));
|
||
|
|
||
|
// Get gravity force
|
||
|
Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0xd1f46992, "Physics_Rail_Gravity")), 0.0f);
|
||
|
|
||
|
// Project gravity onto the line we are on
|
||
|
gravity.ProjectToNormal(dir);
|
||
|
GetVel() += gravity * m_frame_length;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// sign is which way we are going along the rail
|
||
|
float sign = Mth::Sgn(Mth::DotProduct(dir, GetVel()));
|
||
|
|
||
|
// sign might have changed here
|
||
|
// so could do the "flipping on a rail" thing
|
||
|
// .................
|
||
|
|
||
|
if (sign != old_sign)
|
||
|
{
|
||
|
// Note, we JUST flip the "Backwards" flag, as we want to stay in essentailly the same
|
||
|
// pose as before, and as the sign of our velocity dotted with the rail has
|
||
|
// changed, then all we need to to is tell the code we are
|
||
|
// going in the opposite direction to what we were going before, and the
|
||
|
// target vector will be calculated correctly.
|
||
|
mRail_Backwards = !mRail_Backwards;
|
||
|
}
|
||
|
|
||
|
// check to see if we are on the last segment of the rail
|
||
|
// this logic could be folded into the logic below but it's perhps clearer to have it here
|
||
|
bool last_segment = false;
|
||
|
if (sign < 0.0f)
|
||
|
{
|
||
|
if (pStart->GetPrevLink() && pStart->GetPrevLink()->IsActive())
|
||
|
{
|
||
|
Mth::Vector v1, v2;
|
||
|
v1 = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
|
||
|
v2 = mp_rail_man->GetPos(pStart) - mp_rail_man->GetPos(pStart->GetPrevLink());
|
||
|
v1[Y] = 0.0f;
|
||
|
v2[Y] = 0.0f;
|
||
|
v1.Normalize();
|
||
|
v2.Normalize();
|
||
|
float dot = Mth::DotProduct(v1, v2);
|
||
|
if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle")))))
|
||
|
{
|
||
|
// there is more, but angle is too sharp
|
||
|
last_segment = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pOnto = pStart->GetPrevLink();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
last_segment = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (pEnd && pEnd->GetNextLink() && pEnd->GetNextLink()->IsActive())
|
||
|
{
|
||
|
Mth::Vector v1,v2;
|
||
|
v1 = mp_rail_man->GetPos(pStart) - mp_rail_man->GetPos(pEnd);
|
||
|
v2 = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pEnd->GetNextLink());
|
||
|
v1[Y] = 0.0f;
|
||
|
v2[Y] = 0.0f;
|
||
|
v1.Normalize();
|
||
|
v2.Normalize();
|
||
|
float dot = Mth::DotProduct(v1,v2);
|
||
|
if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15,"Rail_Corner_Leave_Angle")))))
|
||
|
{
|
||
|
// there is more, but angle is too sharp
|
||
|
last_segment = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pOnto = pEnd;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
last_segment = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now we need to see if we have gone beyond the end of the rail segment
|
||
|
|
||
|
float extra_dist = 0.0f;
|
||
|
|
||
|
float length_along;
|
||
|
if (sign < 0.0f)
|
||
|
{
|
||
|
// going backwards, so it's the distance from the end of the segment
|
||
|
length_along = (GetPos() - mp_rail_man->GetPos(pEnd)).Length();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// going forwards, so it's the distance from the start
|
||
|
length_along = (GetPos() - mp_rail_man->GetPos(pStart)).Length();
|
||
|
}
|
||
|
|
||
|
if (length_along > segment_length + 0.1f) // 0.1 inch, so we don't get stuck
|
||
|
{
|
||
|
// remember this, so we can move along next segment
|
||
|
extra_dist = length_along - segment_length;
|
||
|
|
||
|
// gone off the end of the segment
|
||
|
if (sign < 0.0f)
|
||
|
{
|
||
|
if (pStart->GetPrevLink()
|
||
|
&& pStart->GetPrevLink()->IsActive()
|
||
|
&& Rail_ValidInEditor(mp_rail_man->GetPos(pStart), mp_rail_man->GetPos(pStart->GetPrevLink())))
|
||
|
{
|
||
|
if (!last_segment)
|
||
|
{
|
||
|
// go onto previous segment
|
||
|
GetPos() = mp_rail_man->GetPos(pStart);
|
||
|
DUMP_POSITION;
|
||
|
mp_rail_node = pStart->GetPrevLink();
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
skate_off_rail(mp_rail_man->GetPos(pStart));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
skate_off_rail(mp_rail_man->GetPos(pStart));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (pEnd->GetNextLink()
|
||
|
&& pEnd->IsActive()
|
||
|
&& Rail_ValidInEditor(mp_rail_man->GetPos(pEnd), mp_rail_man->GetPos(pEnd->GetNextLink())))
|
||
|
{
|
||
|
if (!last_segment)
|
||
|
{
|
||
|
GetPos() = mp_rail_man->GetPos(pEnd);
|
||
|
DUMP_POSITION;
|
||
|
mp_rail_node = pEnd;
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
skate_off_rail(mp_rail_man->GetPos(pEnd));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
skate_off_rail(mp_rail_man->GetPos(pEnd));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (GetState() == RAIL)
|
||
|
{
|
||
|
// recalculate start, end, dir, as we might be on a new segment
|
||
|
const CRailNode* pStart = mp_rail_node;
|
||
|
const CRailNode* pEnd = pStart->GetNextLink();
|
||
|
|
||
|
Mth::Vector dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
|
||
|
dir.Normalize();
|
||
|
|
||
|
// sign also may have changed, now that we are auto-linking rail segments
|
||
|
|
||
|
// sign is which way we are going along the rail
|
||
|
float sign = Mth::Sgn(Mth::DotProduct(dir,GetVel()));
|
||
|
|
||
|
m_rail_time = Tmr::GetTime();
|
||
|
|
||
|
GetVel().RotateToNormal(dir);
|
||
|
GetVel() *= sign; // sign won't be on a new segment
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
float facing_sign = mRail_Backwards ? -sign : sign;
|
||
|
|
||
|
// z is forward
|
||
|
Mth::Vector target_forward = dir * facing_sign;
|
||
|
|
||
|
m_lerping_display_matrix[Z] = Mth::Lerp(m_lerping_display_matrix[Z], target_forward, 0.3f);
|
||
|
m_lerping_display_matrix[Z].Normalize();
|
||
|
|
||
|
#if 0 // old code
|
||
|
// m_lerping_display_matrix[Y].Set(0.0f, 1.0f, 0.0f);
|
||
|
// m_lerping_display_matrix[X] = Mth::CrossProduct(
|
||
|
// m_lerping_display_matrix[Y],
|
||
|
// m_lerping_display_matrix[Z]
|
||
|
// );
|
||
|
#else
|
||
|
m_lerping_display_matrix[X].Set(
|
||
|
m_lerping_display_matrix[Z][Z],
|
||
|
0.0f,
|
||
|
-m_lerping_display_matrix[Z][X],
|
||
|
0.0f
|
||
|
);
|
||
|
#endif
|
||
|
m_lerping_display_matrix[X].Normalize();
|
||
|
|
||
|
m_lerping_display_matrix[Y] = Mth::CrossProduct(
|
||
|
m_lerping_display_matrix[Z],
|
||
|
m_lerping_display_matrix[X]
|
||
|
);
|
||
|
m_lerping_display_matrix[Y].Normalize();
|
||
|
|
||
|
// adjust our Z value towards the new value
|
||
|
GetMatrix()[Z] = target_forward;
|
||
|
GetMatrix()[Z].Normalize();
|
||
|
|
||
|
#if 0 // old code
|
||
|
// GetMatrix()[Y].Set(0.0f, 1.0f, 0.0f);
|
||
|
// GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y],GetMatrix()[Z]);
|
||
|
#else
|
||
|
GetMatrix()[X].Set(
|
||
|
GetMatrix()[Z][Z],
|
||
|
0.0f,
|
||
|
-GetMatrix()[Z][X],
|
||
|
0.0f
|
||
|
);
|
||
|
#endif
|
||
|
GetMatrix()[X].Normalize();
|
||
|
|
||
|
GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
|
||
|
GetMatrix()[Y].Normalize();
|
||
|
|
||
|
Mth::Vector m_movement(0.0f, 0.0f, 0.0f);
|
||
|
|
||
|
// This is where we do the actual movement
|
||
|
|
||
|
// if this makes us bump into the wall or the ground, then we should leave the rail
|
||
|
|
||
|
Mth::Vector old_pos = GetPos();
|
||
|
GetPos() += GetVel() * m_frame_length; // current movement
|
||
|
GetPos() += extra_dist * target_forward; // any extra dist from previous segment
|
||
|
GetPos() += m_movement; // movement due to contact with moving object
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// Mick: use "old_pos" to generate the direction
|
||
|
// so it is guarenteed to be parallel to the rail
|
||
|
// and m_pos might have been adjusted if we continued from
|
||
|
// one rail to another (in-line) rail, like in the park editor
|
||
|
Mth::Vector movement = GetPos() - old_pos;
|
||
|
Mth::Vector direction = movement;
|
||
|
direction.Normalize();
|
||
|
|
||
|
bool always_check = false;
|
||
|
if (!last_segment && Ed::CParkEditor::Instance()->UsingCustomPark())
|
||
|
{
|
||
|
// in the park editor we can have tight curves that end in walls, so we want to always do the forward check
|
||
|
// if we are on a segment that is horizontal, and leads to another segment that is also horizontal
|
||
|
Mth::Vector from = mp_rail_man->GetPos(pFrom->GetNextLink()) - mp_rail_man->GetPos(pFrom);
|
||
|
Mth::Vector onto = mp_rail_man->GetPos(pOnto->GetNextLink()) - mp_rail_man->GetPos(pOnto);
|
||
|
from.Normalize();
|
||
|
onto.Normalize();
|
||
|
float delta = Mth::Abs(from[Y] - onto[Y]);
|
||
|
if (delta < 0.01f)
|
||
|
{
|
||
|
// lines have a sufficently close Y angle
|
||
|
always_check = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only check for hitting a wall if we are on a segment of rail that has no more rail
|
||
|
if (last_segment || always_check)
|
||
|
{
|
||
|
m_col_start = old_pos;
|
||
|
m_col_end = GetPos();
|
||
|
m_col_end += movement + (direction * 6.0f);
|
||
|
|
||
|
// raise them up one inch, so we don't collide with the rail
|
||
|
m_col_start += GetMatrix()[Y];
|
||
|
m_col_end += GetMatrix()[Y];
|
||
|
if (get_member_feeler_collision())
|
||
|
{
|
||
|
// if in the park editor, then ignore collision with invisible surfaces
|
||
|
if (!Ed::CParkEditor::Instance()->UsingCustomPark() || !(m_feeler.GetFlags() & mFD_INVISIBLE))
|
||
|
{
|
||
|
maybe_trip_rail_trigger(TRIGGER_SKATE_OFF);
|
||
|
// don't let him make this movement!!
|
||
|
GetPos() = GetOldPos();
|
||
|
GetPos()[Y] += 1.0f;
|
||
|
DUMP_POSITION;
|
||
|
|
||
|
// project velocity along the plane if we run into a wall
|
||
|
GetVel().ProjectToPlane(m_feeler.GetNormal());
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
SetState(AIR); // knocked off rail, as something in front
|
||
|
FLAGEXCEPTION(CRCD(0xafaa46ba, "OffRail"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set the normal value, so the camera is not too confused
|
||
|
m_current_normal = GetMatrix()[Y];
|
||
|
|
||
|
// Add mGrindTweak to the score.
|
||
|
// adjusted by the robot rail mult (1.0 to 0.1, depending on how much you've ground the rail)
|
||
|
mp_score_component->GetScore()->TweakTrick(mGrindTweak * mp_score_component->GetScore()->GetRobotRailMult());
|
||
|
|
||
|
// if we've already started doing a trick, then start remembering our trick chain
|
||
|
{
|
||
|
const CRailNode* pRail = mp_rail_node;
|
||
|
Dbg_Assert(pRail);
|
||
|
|
||
|
if (mp_rail_man->GetNodeArray())
|
||
|
{
|
||
|
Script::CArray* pNodeArray=Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
|
||
|
Script::CStruct* pNode=pNodeArray->GetStructure(pRail->GetNode());
|
||
|
pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &m_last_rail_node_name, true);
|
||
|
mp_trick_component->TrickOffObject(m_last_rail_node_name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::ollie_off_rail_rotate ( )
|
||
|
{
|
||
|
float rot;
|
||
|
if (mp_input_component->GetControlPad().m_left.GetPressed())
|
||
|
{
|
||
|
rot = Mth::DegToRad(GetPhysicsFloat(CRCD(0x38505ef3, "Rail_Jump_Angle")));
|
||
|
}
|
||
|
else if (mp_input_component->GetControlPad().m_right.GetPressed())
|
||
|
{
|
||
|
rot = -Mth::DegToRad(GetPhysicsFloat(CRCD(0x38505ef3, "Rail_Jump_Angle")));
|
||
|
}
|
||
|
else return;
|
||
|
|
||
|
GetVel().RotateY(rot);
|
||
|
GetMatrix().RotateYLocal(rot);
|
||
|
m_lerping_display_matrix.RotateYLocal(rot);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::skate_off_rail ( const Mth::Vector& off_point )
|
||
|
{
|
||
|
// we have skated off a rail; either it was the end of a rail or we hit a sharp corner
|
||
|
// we need to see if there is another rail in front of us that we can continue skating on
|
||
|
|
||
|
bool really_off = true;
|
||
|
|
||
|
Mth::Vector a;
|
||
|
if (Ed::CParkEditor::Instance()->UsingCustomPark())
|
||
|
{
|
||
|
// in the park editor, we use the last point on the rail we just took off from as the start of the line to use for looking for new rails
|
||
|
// so we don't get rail segments earlier in the list
|
||
|
a = off_point;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// go back a step
|
||
|
a = GetPos() - GetVel() * m_frame_length;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
// use current pos, as we know that is off the end
|
||
|
Mth::Vector b = GetPos();
|
||
|
|
||
|
Mth::Vector rail_pos;
|
||
|
CRailNode* pNode;
|
||
|
|
||
|
|
||
|
// we need to tilt the line down, as the rail code currently fails to find a rail if the line we check is exactly parallel with it
|
||
|
a[Y] += 6.0f;
|
||
|
b[Y] += 6.1f;
|
||
|
|
||
|
float min_dot = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle"))));
|
||
|
|
||
|
// Note: we are now passing in Rail_Side() of the current rail and velocity
|
||
|
// so we can see if we switch from a rail on a left facing ledge to a rail on a right facing ledge, and try to inhibit that type of transition
|
||
|
// in favour of one that retains the same side
|
||
|
if (mp_rail_man->StickToRail(
|
||
|
a,
|
||
|
b,
|
||
|
&rail_pos,
|
||
|
&pNode,
|
||
|
mp_rail_node,
|
||
|
min_dot,
|
||
|
mp_rail_node->Side(GetVel())
|
||
|
))
|
||
|
{
|
||
|
// Mick, in park editor, we also disallow this if the rail is the next or the prev rail node from our current node
|
||
|
if (mp_rail_node != pNode && (
|
||
|
!Ed::CParkEditor::Instance()->UsingCustomPark()
|
||
|
|| mp_rail_node->GetNextLink() != pNode
|
||
|
&& mp_rail_node->GetPrevLink() != pNode
|
||
|
))
|
||
|
{
|
||
|
const CRailNode* pNewStart = pNode;
|
||
|
const CRailNode* pNewEnd = pNewStart->GetNextLink();
|
||
|
|
||
|
// check to see if our new position is within the two points
|
||
|
Mth::Vector to_start = mp_rail_man->GetPos(pNewStart) - GetPos();
|
||
|
Mth::Vector to_end = mp_rail_man->GetPos(pNewEnd) - GetPos();
|
||
|
|
||
|
float mid_dot = Mth::DotProduct(to_start, to_end);
|
||
|
|
||
|
// In game, the point must actualy be in the line, so mid dot will be negative
|
||
|
bool ok = mid_dot < 0.0f;
|
||
|
|
||
|
// Park Editor specific rail joining
|
||
|
if (!ok && Ed::CParkEditor::Instance()->UsingCustomPark())
|
||
|
{
|
||
|
// in the park editor, we let the user overshoot, so he sticks to tight curves that are between two pieces
|
||
|
ok = true;
|
||
|
|
||
|
// we need to ensure that the rail segment is a good continuation of the segment that we were just on
|
||
|
// Namely that one of the new start/end points is close to the end of the rail that we just came off
|
||
|
// and that the direction we will be going along is within 45 degrees of the direction we were going along before.
|
||
|
// (NOT DONE HERE, as the following test is sufficent)
|
||
|
|
||
|
// first a simple elimination, if the rail is longer than our velocity projected onto it then we can not posibly have overshot it!!
|
||
|
Mth::Vector new_rail_segment = mp_rail_man->GetPos(pNewEnd) - mp_rail_man->GetPos(pNewStart);
|
||
|
Mth::Vector skater_movement = b - a;
|
||
|
float new_rail_length = new_rail_segment.Length();
|
||
|
if (new_rail_length > skater_movement.Length())
|
||
|
{
|
||
|
ok = false;
|
||
|
}
|
||
|
|
||
|
// now we could find the shortest distance between two line segments; should be within 2 inches
|
||
|
// (ALSO NOT DONE)
|
||
|
|
||
|
// bit of a patch for the park editor, it's gone past the end of the rail, so set him in the middle of the rail, so we can continue nicely
|
||
|
// possible glitch here, but better than falling of the rail
|
||
|
// It's going to be a small rail anyway, so won't look too bad.
|
||
|
if (ok && mid_dot >= 0.0f)
|
||
|
{
|
||
|
rail_pos = (mp_rail_man->GetPos(pNewEnd) + mp_rail_man->GetPos(pNewStart)) / 2.0f;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (ok)
|
||
|
{
|
||
|
Mth::Vector newdir = mp_rail_man->GetPos(pNewEnd) - mp_rail_man->GetPos(pNewStart);
|
||
|
newdir.Normalize();
|
||
|
if (GetVel()[X] == 0.0f && GetVel()[Z] == 0.0f)
|
||
|
{
|
||
|
GetVel()[X] = GetMatrix()[Z][X];
|
||
|
GetVel()[Z] = GetMatrix()[Z][Z];
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
|
||
|
// sign is which way we are going along the rail
|
||
|
float sign = Mth::Sgn(Mth::DotProduct(newdir, GetVel()));
|
||
|
GetVel().RotateToNormal(newdir);
|
||
|
GetVel() *= sign;
|
||
|
|
||
|
mp_rail_node = pNode; // oh yes, this is the node
|
||
|
GetPos() = rail_pos; // move to closest position on the line
|
||
|
DUMP_POSITION;
|
||
|
maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
|
||
|
set_terrain(mp_rail_node->GetTerrain());
|
||
|
|
||
|
really_off = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// new position is outside this rail
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// its the same as this one
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// did not find any
|
||
|
}
|
||
|
|
||
|
if (!really_off) return;
|
||
|
|
||
|
maybe_trip_rail_trigger(TRIGGER_SKATE_OFF);
|
||
|
SetState(AIR);
|
||
|
GetPos()[Y] += 1.0f;
|
||
|
DUMP_POSITION;
|
||
|
FLAGEXCEPTION(CRCD(0xafaa46ba,"OffRail"));
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::maybe_trip_rail_trigger ( uint32 type )
|
||
|
{
|
||
|
// given that m_rail_node is valid, then trigger any script associated with this rail node
|
||
|
// will search backwards for the first rail that has a trigger script, and then execute that.
|
||
|
|
||
|
if (!mp_rail_node) return;
|
||
|
|
||
|
Dbg_Assert(mp_rail_man);
|
||
|
|
||
|
|
||
|
const CRailNode* pStartOfRail = mp_rail_node;
|
||
|
const CRailNode* pRail = mp_rail_node;
|
||
|
|
||
|
// no node array in rail manager indicates auto generated rails, so just return
|
||
|
Script::CArray* pNodeArray = mp_rail_man->GetNodeArray();
|
||
|
if (!pNodeArray) return;
|
||
|
|
||
|
Script::CStruct* pNode = pNodeArray->GetStructure(mp_rail_node->GetNode());
|
||
|
|
||
|
// find a rail node that has a "TriggerScript" in it
|
||
|
uint32 value = 0;
|
||
|
if (pNode->GetChecksum(CRCD(0x2ca8a299, "Triggerscript"), &value))
|
||
|
{
|
||
|
// we got it
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// did not get it, so scoot backwards until we detect a loop or we find one
|
||
|
const CRailNode* p_loop_detect = pRail; // start loop detect at the start
|
||
|
pRail = pRail->GetPrevLink(); // and the first node we check is the next one
|
||
|
int loop_advance = 0;
|
||
|
while (pRail && pRail != pStartOfRail && pRail != p_loop_detect)
|
||
|
{
|
||
|
pNode = pNodeArray->GetStructure(pRail->GetNode());
|
||
|
if (pNode->GetChecksum(CRCD(0x2ca8a299, "Triggerscript"), &value)) break;
|
||
|
|
||
|
pRail = pRail->GetPrevLink();
|
||
|
// The p_loop_detect pointer goes backwards at half speed, so if there is a loop
|
||
|
// then pRail is guarenteed to eventually catch up with p_loop_detect
|
||
|
if (loop_advance)
|
||
|
{
|
||
|
p_loop_detect = p_loop_detect->GetPrevLink();
|
||
|
}
|
||
|
loop_advance ^= 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (value)
|
||
|
{
|
||
|
// If this is different to last time, then set flag accordingly
|
||
|
uint32 new_last_rail_node_name;
|
||
|
pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &new_last_rail_node_name, Script::ASSERT);
|
||
|
// printf ("%s,%s\n",Script::FindChecksumName(new_last_rail_node_name), Script::FindChecksumName(m_last_rail_node_name));
|
||
|
if (new_last_rail_node_name != m_last_rail_trigger_node_name)
|
||
|
{
|
||
|
SetFlagTrue(NEW_RAIL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetFlagFalse(NEW_RAIL);
|
||
|
}
|
||
|
m_last_rail_node_name = new_last_rail_node_name;
|
||
|
m_last_rail_trigger_node_name = new_last_rail_node_name;
|
||
|
|
||
|
|
||
|
// if we are using the default node array, then set it to NULL, so TriggerEventFromNode can use this default, which is a lot faster
|
||
|
if (pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")))
|
||
|
{
|
||
|
pNodeArray = NULL;
|
||
|
}
|
||
|
|
||
|
mp_trigger_component->TripTrigger(
|
||
|
type,
|
||
|
m_last_rail_node_name,
|
||
|
pNodeArray,
|
||
|
mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::handle_tensing ( )
|
||
|
{
|
||
|
if (!GetFlag(TENSE) && mp_input_component->GetControlPad().m_x.GetPressed())
|
||
|
{
|
||
|
// just starting to tense, so set the flag
|
||
|
SetFlagTrue(TENSE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
bool CSkaterCorePhysicsComponent::get_member_feeler_collision ( uint16 ignore_1, uint16 ignore_0 )
|
||
|
{
|
||
|
// use m_col_start and m_col_end to set up m_feeler, then check for collision and if there is a collision, then get the various skater collision flags
|
||
|
|
||
|
m_feeler.SetLine(m_col_start, m_col_end);
|
||
|
m_feeler.SetIgnore(ignore_1, ignore_0);
|
||
|
|
||
|
bool collision = m_feeler.GetCollision();
|
||
|
|
||
|
if (!collision) return false;
|
||
|
|
||
|
if (m_feeler.GetNodeChecksum() && m_feeler.GetTrigger())
|
||
|
{
|
||
|
// Given a node name, then find the checksum of the triggerscript in that node
|
||
|
// note that this is VERY SLOW, as it has to scan the whole node array
|
||
|
// it's usually only called once per frame, per skater, but might still be a good candidate for optimization
|
||
|
|
||
|
// Now just clear the script, indicating it needed doing later
|
||
|
m_feeler.SetScript(0);
|
||
|
}
|
||
|
|
||
|
// get any skating specific flags here
|
||
|
uint16 flags = m_feeler.GetFlags();
|
||
|
|
||
|
// get normal, as it's the kind of things we use in face flag determination
|
||
|
Mth::Vector normal = m_feeler.GetNormal();
|
||
|
|
||
|
// Make wallable default to off.
|
||
|
m_col_flag_wallable = false;
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////
|
||
|
// Vert
|
||
|
|
||
|
m_col_flag_vert = flags & mFD_VERT;
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
// Skatable
|
||
|
|
||
|
if (flags & mFD_SKATABLE)
|
||
|
{
|
||
|
// if flaged as skatable, than that overrides all other flags
|
||
|
m_col_flag_skatable = true;
|
||
|
}
|
||
|
else if (flags & mFD_NOT_SKATABLE)
|
||
|
{
|
||
|
// if flagged as non_skatable, then that overrides all other flags
|
||
|
m_col_flag_skatable = false;
|
||
|
}
|
||
|
else if (flags & mFD_VERT)
|
||
|
{
|
||
|
// if flagged as VERT, then it's the top of a QP, so it's skatable and this overrides the angle flag
|
||
|
m_col_flag_skatable = true;
|
||
|
}
|
||
|
else if (flags & mFD_WALL_RIDABLE)
|
||
|
{
|
||
|
m_col_flag_skatable = false;
|
||
|
m_col_flag_wallable = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// determine the skatablitlity based on the angle
|
||
|
// if angle is > 5 degrees from vert, then it is skatable
|
||
|
// here the y component of the normal is the cosine of the angle from vertical
|
||
|
|
||
|
if (normal[Y] < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x3eede4d3,"Wall_Non_Skatable_Angle")))))
|
||
|
{
|
||
|
// get could possibly do another test here to see if there is a polygon beneath this meaning this is a QP
|
||
|
// however, we want to be flagging the tops of all QPs
|
||
|
m_col_flag_skatable = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_col_flag_skatable = true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_jump ( Script::CStruct *pParams )
|
||
|
{
|
||
|
// Called by the Jump script command
|
||
|
|
||
|
// play sounds before modifying the skater speed, as the sound volume and pitch are based on the pre-jump velocity
|
||
|
bool play_sound = !pParams->ContainsFlag(CRCD(0xe13620a8, "no_sound"));
|
||
|
|
||
|
switch (GetState())
|
||
|
{
|
||
|
case GROUND:
|
||
|
mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
|
||
|
if (mp_physics_control_component->HaveBeenReset()) return;
|
||
|
|
||
|
// K: Remember the last ground position for calculating which side of the rail we're on later.
|
||
|
m_last_ground_pos = GetPos();
|
||
|
|
||
|
if (play_sound)
|
||
|
{
|
||
|
mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_last_ground_feeler.GetTerrain());
|
||
|
}
|
||
|
|
||
|
maybe_straight_up();
|
||
|
SetFlagTrue(CAN_BREAK_VERT);
|
||
|
break;
|
||
|
|
||
|
case AIR:
|
||
|
if (play_sound)
|
||
|
{
|
||
|
mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_last_ground_feeler.GetTerrain());
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case RAIL:
|
||
|
if (play_sound)
|
||
|
{
|
||
|
Dbg_Assert(mp_rail_node);
|
||
|
mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Dbg_Assert(pParams);
|
||
|
|
||
|
int max_tense_time = GetPhysicsInt(CRCD(0xf733c097, "skater_max_tense_time"));
|
||
|
m_tense_time = Mth::Min(m_tense_time, max_tense_time);
|
||
|
|
||
|
float max_jump_speed;
|
||
|
float min_jump_speed;
|
||
|
|
||
|
bool from_vert = GetFlag(VERT_AIR) || (GetState() == GROUND && GetFlag(LAST_POLY_WAS_VERT));
|
||
|
|
||
|
if (pParams->ContainsFlag(CRCD(0xec6b7fc7, "BonelessHeight")))
|
||
|
{
|
||
|
if (from_vert)
|
||
|
{
|
||
|
max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x39766891, "Physics_Boneless_air_Jump_Speed_stat"));
|
||
|
min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x4cdb18cb, "Physics_Boneless_air_Jump_Speed_min_stat"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x8851a76e, "Physics_Boneless_Jump_Speed_stat"));
|
||
|
min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x6e8efe38, "Physics_Boneless_Jump_Speed_min_stat"));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (from_vert)
|
||
|
{
|
||
|
max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x9f79c8ea, "Physics_air_Jump_Speed_stat"));
|
||
|
min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x91b07824, "Physics_air_Jump_Speed_min_stat"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x2ade3ad, "Physics_Jump_Speed_stat"));
|
||
|
min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0xc8815e43, "Physics_Jump_Speed_min_stat"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float jump_speed = Mth::LinearMap(min_jump_speed, max_jump_speed, m_tense_time, 0.0f, max_tense_time);
|
||
|
|
||
|
// If any speed param is specified, then use that instead. Need by Zac for when small skater is doing scripted jumps in the front end.
|
||
|
pParams->GetFloat(CRCD(0xf0d90109, "speed"), &jump_speed);
|
||
|
|
||
|
SetFlagFalse(TENSE);
|
||
|
|
||
|
// if we have a very high downward velocity and we are landing on vert, then do not jump
|
||
|
|
||
|
// when jumping, don't add in the current velocity if it is less than zero
|
||
|
// this will give us nicer feeling jumps when
|
||
|
// - comping down wall rides
|
||
|
// - skating down slopes
|
||
|
// - skating down the back of bumps, liks the speed bumps in CA
|
||
|
// - landing on a QP
|
||
|
|
||
|
|
||
|
if (GetFlag(VERT_AIR) && GetVel()[Y] < 0.0f)
|
||
|
{
|
||
|
// push outward (and upward) away from vert poly
|
||
|
GetVel() += jump_speed * m_display_normal;
|
||
|
DUMP_VELOCITY;
|
||
|
jump_speed = 0;
|
||
|
SetFlagFalse(VERT_AIR);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (GetVel()[Y] < 0.0f)
|
||
|
{
|
||
|
GetVel()[Y] = 0.0f;
|
||
|
DUMP_VELOCITY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GetVel()[Y] += jump_speed;
|
||
|
DUMP_VELOCITY;
|
||
|
|
||
|
// if pointing down, then jump down, as moving up will make us pass through whatever we are on
|
||
|
if (GetMatrix()[Y][Y] < -0.1f)
|
||
|
{
|
||
|
GetVel()[Y] -= jump_speed * 1.5f;
|
||
|
DUMP_VELOCITY;
|
||
|
GetPos() += GetMatrix()[Y] * 12.0f;
|
||
|
DUMP_POSITION;
|
||
|
}
|
||
|
|
||
|
// allow side jumps when jumping off rails or when late jumping in air after skating off a rail
|
||
|
if (GetState() == RAIL || (GetState() == AIR && m_previous_state == RAIL))
|
||
|
{
|
||
|
ollie_off_rail_rotate();
|
||
|
}
|
||
|
|
||
|
// don't change the state until after sound is played, as the above switch relyies on the GROUND/RAIL state...
|
||
|
SetState(AIR);
|
||
|
|
||
|
m_last_jump_time_stamp = Tmr::GetTime();
|
||
|
|
||
|
GetObject()->BroadcastEvent(CRCD(0x8687163a, "SkaterJump"));
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::start_skitch ( )
|
||
|
{
|
||
|
// Start Skitching, given that we've queue one up
|
||
|
// Note, mp_skitch_object is a smart pointer so there is a slight possibility that it might have died but we check that
|
||
|
|
||
|
// Mick: I'm using .Convert here. I'm not sure if makes any difference,
|
||
|
// but it can't hurt and should compile the same.
|
||
|
if (!mp_skitch_object.Convert()) return;
|
||
|
|
||
|
mp_movable_contact_component->ObtainContact(mp_skitch_object);
|
||
|
SetFlagTrue(SKITCHING);
|
||
|
m_moving_to_skitch = true;
|
||
|
move_to_skitch_point();
|
||
|
|
||
|
// Mick: For some reason we sometimes get a NULL smart pointer here
|
||
|
// so I'm checking again here, in addition to changing the code
|
||
|
// to use .Convert
|
||
|
if (!mp_skitch_object.Convert()) return;
|
||
|
|
||
|
mp_skitch_object->SelfEvent(CRCD(0x35224f25, "SkitchOn"));
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::do_grind_trick ( uint Direction, bool Right, bool Parallel, bool Backwards, bool Regular )
|
||
|
{
|
||
|
int SubArrayIndex = 0;
|
||
|
|
||
|
// Choose which array index to use.
|
||
|
switch (Direction)
|
||
|
{
|
||
|
case 0: // Nowt
|
||
|
SubArrayIndex = 0;
|
||
|
break;
|
||
|
case PAD_U: // Up
|
||
|
SubArrayIndex = 1;
|
||
|
break;
|
||
|
case PAD_D: // Down
|
||
|
SubArrayIndex = 2;
|
||
|
break;
|
||
|
case PAD_L: // Left
|
||
|
SubArrayIndex = 3;
|
||
|
break;
|
||
|
case PAD_R: // Right
|
||
|
SubArrayIndex = 4;
|
||
|
break;
|
||
|
case PAD_UL: // UpLeft
|
||
|
SubArrayIndex = 5;
|
||
|
break;
|
||
|
case PAD_UR: // UpRight
|
||
|
SubArrayIndex = 6;
|
||
|
break;
|
||
|
case PAD_DL: // DownLeft
|
||
|
SubArrayIndex = 7;
|
||
|
break;
|
||
|
case PAD_DR: // DownRight
|
||
|
SubArrayIndex = 8;
|
||
|
break;
|
||
|
default:
|
||
|
Dbg_MsgAssert(false, ("Bad button checksum"));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// GetArray will assert if GrindTrickList not found.
|
||
|
Script::CArray* pMainArray = Script::GetArray(CRCD(0x2ab3341d, "GrindTrickList"));
|
||
|
Script::CArray* pSubArray = pMainArray->GetArray(SubArrayIndex);
|
||
|
|
||
|
uint Index = 0;
|
||
|
if (Right) Index |= 1;
|
||
|
if (Parallel) Index |= 2;
|
||
|
if (Backwards) Index |= 4;
|
||
|
if (Regular) Index |= 8;
|
||
|
|
||
|
// Initialise mGrindTweak, which should get set by a SetGrindTweak command in the script that is about to be run.
|
||
|
ResetGrindTweak();
|
||
|
|
||
|
CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
|
||
|
Dbg_Assert(p_flip_and_rotate_component);
|
||
|
p_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters();
|
||
|
|
||
|
if (!GetObject()->GetScript())
|
||
|
{
|
||
|
GetObject()->SetScript(new Script::CScript);
|
||
|
}
|
||
|
|
||
|
GetObject()->GetScript()->SetScript(pSubArray->GetNameChecksum(Index), NULL, GetObject());
|
||
|
GetObject()->GetScript()->Update();
|
||
|
|
||
|
// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
|
||
|
mp_state_component->SetDoingTrick(true);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::StopSkitch ( )
|
||
|
{
|
||
|
// If we were skitching, then stop it
|
||
|
|
||
|
if (!GetFlag(SKITCHING)) return;
|
||
|
|
||
|
SetFlagFalse(SKITCHING);
|
||
|
if (mp_skitch_object)
|
||
|
{
|
||
|
mp_skitch_object->SelfEvent(CRCD(0x2739b86d, "SkitchOff"));
|
||
|
mp_skitch_object = NULL;
|
||
|
}
|
||
|
mp_movable_contact_component->LoseAnyContact();
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::setup_default_collision_cache ( )
|
||
|
{
|
||
|
// reduced Y extent as this no longer subsumes the uberfrig
|
||
|
Mth::CBBox bbox(
|
||
|
GetPos() - Mth::Vector(FEET(15.0f), FEET(17.0f), FEET(15.0f), 0.0f),
|
||
|
GetPos() + Mth::Vector(FEET(15.0f), FEET(8.0f), FEET(15.0f), 0.0f)
|
||
|
);
|
||
|
|
||
|
mp_coll_cache->Update(bbox);
|
||
|
CFeeler::sSetDefaultCache(mp_coll_cache);
|
||
|
}
|
||
|
|
||
|
/******************************************************************/
|
||
|
/* */
|
||
|
/* */
|
||
|
/******************************************************************/
|
||
|
|
||
|
void CSkaterCorePhysicsComponent::update_special_friction_index ( )
|
||
|
{
|
||
|
if (m_special_friction_index == 0) return;
|
||
|
|
||
|
if (Tmr::ElapsedTime(m_special_friction_decrement_time_stamp) > static_cast< Tmr::Time >(1000.0f * Script::GetFloat(CRCD(0xf4813ad5, "Physics_Time_Before_Free_Revert"))))
|
||
|
{
|
||
|
m_special_friction_index--;
|
||
|
MESSAGE("You earned a free revert!!!!");
|
||
|
if (m_special_friction_index != 0)
|
||
|
{
|
||
|
m_special_friction_decrement_time_stamp = Tmr::GetTime();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|