Files
MovementTests/player_controller/Scripts/PlayerController.cs

943 lines
28 KiB
C#

using System;
using Godot;
using GodotStateCharts;
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 DashCooldownIndicator;
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;
// Timers
private Timer _coyoteTimer;
private Timer _timeScaleAimInAirTimer;
private Timer _timeAfterDashingTimer;
private Timer _dashCooldownTimer;
private Timer _empowerTimeDownscale;
[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 AccelerationSpeedFactorFloor = 5.0f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DecelerationSpeedFactorFloor = 5.0f;
[ExportGroup("Air")]
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DecelerationSpeedFactorAir = 1.0f;
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
public float Weight { get; set; } = 3.0f;
[ExportGroup("Jump")]
[Export(PropertyHint.Range, "0,2,0.01,or_greater")]
public float StartVelocity { get; set; } = 1.0f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float DoubleJumpSpeedFactor { get; set; } = 2f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float JumpFromDashSpeedFactor { get; set; } = 2f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float JumpFromWallSpeedFactor { get; set; } = 2f;
[ExportGroup("WallHug")]
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float WallHugDownwardSpeed { 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,0.1,or_greater")]
public float MaxJumpBoostAfterDashing { get; set; } = 1f;
[Export(PropertyHint.Range, "0,5,1,or_greater")]
public int MaxNumberOfDashActions { get; set; } = 1;
[Export(PropertyHint.Range, "0,200,1,or_greater")]
public int DashIndicatorStartSize { get; set; } = 100;
[Export(PropertyHint.Range, "0,1,0.01")]
public float PerfectlyTimedActionTimer { get; set; } = 0.8f;
[Export(PropertyHint.Range, "0,50,0.1")]
public float BasicDashStrength { get; set; } = 10f;
[Export]
public Curve DashTimeDilationCurve { get; set; }
private bool _canDash = true;
private bool _isDashOnCooldown = false;
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 _dashing;
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 _doubleJumpEnabled;
private StateChartState _onWall;
private StateChartState _onWallHugCanceled;
private StateChartState _onWallHugging;
private StateChartState _onWallHanging;
private StateChartState _falling;
public override void _Ready()
{
///////////////////////////
// Getting components /////
///////////////////////////
// General use stuff
TweenQueueSystem = GetNode<TweenQueueSystem>("TweenQueueSystem");
PlayerUi = GetNode<PlayerUi>("UI");
// DashIndicator = GetNode<TextureRect>("%DashIndicator");
DashCooldownIndicator = GetNode<ColorRect>("%DashCooldownIndicator");
DashCooldownIndicator.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"));
_dashing = StateChartState.Of(GetNode("StateChart/Root/Movement/Dashing"));
// _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"));
_doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled"));
_onWall = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall"));
_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"));
_falling = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/Falling"));
// State timers
_coyoteTimer = GetNode<Timer>("CoyoteTime");
_dashCooldownTimer = GetNode<Timer>("DashCooldown");
_timeScaleAimInAirTimer = GetNode<Timer>("TimeScaleAimInAir");
_timeAfterDashingTimer = GetNode<Timer>("TimeAfterDashing");
_empowerTimeDownscale = GetNode<Timer>("EmpowerTimeDownscale");
///////////////////////////
// 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 ///////////
///////////////////////////
DashSystem.DashEnded += OnDashEnded;
DashSystem.DashProgress += OnDashProgress;
_weaponInHand.StateProcessing += HandleWeaponInHand;
_aiming.StateProcessing += HandleAiming;
_aiming.StateEntered += OnAimingEntered;
_aiming.StateExited += ResetTimeScale;
_aiming.StateExited += OnAimingExited;
_grounded.StateEntered += OnGrounded;
_grounded.StatePhysicsProcessing += HandleGrounded;
_airborne.StatePhysicsProcessing += HandleAirborne;
_onWallHugCanceled.StatePhysicsProcessing += HandleAirborne;
_onWallHugging.StatePhysicsProcessing += HandleWallHugging;
_onWallHanging.StatePhysicsProcessing += HandleWallHanging;
_coyoteEnabled.StateEntered += StartCoyoteTime;
_coyoteTimer.Timeout += CoyoteExpired;
_timeScaleAimInAirTimer.Timeout += ResetTimeScale;
_dashing.StatePhysicsProcessing += Dashing;
// _weaponThrown.StateEntered += OnWeaponThrown;
// _empowerOn.StateEntered += OnEmpowerStarted;
// _empowerOn.StateProcessing += HandleEmpower;
// _empowerOff.StateEntered += EmpowerStopped;
// _empowerTimeDownscale.Timeout += EmpowerTimerTimeout;
_powerFull.StateExited += StartDashCooldown;
_powerRecharging.StateEntered += StartDashCooldown;
_powerFull.StateEntered += StopDashCooldown;
_dashCooldownTimer.Timeout += DashCooldownExpired;
_powerRecharging.StateProcessing += PowerRecharging;
_powerExpired.StateProcessing += PowerRecharging;
}
public void PowerRecharging(float delta)
{
var progress = (float) (_dashCooldownTimer.TimeLeft / _dashCooldownTimer.WaitTime);
DashCooldownIndicator.SetSize(new Vector2(100 * progress, 10));
}
public void StartDashCooldown()
{
_dashCooldownTimer.Start();
DashCooldownIndicator.Visible = true;
}
public void StopDashCooldown()
{
_dashCooldownTimer.Stop();
DashCooldownIndicator.Visible = false;
}
public void DashCooldownExpired()
{
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 OnInputJumpPressed()
{
if (CanMantle())
{
Mantle();
return;
}
if (_grounded.Active || _coyoteEnabled.Active)
if (_empowerOn.Active && CanPerformEmpoweredAction())
{
PerformEmpoweredAction();
PerformJump(JumpTypes.JumpFromDash);
_playerState.SendEvent("megajump");
}
else
PerformJump(JumpTypes.SimpleJump);
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);
_playerState.SendEvent("jump");
}
public void OnInputDashPressed()
{
_playerState.SendEvent("dash");
PerformDash(_empowerOn.Active);
}
public void OnInputEmpowerDown()
{
_playerState.SendEvent("empower_down");
}
public void OnInputEmpowerReleased()
{
_playerState.SendEvent("empower_released");
}
public void PerformDash(bool isEmpowered)
{
if (_aiming.Active)
{
OnDashStarted();
return;
}
if (!_canDash)
return;
_canDash = false;
var dashStrength = BasicDashStrength;
if (isEmpowered && CanPerformEmpoweredAction())
{
PerformEmpoweredAction();
dashStrength *= 2.5f;
}
var direction = HeadSystem.Transform.Basis * _inputMove;
var planarDirection = new Vector3(direction.X, 0, direction.Z).Normalized();
SetVelocity(planarDirection * dashStrength);
}
public void Dashing(float delta)
{
_playerState.SendEvent("dash_ended");
}
///////////////////////////
// Stateful logic /////////
///////////////////////////
// Simple states
public void OnGrounded()
{
RestoreEmpoweredActions();
}
private void RestoreEmpoweredActions()
{
// EmpoweredActionsLeft = MaxNumberOfDashActions;
_isWallJumpAvailable = true;
}
public bool CanPerformEmpoweredAction()
{
return EmpoweredActionsLeft > 0;
}
public void PerformEmpoweredAction()
{
_isWallJumpAvailable = true;
EmpoweredActionsLeft--;
_playerState.SendEvent(EmpoweredActionsLeft <= 0 ? "expired" : "power_used");
}
// Jumping
public void StartCoyoteTime()
{
_coyoteTimer.Start();
}
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();
if (jumpType == JumpTypes.DoubleJump)
_canDash = false;
// var proportionOfTimeGone = _timeAfterDashingTimer.TimeLeft / _timeAfterDashingTimer.WaitTime;
// var actualBoost = 1 + MaxJumpBoostAfterDashing * proportionOfTimeGone;
// var makeItDouble = actualBoost > 1;
// if (makeItDouble && jumpType == MoveSystem.JumpTypes.SimpleJump)
// jumpType = MoveSystem.JumpTypes.DoubleJump; // convert simple jump to double if done right after a dash
// _timeAfterDashingTimer.Stop();
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 = 0.0f;
switch (jumpType)
{
case JumpTypes.DoubleJump:
jumpForce = CalculateDoubleJumpForce();
break;
case JumpTypes.SimpleJump:
jumpForce = CalculateJumpForce();
break;
case JumpTypes.JumpFromDash:
jumpForce = CalculateJumpFromDashForce();
break;
case JumpTypes.JumpFromWall:
jumpForce = CalculateJumpFromWallForce();
break;
default:
jumpForce = CalculateJumpForce();
break;
}
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();
_timeAfterDashingTimer.Start();
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 OnDashProgress(float progress)
{
return;
Engine.SetTimeScale(DashTimeDilationCurve.Sample(progress));
DashIndicator.SetCustomMinimumSize(Vector2.One * DashIndicatorStartSize * (1 - progress));
var indicatorColor = progress < PerfectlyTimedActionTimer ? new Color(1, 1, 1) : new Color(0, 1, 0);
DashIndicator.SetModulate(indicatorColor);
}
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");
if (isOnFloorCustom())
RestoreEmpoweredActions(); // Make sure to restore actions if we're still on the ground
}
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();
}
// Physics processes
public void HandleGrounded(float delta)
{
MoveOnGround(delta);
_canDash = true;
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);
}
///////////////////////////
// Stateless logic ////////
///////////////////////////
private void LookAround()
{
Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane);
HeadSystem.LookAround(inputLookDir);
}
public void MoveOnGround(double delta)
{
Vector3 direction = HeadSystem.Transform.Basis * _inputMove;
var accelerationFactor = direction.Length() > 0 ? AccelerationSpeedFactorFloor : DecelerationSpeedFactorFloor;
float xAcceleration = Mathf.Lerp(Velocity.X, direction.X * _targetSpeed,
(float)delta * accelerationFactor);
float zAcceleration = Mathf.Lerp(Velocity.Z, direction.Z * _targetSpeed,
(float)delta * accelerationFactor);
Velocity = new Vector3(xAcceleration, Velocity.Y, zAcceleration);
}
public void MoveInAir(double delta)
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - (CalculateGravityForce() * (float)delta),
z: Velocity.Z);
// The code below is required to quickly adjust player's position on Y-axis when there's a ceiling on the
// trajectory of player's jump and player is standing
if (IsHeadTouchingCeiling())
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - 2.0f,
z: Velocity.Z);
}
}
public void WallHug(float delta)
{
float xAcceleration = Mathf.Lerp(Velocity.X, 0,
(float)delta * WallHugHorizontalDeceleration);
float zAcceleration = Mathf.Lerp(Velocity.Z, 0,
(float)delta * WallHugHorizontalDeceleration);
Velocity = new Vector3(
x: xAcceleration,
y: -WallHugDownwardSpeed,
z: zAcceleration);
}
public void WallHang(float delta)
{
Velocity = Vector3.Zero;
MoveAndSlide();
}
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 = (float)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 = (float)delta,
SprintSpeed = WalkSpeed,
Velocity = Velocity
};
FieldOfView.PerformFovAdjustment(fovParams);
}
///////////////////////////
// Helpers ////////////////
///////////////////////////
public float CalculateJumpForce() => _gravity * StartVelocity;
public float CalculateJumpFromDashForce() => CalculateJumpForce() * JumpFromDashSpeedFactor;
public float CalculateJumpFromWallForce() => CalculateJumpForce() * JumpFromWallSpeedFactor;
public float CalculateDoubleJumpForce() => CalculateJumpForce() * DoubleJumpSpeedFactor;
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);
}
}