1115 lines
33 KiB
C#
1115 lines
33 KiB
C#
using System;
|
|
using Godot;
|
|
using GodotStateCharts;
|
|
using Movementtests.addons.godot_state_charts.csharp;
|
|
using Movementtests.systems;
|
|
using Movementtests.player_controller.Scripts;
|
|
using RustyOptions;
|
|
|
|
public enum JumpTypes
|
|
{
|
|
SimpleJump,
|
|
DoubleJump,
|
|
JumpFromDash,
|
|
JumpFromWall
|
|
}
|
|
|
|
public partial class PlayerController : CharacterBody3D
|
|
{
|
|
// User API to important child nodes.
|
|
public HeadSystem HeadSystem;
|
|
public Bobbing Bobbing;
|
|
public FieldOfView FieldOfView;
|
|
public Stamina Stamina;
|
|
public StairsSystem StairsSystem;
|
|
public MantleSystem MantleSystem;
|
|
public DashSystem DashSystem;
|
|
public CapsuleCollider CapsuleCollider;
|
|
public HealthSystem HealthSystem;
|
|
public TweenQueueSystem TweenQueueSystem;
|
|
public Node3D WeaponRoot;
|
|
public WeaponSystem WeaponSystem;
|
|
public WallHugSystem WallHugSystem;
|
|
public PlayerUi PlayerUi;
|
|
public TextureRect DashIndicator;
|
|
public ColorRect PowerCooldownIndicator;
|
|
|
|
private bool _movementEnabled = true;
|
|
|
|
private bool _shouldMantle;
|
|
private Vector3 _mantleLocation = Vector3.Zero;
|
|
private Vector3 _dashDirection = Vector3.Zero;
|
|
|
|
private float _lastFrameWasOnFloor = -Mathf.Inf;
|
|
|
|
private const int NUM_OF_HEAD_COLLISION_DETECTORS = 4;
|
|
private RayCast3D[] _headCollisionDetectors;
|
|
|
|
private Vector3 _inputMove = Vector3.Zero;
|
|
private float _inputRotateY;
|
|
private float _inputRotateFloorplane;
|
|
|
|
private int _framesSinceJumpAtApex = 0;
|
|
|
|
// Timers
|
|
private Timer _timeScaleAimInAirTimer;
|
|
private Timer _simpleDashCooldownTimer;
|
|
private Timer _powerCooldownTimer;
|
|
|
|
[ExportCategory("Movement")]
|
|
[ExportGroup("Ground")]
|
|
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
|
|
public float WalkSpeed { get; set; } = 7.0f;
|
|
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
|
|
public float AccelerationFloor = 5.0f;
|
|
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
|
|
public float DecelerationFloor = 5.0f;
|
|
[ExportGroup("Air")]
|
|
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
|
|
public float AccelerationAir = 3.0f;
|
|
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
|
public float DecelerationAir = 1.0f;
|
|
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
|
public float Weight { get; set; } = 3.0f;
|
|
|
|
// Jump
|
|
[ExportGroup("Jump")]
|
|
[Export(PropertyHint.Range, "0,1,0.01")]
|
|
public float CoyoteTime { get; set; } = 0.2f;
|
|
|
|
// Simple jump
|
|
[ExportSubgroup("Simple jump")]
|
|
[Export(PropertyHint.Range, "0,100,1,or_greater")]
|
|
public float SimpleJumpStartVelocity { get; set; } = 3.0f;
|
|
[Export(PropertyHint.Range, "0,10,1,or_greater")]
|
|
public int SimpleJumpHangTimeInFrames { get; set; } = 5;
|
|
[Export(PropertyHint.Range, "1,10,0.1,or_greater")]
|
|
public float SimpleJumpGravityLesseningFactor { get; set; } = 3f;
|
|
|
|
|
|
// Double jump
|
|
[ExportSubgroup("Double jump")]
|
|
[Export(PropertyHint.Range, "0,100,1,or_greater")]
|
|
public float DoubleJumpStartVelocity { get; set; } = 10.0f;
|
|
[Export(PropertyHint.Range, "0,10,1,or_greater")]
|
|
public int DoubleJumpHangTimeInFrames { get; set; } = 5;
|
|
[Export(PropertyHint.Range, "1,10,0.1,or_greater")]
|
|
public float DoubleJumpGravityLesseningFactor { get; set; } = 3f;
|
|
|
|
|
|
// Mega jump
|
|
[ExportSubgroup("Mega jump")]
|
|
[Export(PropertyHint.Range, "0,100,1,or_greater")]
|
|
public float MegaJumpStartVelocity { get; set; } = 10.0f;
|
|
[Export(PropertyHint.Range, "0,10,1,or_greater")]
|
|
public int MegaJumpHangTimeInFrames { get; set; } = 5;
|
|
[Export(PropertyHint.Range, "1,10,0.1,or_greater")]
|
|
public float MegaJumpGravityLesseningFactor { get; set; } = 3f;
|
|
|
|
// Wall jump
|
|
[ExportSubgroup("Wall jump")]
|
|
[Export(PropertyHint.Range, "0,100,1,or_greater")]
|
|
public float WallJumpStartVelocity { get; set; } = 10.0f;
|
|
[Export(PropertyHint.Range, "0,100,1,or_greater")]
|
|
public float WallMegajumpStartVelocity { get; set; } = 20.0f;
|
|
|
|
// Dash
|
|
[ExportGroup("Dash")]
|
|
// Simple dash
|
|
[ExportSubgroup("Simple")]
|
|
[Export(PropertyHint.Range, "0,50,0.1")]
|
|
public float SimpleDashStrength { get; set; } = 10f;
|
|
// Powered Dash
|
|
[ExportSubgroup("Powered")]
|
|
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
|
|
public float PoweredDashTime { get; set; } = 0.3f;
|
|
[Export(PropertyHint.Range, "0,100,0.1")]
|
|
public float PoweredDashStrength { get; set; } = 10f;
|
|
|
|
// Wall hug
|
|
[ExportGroup("Wall hug")]
|
|
[Export(PropertyHint.Range, "0,50,0.1,or_greater")]
|
|
public float WallHugGravityLesseningFactor { get; set; } = 2f;
|
|
[Export(PropertyHint.Range, "0.1,50,0.1,or_greater")]
|
|
public float WallHugDownwardMaxSpeed { get; set; } = 2f;
|
|
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
|
|
public float WallHugHorizontalDeceleration { get; set; } = 5f;
|
|
|
|
private float _targetSpeed;
|
|
private float _gravity;
|
|
|
|
[ExportCategory("Other")]
|
|
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
|
|
public float TimeScaleAimInAir { get; set; } = 0.05f;
|
|
[Export(PropertyHint.Range, "0,5,1,or_greater")]
|
|
public int MaxNumberOfDashActions { get; set; } = 1;
|
|
|
|
[Export]
|
|
public Curve DashTimeDilationCurve { get; set; }
|
|
private bool _canDash = true;
|
|
|
|
private int _empoweredActionsLeft;
|
|
public int EmpoweredActionsLeft
|
|
{
|
|
get => _empoweredActionsLeft;
|
|
set
|
|
{
|
|
_empoweredActionsLeft = value;
|
|
PlayerUi.SetNumberOfDashesLeft(value);
|
|
}
|
|
}
|
|
|
|
private bool _isWallJumpAvailable = true;
|
|
private bool _isActionPerfectlyTimed = false;
|
|
|
|
private StateChart _playerState;
|
|
|
|
private StateChartState _weaponInHand;
|
|
private StateChartState _aiming;
|
|
private StateChartState _weaponThrown;
|
|
private StateChartState _actionHanging;
|
|
private StateChartState _empowerOn;
|
|
private StateChartState _empowerOff;
|
|
private StateChartState _powerExpired;
|
|
private StateChartState _powerRecharging;
|
|
private StateChartState _powerFull;
|
|
|
|
private StateChartState _grounded;
|
|
private StateChartState _mantling;
|
|
private StateChartState _movHanging;
|
|
private StateChartState _airborne;
|
|
private StateChartState _coyoteEnabled;
|
|
private StateChartState _simpleJump;
|
|
private StateChartState _doubleJump;
|
|
private StateChartState _megaJump;
|
|
private StateChartState _simpleDash;
|
|
private StateChartState _poweredDash;
|
|
private StateChartState _doubleJumpEnabled;
|
|
private StateChartState _onWall;
|
|
private StateChartState _onWallHugCanceled;
|
|
private StateChartState _onWallHugging;
|
|
private StateChartState _onWallHanging;
|
|
private StateChartState _falling;
|
|
|
|
private Transition _onJumpFromWall;
|
|
private Transition _onMegajumpFromWall;
|
|
|
|
public override void _Ready()
|
|
{
|
|
///////////////////////////
|
|
// Getting components /////
|
|
///////////////////////////
|
|
|
|
// General use stuff
|
|
TweenQueueSystem = GetNode<TweenQueueSystem>("TweenQueueSystem");
|
|
PlayerUi = GetNode<PlayerUi>("UI");
|
|
// DashIndicator = GetNode<TextureRect>("%DashIndicator");
|
|
PowerCooldownIndicator = GetNode<ColorRect>("%DashCooldownIndicator");
|
|
PowerCooldownIndicator.Visible = false;
|
|
EmpoweredActionsLeft = MaxNumberOfDashActions;
|
|
_targetSpeed = WalkSpeed;
|
|
|
|
// Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D;
|
|
|
|
// Camera stuff
|
|
HeadSystem = GetNode<HeadSystem>("HeadSystem");
|
|
Bobbing = GetNode<Bobbing>("Bobbing");
|
|
FieldOfView = GetNode<FieldOfView>("FieldOfView");
|
|
Camera3D camera = GetNode<Camera3D>("HeadSystem/CameraSmooth/Camera3D");
|
|
Node3D cameraSmooth = GetNode<Node3D>("HeadSystem/CameraSmooth");
|
|
ColorRect vignetteRect = GetNode<ColorRect>(
|
|
"HeadSystem/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect");
|
|
ColorRect distortionRect = GetNode<ColorRect>(
|
|
"HeadSystem/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect");
|
|
ColorRect blurRect = GetNode<ColorRect>("HeadSystem/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect");
|
|
|
|
// Movement stuff
|
|
WeaponRoot = GetNode<Node3D>("WeaponRoot");
|
|
WeaponSystem = GetNode<WeaponSystem>("WeaponRoot/WeaponSystem");
|
|
MantleSystem = GetNode<MantleSystem>("MantleSystem");
|
|
CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider");
|
|
DashSystem = GetNode<DashSystem>("DashSystem");
|
|
StairsSystem = GetNode<StairsSystem>("StairsSystem");
|
|
WallHugSystem = GetNode<WallHugSystem>("WallHugSystem");
|
|
RayCast3D stairsBelowRayCast3D = GetNode<RayCast3D>("StairsBelowRayCast3D");
|
|
RayCast3D stairsAheadRayCast3D = GetNode<RayCast3D>("StairsAheadRayCast3D");
|
|
_headCollisionDetectors = new RayCast3D[NUM_OF_HEAD_COLLISION_DETECTORS];
|
|
for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++)
|
|
{
|
|
_headCollisionDetectors[i] = GetNode<RayCast3D>(
|
|
"HeadCollisionDetectors/HeadCollisionDetector" + i);
|
|
}
|
|
|
|
// RPG Stuff
|
|
Stamina = GetNode<Stamina>("Stamina");
|
|
HealthSystem = GetNode<HealthSystem>("HealthSystem");
|
|
|
|
// State management
|
|
_playerState = StateChart.Of(GetNode("StateChart"));
|
|
|
|
_weaponInHand = StateChartState.Of(GetNode("StateChart/Root/WeaponState/InHand"));
|
|
_weaponThrown = StateChartState.Of(GetNode("StateChart/Root/WeaponState/Flying"));
|
|
_aiming = StateChartState.Of(GetNode("StateChart/Root/Aim/On"));
|
|
_simpleDash = StateChartState.Of(GetNode("StateChart/Root/Movement/Dashing/Dash"));
|
|
_poweredDash = StateChartState.Of(GetNode("StateChart/Root/Movement/Dashing/PoweredDash"));
|
|
// _actionHanging = StateChartState.Of(GetNode("StateChart/Root/Actions/Hanging"));
|
|
_empowerOn = StateChartState.Of(GetNode("StateChart/Root/Empower/On"));
|
|
_empowerOff = StateChartState.Of(GetNode("StateChart/Root/Empower/Off"));
|
|
_powerExpired = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/Expired"));
|
|
_powerRecharging = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/AtLeastOneCharge"));
|
|
_powerFull = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/Full"));
|
|
|
|
_grounded = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded"));
|
|
_mantling = StateChartState.Of(GetNode("StateChart/Root/Movement/Mantling"));
|
|
_movHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hanging"));
|
|
_airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne"));
|
|
_coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled"));
|
|
_simpleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/SimpleJump"));
|
|
_doubleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/DoubleJump"));
|
|
_megaJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/MegaJump"));
|
|
_doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled"));
|
|
_falling = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/Falling"));
|
|
_onWall = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall"));
|
|
_onJumpFromWall = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/OnJump"));
|
|
_onMegajumpFromWall = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/OnMegajump"));
|
|
_onWallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hugging"));
|
|
_onWallHugCanceled = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/HugCanceled"));
|
|
_onWallHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hanging"));
|
|
// State timers
|
|
_powerCooldownTimer = GetNode<Timer>("PowerCooldown");
|
|
_timeScaleAimInAirTimer = GetNode<Timer>("TimeScaleAimInAir");
|
|
_simpleDashCooldownTimer = GetNode<Timer>("DashCooldown");
|
|
|
|
///////////////////////////
|
|
// Initialize components //
|
|
///////////////////////////
|
|
|
|
// General use stuff
|
|
TweenQueueSystem.Init(this);
|
|
|
|
// Camera stuff
|
|
HeadSystem.Init();
|
|
Bobbing.Init(camera);
|
|
FieldOfView.Init(camera);
|
|
|
|
// Movement stuff
|
|
// Getting universal setting from GODOT editor to be in sync
|
|
_gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
|
|
MantleSystem.Init(HeadSystem);
|
|
StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
|
|
DashSystem.Init(HeadSystem, camera, TweenQueueSystem);
|
|
WeaponSystem.Init(HeadSystem, camera);
|
|
WallHugSystem.Init();
|
|
|
|
// RPG Stuff
|
|
HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams()
|
|
{
|
|
Parent = this,
|
|
Camera = camera,
|
|
Head = HeadSystem,
|
|
VignetteRect = vignetteRect,
|
|
DistortionRect = distortionRect,
|
|
BlurRect = blurRect,
|
|
};
|
|
HealthSystem.Init(healthSystemParams);
|
|
Stamina.SetSpeeds(WalkSpeed, WalkSpeed);
|
|
|
|
EmpoweredActionsLeft = MaxNumberOfDashActions;
|
|
|
|
///////////////////////////
|
|
// Signal setup ///////////
|
|
///////////////////////////
|
|
_weaponInHand.StateProcessing += HandleWeaponInHand;
|
|
_aiming.StateProcessing += HandleAiming;
|
|
_aiming.StateEntered += OnAimingEntered;
|
|
_aiming.StateExited += ResetTimeScale;
|
|
_aiming.StateExited += OnAimingExited;
|
|
|
|
_grounded.StateEntered += OnGrounded;
|
|
_grounded.StatePhysicsProcessing += HandleGrounded;
|
|
_airborne.StateEntered += OnAirborne;
|
|
_airborne.StatePhysicsProcessing += HandleAirborne;
|
|
|
|
_coyoteEnabled.StateEntered += StartCoyoteTime;
|
|
_timeScaleAimInAirTimer.Timeout += ResetTimeScale;
|
|
|
|
// _weaponThrown.StateEntered += OnWeaponThrown;
|
|
|
|
_powerFull.StateEntered += StopPowerCooldown;
|
|
_powerFull.StateExited += StartPowerCooldown;
|
|
_powerRecharging.StateEntered += StartPowerCooldown;
|
|
_powerCooldownTimer.Timeout += PowerCooldownExpired;
|
|
_powerRecharging.StateProcessing += PowerRecharging;
|
|
_powerExpired.StateProcessing += PowerRecharging;
|
|
|
|
_simpleJump.StateEntered += OnSimpleJumpStarted;
|
|
_simpleJump.StatePhysicsProcessing += HandleSimpleJump;
|
|
|
|
_doubleJump.StateEntered += OnDoubleJumpStarted;
|
|
_doubleJump.StatePhysicsProcessing += HandleDoubleJump;
|
|
|
|
_megaJump.StateEntered += OnMegaJumpStarted;
|
|
_megaJump.StatePhysicsProcessing += HandleMegaJump;
|
|
|
|
_simpleDash.StateEntered += OnSimpleDashStarted;
|
|
_simpleDash.StatePhysicsProcessing += HandleSimpleDash;
|
|
|
|
_poweredDash.StateEntered += OnPoweredDashStarted;
|
|
_poweredDash.StatePhysicsProcessing += HandlePoweredDash;
|
|
_poweredDash.StateExited += OnPoweredDashFinished;
|
|
|
|
_simpleDashCooldownTimer.Timeout += DashCooldownTimeout;
|
|
|
|
_onWallHugCanceled.StatePhysicsProcessing += HandleAirborne;
|
|
_onWallHugging.StatePhysicsProcessing += HandleWallHugging;
|
|
_onWallHanging.StatePhysicsProcessing += HandleWallHanging;
|
|
|
|
_onJumpFromWall.Taken += OnJumpFromWall;
|
|
_onMegajumpFromWall.Taken += OnMegajumpFromWall;
|
|
}
|
|
|
|
private bool _canDashAirborne = true;
|
|
|
|
public void OnWallDetected()
|
|
{
|
|
FinishPoweredDash();
|
|
}
|
|
|
|
public void OnGrounded()
|
|
{
|
|
_isWallJumpAvailable = true;
|
|
_canDashAirborne = true;
|
|
|
|
if (_simpleDashCooldownTimer.IsStopped())
|
|
_simpleDashCooldownTimer.Start();
|
|
}
|
|
|
|
public void OnAirborne()
|
|
{
|
|
}
|
|
public void DashCooldownTimeout()
|
|
{
|
|
_canDash = true;
|
|
}
|
|
|
|
public void HandleGrounded(float delta)
|
|
{
|
|
MoveOnGround(delta);
|
|
if (!isOnFloorCustom())
|
|
_playerState.SendEvent("start_falling");
|
|
}
|
|
public void HandleAirborne(float delta)
|
|
{
|
|
MoveInAir(delta);
|
|
if (isOnFloorCustom())
|
|
_playerState.SendEvent("grounded");
|
|
if (WallHugSystem.IsWallHugging() && Velocity.Y < 0)
|
|
_playerState.SendEvent("wall_hug");
|
|
}
|
|
public void HandleWallHugging(float delta)
|
|
{
|
|
WallHug(delta);
|
|
if (isOnFloorCustom())
|
|
_playerState.SendEvent("grounded");
|
|
if (!WallHugSystem.IsWallHugging())
|
|
_playerState.SendEvent("start_falling");
|
|
}
|
|
public void HandleWallHanging(float delta)
|
|
{
|
|
WallHang(delta);
|
|
}
|
|
|
|
// Jump
|
|
public void OnInputJumpStarted()
|
|
{
|
|
if (CanMantle())
|
|
{
|
|
Mantle();
|
|
return;
|
|
}
|
|
|
|
// if (_grounded.Active || _coyoteEnabled.Active)
|
|
// {
|
|
// if (_empowerOn.Active && CanPerformEmpoweredAction())
|
|
// {
|
|
// PerformEmpoweredAction();
|
|
// PerformJump(JumpTypes.JumpFromDash);
|
|
// _playerState.SendEvent("megajump");
|
|
// }
|
|
// }
|
|
// else if (_doubleJumpEnabled.Active)
|
|
// if (_empowerOn.Active && CanPerformEmpoweredAction())
|
|
// {
|
|
// PerformEmpoweredAction();
|
|
// PerformJump(JumpTypes.JumpFromDash);
|
|
// _playerState.SendEvent("megajump");
|
|
// }
|
|
// else
|
|
// PerformJump(JumpTypes.DoubleJump);
|
|
// else if (_onWall.Active)
|
|
// JumpFromWall(_empowerOn.Active);
|
|
|
|
if (_empowerOn.Active && CanPerformEmpoweredAction())
|
|
{
|
|
_playerState.SendEvent("megajump");
|
|
return;
|
|
}
|
|
_playerState.SendEvent("jump");
|
|
}
|
|
|
|
public void OnInputJumpOngoing()
|
|
{
|
|
}
|
|
|
|
public void OnInputJumpEnded()
|
|
{
|
|
_playerState.SendEvent("jump_ended");
|
|
}
|
|
|
|
public void OnJumpStarted(float verticalVelocity)
|
|
{
|
|
_framesSinceJumpAtApex = 0;
|
|
SetVerticalVelocity(verticalVelocity);
|
|
}
|
|
public void OnSimpleJumpStarted()
|
|
{
|
|
OnJumpStarted(SimpleJumpStartVelocity);
|
|
}
|
|
public void OnDoubleJumpStarted()
|
|
{
|
|
OnJumpStarted(DoubleJumpStartVelocity);
|
|
}
|
|
public void OnMegaJumpStarted()
|
|
{
|
|
PerformEmpoweredAction();
|
|
OnJumpStarted(MegaJumpStartVelocity);
|
|
}
|
|
|
|
public void WallHug(float delta)
|
|
{
|
|
var hvel = ComputeHVelocity(delta, WallHugHorizontalDeceleration, WallHugHorizontalDeceleration);
|
|
var vvel = Velocity.Y - (CalculateGravityForce() * delta / WallHugGravityLesseningFactor);
|
|
vvel = Math.Abs(vvel) > WallHugDownwardMaxSpeed ? -WallHugDownwardMaxSpeed : vvel;
|
|
Velocity = hvel + vvel*Vector3.Up;
|
|
}
|
|
|
|
public void WallHang(float delta)
|
|
{
|
|
Velocity = Vector3.Zero;
|
|
MoveAndSlide();
|
|
}
|
|
|
|
public void ComputeJumpFromWallHSpeed(float jumpStrength)
|
|
{
|
|
// if (!_isWallJumpAvailable)
|
|
// return;
|
|
// _isWallJumpAvailable = false;
|
|
// var isLookingTowardsWall = HeadSystem.GetForwardHorizontalVector().Dot(wallNormal) > 0.5;
|
|
// var jumpDirection = isLookingTowardsWall ? Vector3.Up : wallNormal;
|
|
var wallNormal = WallHugSystem.GetWallNormal().UnwrapOr(Vector3.Up);
|
|
var jumpVector = wallNormal * jumpStrength;
|
|
|
|
SetHorizontalVelocity(new Vector2(jumpVector.X, jumpVector.Z));
|
|
}
|
|
public void OnJumpFromWall()
|
|
{
|
|
ComputeJumpFromWallHSpeed(WallJumpStartVelocity);
|
|
}
|
|
public void OnMegajumpFromWall()
|
|
{
|
|
ComputeJumpFromWallHSpeed(WallMegajumpStartVelocity);
|
|
}
|
|
|
|
public Vector3 ComputeHVelocity(float delta, float accelerationFactor, float decelerationFactor, Vector3? direction = null)
|
|
{
|
|
var dir = direction ?? HeadSystem.Transform.Basis * _inputMove;
|
|
|
|
var acceleration = dir.Length() > 0 ? accelerationFactor : decelerationFactor;
|
|
|
|
float xAcceleration = Mathf.Lerp(Velocity.X, dir.X * _targetSpeed, delta * acceleration);
|
|
float zAcceleration = Mathf.Lerp(Velocity.Z, dir.Z * _targetSpeed, delta * acceleration);
|
|
return new Vector3(xAcceleration, 0, zAcceleration);
|
|
}
|
|
public Vector3 ComputeHVelocityGround(float delta)
|
|
{
|
|
return ComputeHVelocity(delta, AccelerationFloor, DecelerationFloor);
|
|
}
|
|
public Vector3 ComputeHVelocityAir(float delta)
|
|
{
|
|
return ComputeHVelocity(delta, AccelerationAir, DecelerationAir);
|
|
}
|
|
|
|
public void SetVerticalVelocity(float verticalVelocity)
|
|
{
|
|
Velocity = new Vector3(
|
|
x: Velocity.X,
|
|
y: verticalVelocity,
|
|
z: Velocity.Z);
|
|
}
|
|
public void SetHorizontalVelocity(Vector2 velocity)
|
|
{
|
|
Velocity = new Vector3(
|
|
x: velocity.X,
|
|
y: Velocity.Y,
|
|
z: velocity.Y);
|
|
}
|
|
|
|
public void HandleJump(float delta, float gravityFactor, int hangFrames)
|
|
{
|
|
// Update horizontal velocity
|
|
var horizontalVelocity = ComputeHVelocityAir(delta);
|
|
Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z);
|
|
|
|
// Hang time at the top of the jump
|
|
if (Velocity.Y <= Mathf.Epsilon)
|
|
{
|
|
_framesSinceJumpAtApex++;
|
|
SetVerticalVelocity(0);
|
|
}
|
|
|
|
// Cancel gravity on jump apex
|
|
var gravity = CalculateGravityForce() / gravityFactor;
|
|
var isAtApex = _framesSinceJumpAtApex > 0;
|
|
if (isAtApex)
|
|
{
|
|
gravity = 0;
|
|
}
|
|
// Update velocity accordingly
|
|
var newVerticalSpeed = Velocity.Y - gravity * delta;
|
|
SetVerticalVelocity(newVerticalSpeed);
|
|
if (IsHeadTouchingCeiling())
|
|
{
|
|
SetVerticalVelocity(Velocity.Y - 2.0f);
|
|
}
|
|
|
|
// Move back to Airborne state management when starting to go down again
|
|
if (_framesSinceJumpAtApex > hangFrames)
|
|
_playerState.SendEvent("jump_ended");
|
|
}
|
|
public void HandleSimpleJump(float delta)
|
|
{
|
|
HandleJump(delta, SimpleJumpGravityLesseningFactor, SimpleJumpHangTimeInFrames);
|
|
}
|
|
public void HandleDoubleJump(float delta)
|
|
{
|
|
HandleJump(delta, DoubleJumpGravityLesseningFactor, DoubleJumpHangTimeInFrames);
|
|
}
|
|
public void HandleMegaJump(float delta)
|
|
{
|
|
HandleJump(delta, MegaJumpGravityLesseningFactor, MegaJumpHangTimeInFrames);
|
|
}
|
|
|
|
public void PowerRecharging(float delta)
|
|
{
|
|
var progress = (float) (_powerCooldownTimer.TimeLeft / _powerCooldownTimer.WaitTime);
|
|
PowerCooldownIndicator.SetSize(new Vector2(100 * progress, 10));
|
|
}
|
|
public void StartPowerCooldown()
|
|
{
|
|
_powerCooldownTimer.Start();
|
|
PowerCooldownIndicator.Visible = true;
|
|
}
|
|
public void StopPowerCooldown()
|
|
{
|
|
_powerCooldownTimer.Stop();
|
|
PowerCooldownIndicator.Visible = false;
|
|
}
|
|
public void PowerCooldownExpired()
|
|
{
|
|
EmpoweredActionsLeft += 1;
|
|
var eventToSend = EmpoweredActionsLeft == MaxNumberOfDashActions ? "fully_charged" : "recharge";
|
|
_playerState.SendEvent(eventToSend);
|
|
}
|
|
|
|
///////////////////////////
|
|
// Input Management ///////
|
|
///////////////////////////
|
|
public void OnInputMove(Vector3 value)
|
|
{
|
|
_inputMove = value;
|
|
}
|
|
public void OnInputRotateY(float value)
|
|
{
|
|
_inputRotateY = value;
|
|
}
|
|
public void OnInputRotateFloorplane(float value)
|
|
{
|
|
_inputRotateFloorplane = value;
|
|
}
|
|
public void OnInputAimPressed()
|
|
{
|
|
_playerState.SendEvent("aim_pressed");
|
|
if (!WeaponSystem.InHandState.Active)
|
|
{
|
|
OnDashStarted();
|
|
}
|
|
}
|
|
public void OnInputAimDown()
|
|
{
|
|
_playerState.SendEvent("aim_down");
|
|
}
|
|
public void OnInputAimReleased()
|
|
{
|
|
_playerState.SendEvent("aim_released");
|
|
}
|
|
public void OnInputAimCanceled()
|
|
{
|
|
_playerState.SendEvent("cancel");
|
|
DashSystem.CancelDash();
|
|
}
|
|
public void OnInputHitPressed()
|
|
{
|
|
if (_aiming.Active)
|
|
{
|
|
OnWeaponThrown();
|
|
}
|
|
}
|
|
public void OnInputEmpowerDown()
|
|
{
|
|
_playerState.SendEvent("empower_down");
|
|
}
|
|
public void OnInputEmpowerReleased()
|
|
{
|
|
_playerState.SendEvent("empower_released");
|
|
}
|
|
|
|
private bool _dashAvailable = true;
|
|
|
|
public void OnInputDashPressed()
|
|
{
|
|
if (_empowerOn.Active && CanPerformEmpoweredAction())
|
|
{
|
|
PerformEmpoweredAction();
|
|
_playerState.SendEvent("powered_dash");
|
|
return;
|
|
}
|
|
|
|
if (_airborne.Active)
|
|
{
|
|
if (!_canDashAirborne)
|
|
return;
|
|
_canDashAirborne = false;
|
|
}
|
|
|
|
_playerState.SendEvent("dash");
|
|
}
|
|
|
|
public void OnSimpleDashStarted()
|
|
{
|
|
if (!_canDash)
|
|
return;
|
|
_canDash = false;
|
|
|
|
var dashStrength = SimpleDashStrength;
|
|
|
|
var direction = GetInputGlobalHDirection();
|
|
SetVelocity(direction * dashStrength);
|
|
}
|
|
|
|
public void HandleSimpleDash(float delta)
|
|
{
|
|
_playerState.SendEvent("dash_finished");
|
|
}
|
|
|
|
public Vector3 GetInputGlobalHDirection()
|
|
{
|
|
var direction = HeadSystem.Transform.Basis * _inputMove;
|
|
return new Vector3(direction.X, 0, direction.Z).Normalized();
|
|
}
|
|
|
|
public Vector3 GetInputLocalHDirection()
|
|
{
|
|
var direction = _inputMove;
|
|
return new Vector3(direction.X, 0, direction.Z).Normalized();
|
|
}
|
|
|
|
public void OnPoweredDashStarted()
|
|
{
|
|
Velocity = GetInputGlobalHDirection() * PoweredDashStrength;
|
|
GetTree().CreateTimer(PoweredDashTime).Timeout += FinishPoweredDash;
|
|
}
|
|
|
|
public void OnPoweredDashFinished()
|
|
{
|
|
// Try mantling here
|
|
}
|
|
|
|
public void FinishPoweredDash()
|
|
{
|
|
_playerState.SendEvent("dash_finished");
|
|
}
|
|
|
|
public void HandlePoweredDash(float delta)
|
|
{
|
|
var collision = MoveAndCollide(Velocity * delta, maxCollisions: 10);
|
|
if (collision != null)
|
|
{
|
|
FinishPoweredDash();
|
|
}
|
|
}
|
|
|
|
public bool CanPerformEmpoweredAction()
|
|
{
|
|
return EmpoweredActionsLeft > 0;
|
|
}
|
|
|
|
public void PerformEmpoweredAction()
|
|
{
|
|
_isWallJumpAvailable = true;
|
|
EmpoweredActionsLeft--;
|
|
_playerState.SendEvent(EmpoweredActionsLeft <= 0 ? "expired" : "power_used");
|
|
}
|
|
|
|
// Jumping
|
|
public void StartCoyoteTime()
|
|
{
|
|
GetTree().CreateTimer(CoyoteTime).Timeout += CoyoteExpired;
|
|
}
|
|
public void CoyoteExpired()
|
|
{
|
|
_playerState.SendEvent("coyote_expired");
|
|
}
|
|
public void JumpFromWall(bool isEmpowered)
|
|
{
|
|
if (!_isWallJumpAvailable)
|
|
return;
|
|
|
|
_isWallJumpAvailable = false;
|
|
var wallNormal = WallHugSystem.GetWallNormal().UnwrapOr(Vector3.Up);
|
|
var isLookingTowardsWall = HeadSystem.GetForwardHorizontalVector().Dot(wallNormal) > 0.5;
|
|
var jumpDirection = isLookingTowardsWall ? Vector3.Up : wallNormal;
|
|
if (isEmpowered && CanPerformEmpoweredAction())
|
|
{
|
|
PerformEmpoweredAction();
|
|
PerformJump(JumpTypes.JumpFromDash, jumpDirection);
|
|
return;
|
|
}
|
|
PerformJump(JumpTypes.JumpFromWall, jumpDirection);
|
|
}
|
|
private void PerformJump(JumpTypes jumpType, Vector3? jumpDirection = null)
|
|
{
|
|
var effectiveJumpDirection = jumpDirection ?? Vector3.Up;
|
|
var jumpVector = (effectiveJumpDirection.Normalized() + Vector3.Up).Normalized();
|
|
|
|
bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
|
|
bool isPlayerDead = HealthSystem.IsDead();
|
|
if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
|
|
Jump(jumpType, jumpVector);
|
|
}
|
|
|
|
public void Jump(JumpTypes jumpType, Vector3? jumpDirection = null, float boost = 1.0f)
|
|
{
|
|
var effectiveJumpDirection = jumpDirection ?? Vector3.Up;
|
|
var jumpForce = 100.0f;
|
|
|
|
var currentHorizontalVelocity = new Vector3(Velocity.X, 0, Velocity.Z);
|
|
var jumpVelocity = jumpForce * effectiveJumpDirection * boost;
|
|
Velocity = currentHorizontalVelocity + jumpVelocity;
|
|
}
|
|
|
|
// Mantling
|
|
public void Mantle()
|
|
{
|
|
_playerState.SendEvent("mantle");
|
|
|
|
var optionTween = FindMantle();
|
|
if (optionTween.IsSome(out var tween))
|
|
tween.Finished += MantleFinished;
|
|
}
|
|
|
|
public bool CanMantle()
|
|
{
|
|
var mantleLocationResult = MantleSystem.FindMantleInFrontOfPlayer();
|
|
return mantleLocationResult.IsSome(out _);
|
|
}
|
|
|
|
public Option<Tween> FindMantle()
|
|
{
|
|
var mantleLocationResult = MantleSystem.FindMantleInFrontOfPlayer();
|
|
if (mantleLocationResult.IsSome(out var mantleLocation))
|
|
{
|
|
var duration = 0.1f * mantleLocation.DistanceTo(Position);
|
|
var tween = TweenQueueSystem.TweenToLocation(new TweenQueueSystem.TweenInputs(mantleLocation, duration));
|
|
return tween.Some();
|
|
}
|
|
return Option<Tween>.None;
|
|
}
|
|
|
|
public void MantleFinished()
|
|
{
|
|
_playerState.SendEvent("grounded");
|
|
}
|
|
|
|
// Dashing and weapon throwing
|
|
public void OnDashStarted()
|
|
{
|
|
if (!CanPerformEmpoweredAction())
|
|
{
|
|
_playerState.SendEvent("aim_canceled");
|
|
_playerState.SendEvent("dash_ended");
|
|
DashSystem.CancelDash();
|
|
return;
|
|
}
|
|
|
|
PerformEmpoweredAction();
|
|
if (WeaponSystem.FlyingState.Active)
|
|
{
|
|
DashSystem.ShouldMantle = false;
|
|
DashSystem.PlannedPlayerLocation = WeaponSystem.GlobalPosition;
|
|
}
|
|
else if (WeaponSystem.PlantedState.Active)
|
|
{
|
|
DashSystem.ShouldMantle = false;
|
|
var dashLocation = WeaponSystem.PlantLocation;
|
|
if (WeaponSystem.IsPlantedInWall())
|
|
{
|
|
dashLocation += WeaponSystem.PlantNormal * 0.5f; // Player radius
|
|
}
|
|
|
|
if (WeaponSystem.IsPlantedUnderPlatform())
|
|
{
|
|
dashLocation += Vector3.Down * 1f; // Player height
|
|
}
|
|
|
|
DashSystem.PlannedPlayerLocation = dashLocation;
|
|
}
|
|
_dashDirection = (DashSystem.PlannedPlayerLocation - GlobalPosition).Normalized();
|
|
DashSystem.Dash();
|
|
}
|
|
public void OnDashEnded()
|
|
{
|
|
// Regular dash
|
|
if (WeaponSystem.InHandState.Active)
|
|
{
|
|
_playerState.SendEvent("dash_ended");
|
|
return;
|
|
}
|
|
|
|
// Store the weapon state before resetting it
|
|
var isPlantedOnWall = WeaponSystem.IsPlantedInWall();
|
|
var isPlantedUnderPlatform = WeaponSystem.IsPlantedUnderPlatform();
|
|
var shouldDashToHanging = isPlantedOnWall || isPlantedUnderPlatform;
|
|
var isFlying = WeaponSystem.FlyingState.Active;
|
|
|
|
// Get the weapon back
|
|
GetTree().GetRoot().RemoveChild(WeaponRoot);
|
|
AddChild(WeaponRoot);
|
|
WeaponRoot.SetGlobalPosition(GlobalPosition);
|
|
WeaponSystem.ResetWeapon();
|
|
|
|
if (isFlying)
|
|
{
|
|
var vel = _dashDirection * DashSystem.PostDashSpeed;
|
|
SetVelocity(vel);
|
|
_playerState.SendEvent("dash_ended");
|
|
return; // In case states aren't exclusives
|
|
}
|
|
|
|
if (shouldDashToHanging)
|
|
{
|
|
_playerState.SendEvent("dash_to_planted");
|
|
return; // In case states aren't exclusives
|
|
}
|
|
|
|
// Weapon planted anywhere else
|
|
_playerState.SendEvent("dash_ended");
|
|
}
|
|
public void OnWeaponThrown()
|
|
{
|
|
DashSystem.CancelDash();
|
|
_playerState.SendEvent("cancel_aim");
|
|
|
|
RemoveChild(WeaponRoot);
|
|
GetTree().GetRoot().AddChild(WeaponRoot);
|
|
WeaponRoot.SetGlobalPosition(GlobalPosition);
|
|
|
|
var weaponTargetLocation = DashSystem.HasHit ? DashSystem.CollisionPoint : DashSystem.TargetLocation;
|
|
WeaponSystem.ThrowWeapon(
|
|
weaponTargetLocation,
|
|
DashSystem.HasHit,
|
|
DashSystem.CollisionPoint,
|
|
DashSystem.CollisionNormal);
|
|
}
|
|
public void OnAimingEntered()
|
|
{
|
|
if (!WeaponSystem.InHandState.Active)
|
|
{
|
|
OnDashStarted();
|
|
return;
|
|
}
|
|
if (!isOnFloorCustom() && CanPerformEmpoweredAction())
|
|
ReduceTimeScaleWhileAiming();
|
|
}
|
|
|
|
public void OnAimingExited()
|
|
{
|
|
DashSystem.CancelDash();
|
|
}
|
|
|
|
// Regular processes
|
|
public void HandleWeaponInHand(float delta)
|
|
{
|
|
if (WeaponSystem.InHandState.Active)
|
|
RotateWeaponWithPlayer();
|
|
}
|
|
public void HandleAiming(float delta)
|
|
{
|
|
RotateWeaponWithPlayer();
|
|
|
|
if (CanPerformEmpoweredAction())
|
|
DashSystem.PrepareDash();
|
|
}
|
|
|
|
///////////////////////////
|
|
// Stateless logic ////////
|
|
///////////////////////////
|
|
private void LookAround()
|
|
{
|
|
Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane);
|
|
HeadSystem.LookAround(inputLookDir);
|
|
}
|
|
|
|
public void MoveOnGround(double delta)
|
|
{
|
|
var horizontalVelocity = ComputeHVelocityGround((float) delta);
|
|
Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z);
|
|
}
|
|
|
|
public void MoveInAir(double delta)
|
|
{
|
|
var horizontalVelocity = ComputeHVelocityAir((float) delta);
|
|
var verticalVelocity = Velocity.Y - (CalculateGravityForce() * (float)delta);
|
|
Velocity = new Vector3(horizontalVelocity.X, verticalVelocity, horizontalVelocity.Z);
|
|
}
|
|
|
|
private void HandleStairs(float delta)
|
|
{
|
|
StairsSystem.UpStairsCheckParams upStairsCheckParams = new StairsSystem.UpStairsCheckParams
|
|
{
|
|
IsOnFloorCustom = isOnFloorCustom(),
|
|
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
|
|
CurrentSpeedGreaterThanWalkSpeed = false,
|
|
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
|
Delta = (float)delta,
|
|
FloorMaxAngle = FloorMaxAngle,
|
|
GlobalPositionFromDriver = GlobalPosition,
|
|
Velocity = Velocity,
|
|
GlobalTransformFromDriver = GlobalTransform,
|
|
Rid = GetRid()
|
|
};
|
|
StairsSystem.UpStairsCheckResult upStairsCheckResult = StairsSystem.SnapUpStairsCheck(upStairsCheckParams);
|
|
|
|
if (upStairsCheckResult.UpdateRequired)
|
|
{
|
|
upStairsCheckResult.Update(this);
|
|
}
|
|
else
|
|
{
|
|
MoveAndSlide();
|
|
|
|
StairsSystem.DownStairsCheckParams downStairsCheckParams = new StairsSystem.DownStairsCheckParams
|
|
{
|
|
IsOnFloor = IsOnFloor(),
|
|
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
|
LastFrameWasOnFloor = _lastFrameWasOnFloor,
|
|
CapsuleDefaultHeight = CapsuleCollider.GetDefaultHeight(),
|
|
CurrentCapsuleHeight = CapsuleCollider.GetCurrentHeight(),
|
|
FloorMaxAngle = FloorMaxAngle,
|
|
VelocityY = Velocity.Y,
|
|
GlobalTransformFromDriver = GlobalTransform,
|
|
Rid = GetRid()
|
|
};
|
|
|
|
StairsSystem.DownStairsCheckResult downStairsCheckResult = StairsSystem.SnapDownStairsCheck(
|
|
downStairsCheckParams);
|
|
|
|
if (downStairsCheckResult.UpdateIsRequired)
|
|
{
|
|
downStairsCheckResult.Update(this);
|
|
}
|
|
}
|
|
|
|
StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams
|
|
{
|
|
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
|
|
CurrentSpeedGreaterThanWalkSpeed = false,
|
|
BetweenCrouchingAndNormalHeight = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(),
|
|
Delta = delta
|
|
};
|
|
StairsSystem.SlideCameraSmoothBackToOrigin(slideCameraParams);
|
|
}
|
|
|
|
private void CameraModifications(float delta)
|
|
{
|
|
Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams
|
|
{
|
|
Delta = delta,
|
|
IsOnFloorCustom = isOnFloorCustom(),
|
|
Velocity = Velocity
|
|
};
|
|
Bobbing.PerformCameraBobbing(cameraBobbingParams);
|
|
|
|
FieldOfView.FovParameters fovParams = new FieldOfView.FovParameters
|
|
{
|
|
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
|
Delta = delta,
|
|
SprintSpeed = WalkSpeed,
|
|
Velocity = Velocity
|
|
};
|
|
FieldOfView.PerformFovAdjustment(fovParams);
|
|
}
|
|
|
|
///////////////////////////
|
|
// Helpers ////////////////
|
|
///////////////////////////
|
|
|
|
public float CalculateGravityForce() => _gravity * Weight;
|
|
|
|
public void ReduceTimeScaleWhileAiming()
|
|
{
|
|
Engine.SetTimeScale(TimeScaleAimInAir);
|
|
_timeScaleAimInAirTimer.Start();
|
|
}
|
|
public void ResetTimeScale()
|
|
{
|
|
Engine.SetTimeScale(1);
|
|
}
|
|
|
|
private bool IsHeadTouchingCeiling()
|
|
{
|
|
for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++)
|
|
{
|
|
if (_headCollisionDetectors[i].IsColliding())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool isOnFloorCustom()
|
|
{
|
|
return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame();
|
|
}
|
|
|
|
public void RotateWeaponWithPlayer()
|
|
{
|
|
WeaponRoot.SetRotation(HeadSystem.Rotation);
|
|
}
|
|
|
|
///////////////////////////
|
|
// Processes //////////////
|
|
///////////////////////////
|
|
public override void _PhysicsProcess(double delta)
|
|
{
|
|
TweenQueueSystem.ProcessTweens();
|
|
|
|
LookAround();
|
|
// MoveAround(delta);
|
|
CameraModifications((float) delta);
|
|
HandleStairs((float) delta);
|
|
}
|
|
|
|
}
|