diff --git a/player_controller/PlayerController.tscn b/player_controller/PlayerController.tscn index c0891398..1e0829c0 100644 --- a/player_controller/PlayerController.tscn +++ b/player_controller/PlayerController.tscn @@ -480,6 +480,27 @@ to = NodePath("../OnWall/Hanging") event = &"to_planted" delay_in_seconds = "0.0" +[node name="Grounded" type="Node" parent="StateChart/Root/Movement"] +script = ExtResource("27_34snm") + +[node name="OnDash" type="Node" parent="StateChart/Root/Movement/Grounded"] +script = ExtResource("28_n7qhm") +to = NodePath("../../Dashing/Dash") +event = &"dash" +delay_in_seconds = "0.0" + +[node name="OnJump" type="Node" parent="StateChart/Root/Movement/Grounded"] +script = ExtResource("28_n7qhm") +to = NodePath("../../Jump/SimpleJump") +event = &"jump" +delay_in_seconds = "0.0" + +[node name="OnAirborne" type="Node" parent="StateChart/Root/Movement/Grounded"] +script = ExtResource("28_n7qhm") +to = NodePath("../../Airborne/CoyoteEnabled") +event = &"start_falling" +delay_in_seconds = "0.0" + [node name="Mantling" type="Node" parent="StateChart/Root/Movement"] script = ExtResource("27_34snm") @@ -514,12 +535,6 @@ to = NodePath("../../../Airborne/DoubleJumpEnabled") event = &"jump_ended" delay_in_seconds = "0.0" -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Jump/SimpleJump"] -script = ExtResource("28_n7qhm") -to = NodePath("../../MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - [node name="DoubleJump" type="Node" parent="StateChart/Root/Movement/Jump"] script = ExtResource("27_34snm") @@ -529,15 +544,6 @@ to = NodePath("../../../Airborne/Falling") event = &"jump_ended" delay_in_seconds = "0.0" -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Jump/DoubleJump"] -script = ExtResource("28_n7qhm") -to = NodePath("../../MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - -[node name="MegaJump" type="Node" parent="StateChart/Root/Movement/Jump"] -script = ExtResource("27_34snm") - [node name="Dashing" type="Node" parent="StateChart/Root/Movement"] script = ExtResource("26_infe6") initial_state = NodePath("Dash") @@ -566,33 +572,40 @@ script = ExtResource("27_34snm") [node name="ToWeaponDash" type="Node" parent="StateChart/Root/Movement/Dashing"] script = ExtResource("27_34snm") -[node name="Grounded" type="Node" parent="StateChart/Root/Movement"] +[node name="Sliding" type="Node" parent="StateChart/Root/Movement"] +script = ExtResource("26_infe6") +initial_state = NodePath("GroundSlide") + +[node name="GroundSlide" type="Node" parent="StateChart/Root/Movement/Sliding"] script = ExtResource("27_34snm") -[node name="OnDash" type="Node" parent="StateChart/Root/Movement/Grounded"] +[node name="OnSlideReleased" type="Node" parent="StateChart/Root/Movement/Sliding/GroundSlide"] script = ExtResource("28_n7qhm") -to = NodePath("../../Dashing/Dash") -event = &"dash" +to = NodePath("../../../Grounded") +event = &"slide_released" delay_in_seconds = "0.0" -[node name="OnJump" type="Node" parent="StateChart/Root/Movement/Grounded"] +[node name="OnAirborne" type="Node" parent="StateChart/Root/Movement/Sliding/GroundSlide"] script = ExtResource("28_n7qhm") -to = NodePath("../../Jump/SimpleJump") -event = &"jump" -delay_in_seconds = "0.0" - -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Grounded"] -script = ExtResource("28_n7qhm") -to = NodePath("../../Jump/MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - -[node name="OnAirborne" type="Node" parent="StateChart/Root/Movement/Grounded"] -script = ExtResource("28_n7qhm") -to = NodePath("../../Airborne/CoyoteEnabled") +to = NodePath("../../AirGlide") event = &"start_falling" delay_in_seconds = "0.0" +[node name="AirGlide" type="Node" parent="StateChart/Root/Movement/Sliding"] +script = ExtResource("27_34snm") + +[node name="OnSlideReleased" type="Node" parent="StateChart/Root/Movement/Sliding/AirGlide"] +script = ExtResource("28_n7qhm") +to = NodePath("../../../Airborne/Reset") +event = &"slide_released" +delay_in_seconds = "0.0" + +[node name="OnGrounded" type="Node" parent="StateChart/Root/Movement/Sliding/AirGlide"] +script = ExtResource("28_n7qhm") +to = NodePath("../../GroundSlide") +event = &"grounded" +delay_in_seconds = "0.0" + [node name="Airborne" type="Node" parent="StateChart/Root/Movement"] script = ExtResource("26_infe6") initial_state = NodePath("CoyoteEnabled") @@ -622,12 +635,6 @@ delay_in_seconds = "0.0" [node name="CoyoteEnabled" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("27_34snm") -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Airborne/CoyoteEnabled"] -script = ExtResource("28_n7qhm") -to = NodePath("../../../Jump/MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - [node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/CoyoteEnabled"] script = ExtResource("28_n7qhm") to = NodePath("../../../Jump/SimpleJump") @@ -645,15 +652,10 @@ script = ExtResource("27_34snm") [node name="OnWallHug" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] script = ExtResource("28_n7qhm") +to = NodePath("../../../OnWall/Hugging") event = &"wall_hug" delay_in_seconds = "0.0" -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] -script = ExtResource("28_n7qhm") -to = NodePath("../../../Jump/MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - [node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] script = ExtResource("28_n7qhm") to = NodePath("../../../Jump/DoubleJump") @@ -685,12 +687,6 @@ to = NodePath("../../Grounded") event = &"grounded" delay_in_seconds = "0.0" -[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/OnWall"] -script = ExtResource("28_n7qhm") -to = NodePath("../../Jump/MegaJump") -event = &"megajump" -delay_in_seconds = "0.0" - [node name="OnLeaveWall" type="Node" parent="StateChart/Root/Movement/OnWall"] script = ExtResource("28_n7qhm") to = NodePath("../../Airborne/Reset") @@ -705,7 +701,7 @@ delay_in_seconds = "0.0" [node name="OnJump" type="Node" parent="StateChart/Root/Movement/OnWall"] script = ExtResource("28_n7qhm") -to = NodePath("../../Jump/DoubleJump") +to = NodePath("../../Jump/SimpleJump") event = &"jump" delay_in_seconds = "0.0" diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index 2605022f..940d2e4d 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -8,6 +8,7 @@ using RustyOptions; public partial class PlayerController : CharacterBody3D { + // Enums public enum AllowedInputs { All, @@ -15,6 +16,7 @@ public partial class PlayerController : CharacterBody3D None, } private bool _isUsingGamepad; + public AllowedInputs CurrentlyAllowedInputs { get; set; } = AllowedInputs.All; public enum BufferedActions { @@ -26,7 +28,9 @@ public partial class PlayerController : CharacterBody3D } private BufferedActions _bufferedAction = BufferedActions.None; - // User API to important child nodes. + /////////////////////////// + // Public stuff // + /////////////////////////// public HeadSystem HeadSystem; public Bobbing Bobbing; public FieldOfView FieldOfView; @@ -46,32 +50,7 @@ public partial class PlayerController : CharacterBody3D public RayCast3D WallRunSnapper; public ShapeCast3D GroundDetector; - private bool _movementEnabled = true; - - private Vector3 _dashDirection = Vector3.Zero; - - private bool _shouldMantle; - private Vector3 _mantleLocation = Vector3.Zero; - private Vector3 _preMantleVelocity = 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 Vector3 _inputMoveKeyboard = Vector3.Zero; - private float _inputRotateY; - private float _inputRotateFloorplane; - - private int _framesSinceJumpAtApex; - - // Timers - private Timer _timeScaleAimInAirTimer; - private Timer _simpleDashCooldownTimer; - private Timer _airborneDashCooldownTimer; - private Timer _powerCooldownTimer; - + // Inspector stuff [Export] public Marker3D TutorialWeaponTarget; [Export] public bool TutorialDone { get; set; } @@ -119,7 +98,6 @@ public partial class PlayerController : CharacterBody3D [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")] @@ -169,10 +147,55 @@ public partial class PlayerController : CharacterBody3D [Export(PropertyHint.Range, "1,20,0.1,or_greater")] public float WallRunSpeedThreshold { get; set; } = 8f; + + + /////////////////////////// + // Private stuff // + /////////////////////////// + // Stairs and shit + private float _lastFrameWasOnFloor = -Mathf.Inf; + private const int NUM_OF_HEAD_COLLISION_DETECTORS = 4; + private RayCast3D[] _headCollisionDetectors; + + // Basic movement + private bool _movementEnabled = true; + private Vector3 _inputMove = Vector3.Zero; + private Vector3 _inputMoveKeyboard = Vector3.Zero; + private float _inputRotateY; + private float _inputRotateFloorplane; + // Basic falling private float _targetSpeed; private float _gravity; + + // Jump stuff + private int _currentInputBufferFrames; + private bool _isJumpInputPressed; + private int _framesSinceJumpAtApex; + private bool _isWallJumpAvailable = true; + // Mantle stuff + private bool _shouldMantleOnDashEnded; + private Path _mantlePath; + private bool _customMantle; + private Transform3D _customMantleStartTransform; + private Curve3D _customMantleCurve; + private Vector3 _mantleStartPosition; + private Vector3 _velocityOnMantleStarted = Vector3.Zero; + + // Wall stuff + private Vector3 _wallHugStartLocation = Vector3.Zero; + private Vector3 _wallHugStartNormal = Vector3.Zero; + private Vector3 _wallHugStartProjectedVelocity = Vector3.Zero; + private Vector3 _currentWallContactPoint = Vector3.Zero; + + // Dash stuff + private bool _canDash = true; + private bool _canDashAirborne = true; + private float _playerHeight; + private float _playerRadius; + private Vector3 _dashDirection = Vector3.Zero; + private Vector3 _preDashVelocity = Vector3.Zero; private int _empoweredActionsLeft; public int EmpoweredActionsLeft { @@ -184,18 +207,19 @@ public partial class PlayerController : CharacterBody3D } } - public AllowedInputs CurrentlyAllowedInputs { get; set; } = AllowedInputs.All; - - private bool _canDashAirborne = true; - private bool _isWallJumpAvailable = true; - private bool _canDash = true; - private bool _shouldMantleOnDashEnded; - - private Vector3 _wallHugStartLocation = Vector3.Zero; - private Vector3 _wallHugStartNormal = Vector3.Zero; - private Vector3 _wallHugStartProjectedVelocity = Vector3.Zero; - private Vector3 _currentWallContactPoint = Vector3.Zero; + // Settings + private float _lookSensitivityMultiplier = 1.0f; + private float _mouseSensitivityMultiplier = 1.0f; + private float _headBobbingMultiplier = 1.0f; + private float _fovChangeMultiplier = 1.0f; + + // Timers + private Timer _timeScaleAimInAirTimer; + private Timer _simpleDashCooldownTimer; + private Timer _airborneDashCooldownTimer; + private Timer _powerCooldownTimer; + // State chart private StateChart _playerState; private StateChartState _aiming; @@ -219,17 +243,6 @@ public partial class PlayerController : CharacterBody3D private Transition _onJumpFromWall; private Transition _onLeaveWallFromRun; - private int _currentInputBufferFrames; - - private float _playerHeight; - private float _playerRadius; - private bool _isJumpInputPressed; - - private float _lookSensitivityMultiplier = 1.0f; - private float _mouseSensitivityMultiplier = 1.0f; - private float _headBobbingMultiplier = 1.0f; - private float _fovChangeMultiplier = 1.0f; - public override void _Ready() { LoadSettings(); @@ -250,8 +263,6 @@ public partial class PlayerController : CharacterBody3D DashIndicatorMeshCylinder = DashIndicatorMesh.Mesh as CylinderMesh; DashIndicatorMesh.Visible = false; - // Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D; - // Camera stuff HeadSystem = GetNode("HeadSystem"); Bobbing = GetNode("Bobbing"); @@ -306,6 +317,7 @@ public partial class PlayerController : CharacterBody3D _onWallHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hanging")); _onWallRunning = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Running")); _onLeaveWallFromRun = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/Running/OnLeaveWall")); + // State timers _powerCooldownTimer = GetNode("PowerCooldown"); _timeScaleAimInAirTimer = GetNode("TimeScaleAimInAir"); @@ -330,7 +342,6 @@ public partial class PlayerController : CharacterBody3D WeaponSystem.Init(HeadSystem, camera); WallHugSystem.Init(); - // RPG Stuff EmpoweredActionsLeft = MaxNumberOfEmpoweredActions; if (!TutorialDone) @@ -387,6 +398,9 @@ public partial class PlayerController : CharacterBody3D _onLeaveWallFromRun.Taken += OnLeaveWallFromRun; } + /////////////////////////// + // Settings & tutorial // + /////////////////////////// public void SetAllowedInputsAll() { CurrentlyAllowedInputs = AllowedInputs.All; @@ -399,7 +413,6 @@ public partial class PlayerController : CharacterBody3D { CurrentlyAllowedInputs = AllowedInputs.None; } - public void LoadSettings() { var config = new ConfigFile(); @@ -418,12 +431,24 @@ public partial class PlayerController : CharacterBody3D _headBobbingMultiplier = (float) config.GetValue("InputSettings", "HeadBobbingWhileWalking", 1.0f); _fovChangeMultiplier = (float) config.GetValue("InputSettings", "FovChangeWithSpeed", 1.0f); } - public void OnTutorialDone(Node3D _) { TutorialDone = true; } + public void PlaceWeaponForTutorial() + { + if (TutorialDone) + return; + + RemoveChild(WeaponRoot); + GetTree().GetRoot().CallDeferred(Node.MethodName.AddChild, WeaponRoot); + WeaponRoot.CallDeferred(Node3D.MethodName.SetGlobalPosition, TutorialWeaponTarget.GlobalPosition); + WeaponSystem.CallDeferred(WeaponSystem.MethodName.PlaceWeaponForTutorial, TutorialWeaponTarget.GlobalPosition); + } + /////////////////////////// + // Grounded management // + /////////////////////////// public void OnGrounded() { _isWallJumpAvailable = true; @@ -455,25 +480,6 @@ public partial class PlayerController : CharacterBody3D _bufferedAction = BufferedActions.None; } - - public void DashCooldownTimeout() - { - _canDash = true; - } - public void AirborneDashCooldownTimeout() - { - _canDashAirborne = true; - } - - public bool IsPlayerInputtingForward() - { - return GetMoveInput().Z < -0.5f; - } - - public bool IsTryingToMantle() - { - return MantleSystem.IsMantlePossible && IsPlayerInputtingForward() && _isJumpInputPressed; - } public void HandleGrounded(float delta) { @@ -482,752 +488,11 @@ public partial class PlayerController : CharacterBody3D if (!isOnFloorCustom()) _playerState.SendEvent("start_falling"); } - - public void HandleAirborne(float delta) - { - var isGroundLike = GroundDetector.GetCollisionResult().Count > 0; - MoveInAir(delta, isGroundLike); - if (isOnFloorCustom()) - _playerState.SendEvent("grounded"); - - if (IsTryingToMantle()) _playerState.SendEvent("mantle"); - - if (!WallHugSystem.IsWallHugging()) - { - _isWallJumpAvailable = true; // reset wall jump if we left the wall - return; - } - - // Going upwards, we stay simply airborne - if (Velocity.AngleTo(Vector3.Up) < Math.PI / 4) - return; - - // Should we start a wall run - var wallNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Zero); - var isIndeedWall = wallNormal.Y < 0.1; - var hvel = new Vector3(Velocity.X, 0, Velocity.Z); - var hvelProjected = hvel.Slide(_wallHugStartNormal); - var haveEnoughSpeed = hvelProjected.Length() > WallRunSpeedThreshold; - var isCoplanarEnough = Velocity.AngleTo(wallNormal) > Math.PI/4 && Velocity.AngleTo(wallNormal) < 3*Math.PI/4; - var isGoingDownwards = Velocity.AngleTo(Vector3.Down) < Math.PI/4; - if (haveEnoughSpeed && isCoplanarEnough && !isGoingDownwards && isIndeedWall && !_coyoteEnabled.Active) - { - SetVerticalVelocity(WallRunUpwardVelocity); - _playerState.SendEvent("wall_run"); - return; - } - - // If all else fail and we go down, we hug - if (Velocity.Y < 0 && !_coyoteEnabled.Active) - { - _playerState.SendEvent("wall_hug"); - } - } - - public void HandleOnWall(float delta) - { - if (IsTryingToMantle()) _playerState.SendEvent("mantle"); - } - - public void OnWallDetected() - { - if (!_onWall.Active) - return; - - var newWallNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); - if (newWallNormal.AngleTo(_wallHugStartNormal) > Mathf.Pi/4) return; - _wallHugStartNormal = newWallNormal; - } - - public void OnWallStarted() - { - if (!WallHugSystem.IsWallHugging()) - return; - - _wallHugStartNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); - _currentWallContactPoint = WallHugSystem.WallHugLocation.UnwrapOr(Vector3.Zero); - _wallHugStartLocation = _currentWallContactPoint + _wallHugStartNormal * _playerRadius; - _wallHugStartProjectedVelocity = Velocity.Slide(_wallHugStartNormal); - } - - public void OnWallStopped() - { - } - - public void OnLeaveWallFromRun() - { - SimpleDashInDirection(Velocity.Normalized()); - } - - public void HandleWallHugging(float delta) - { - _canDash = true; - _canDashAirborne = true; - - WallHug(delta); - if (isOnFloorCustom()) - _playerState.SendEvent("grounded"); - if (!WallHugSystem.IsWallHugging()) - { - _playerState.SendEvent("start_falling"); - } - } - public void HandleWallHanging(float delta) - { - WallHang(delta); - } - - public void HandleWallRunning(float delta) - { - _canDash = false; - _canDashAirborne = false; - - // Find horizontal velocity projected on the current wall - var hvel = new Vector3(Velocity.X, 0, Velocity.Z); - var hvelProjected = hvel.Slide(_wallHugStartNormal); - - // Reorient horizontal velocity so we keep it coplanar with the wall without losing speed - var finalHVel = hvelProjected.Normalized() * hvel.Length(); - - // Adapt vertical speed - var verticalSpeed = Velocity.Y - WallRunAltitudeLossSpeed * delta; - Velocity = finalHVel + Vector3.Up*verticalSpeed; - Velocity *= 0.999f; - - _currentWallContactPoint = WallHugSystem.WallHugLocation.UnwrapOr(Vector3.Zero); - - if (isOnFloorCustom()) - _playerState.SendEvent("grounded"); - if (!WallHugSystem.IsWallHugging()) - _playerState.SendEvent("start_falling"); - } - - public void WallHug(float delta) - { - var hvel = ComputeHVelocity(delta, WallHugHorizontalDeceleration, WallHugHorizontalDeceleration); - var hvelProjected = hvel.Slide(_wallHugStartNormal); - var vvel = Velocity.Y - (CalculateGravityForce() * delta / WallHugGravityLesseningFactor); - vvel = Math.Abs(vvel) > WallHugDownwardMaxSpeed ? -WallHugDownwardMaxSpeed : vvel; - Velocity = hvelProjected + vvel*Vector3.Up; - } - - public void WallHang(float delta) - { - Velocity = Vector3.Zero; - GlobalPosition = _wallHugStartLocation; - } - - // Jump - public void OnInputJumpStarted() - { - _currentInputBufferFrames = InputBufferFrames; - _bufferedAction = _mantling.Active ? BufferedActions.MantleJump : BufferedActions.Jump; - _isJumpInputPressed = true; - - PerformJump(); - } - - public void PerformJump() - { - if (MantleSystem.IsMantlePossible) - { - _playerState.SendEvent("mantle"); - return; - } - - _playerState.SendEvent("jump"); - } - - public bool IsFacingWall() - { - return _wallHugStartNormal.Dot(GetGlobalForwardFacingVector()) < -0.5f; - } - - public void OnInputJumpOngoing() - { - } - - public void OnInputJumpEnded() - { - _isJumpInputPressed = false; - _playerState.SendEvent("jump_ended"); - } - - public void OnJumpStarted(float verticalVelocity) - { - _framesSinceJumpAtApex = 0; - SetVerticalVelocity(verticalVelocity); - } - public void OnSimpleJumpStarted() - { - OnJumpStarted(SimpleJumpStartVelocity); - } - public void OnDoubleJumpStarted() - { - _canDash = true; - // _canDashAirborne = true; - OnJumpStarted(DoubleJumpStartVelocity); - } - - 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.WallHugNormal.UnwrapOr(Vector3.Up); - var jumpVector = wallNormal * jumpStrength; - - var currentHorizontalVelocity = new Vector2(Velocity.X, Velocity.Z); - var wallJumpHorizontalVelocity = new Vector2(jumpVector.X, jumpVector.Z); - - SetHorizontalVelocity(currentHorizontalVelocity + wallJumpHorizontalVelocity); - } - - public void OnJumpFromWall() - { - if (!IsFacingWall() || (!_isWallJumpAvailable && IsFacingWall())) - { - ComputeJumpFromWallHSpeed(WallJumpStartVelocity); - } - // Remove the ability to dash straight away so you cannot scale up the wall - _canDashAirborne = false; - _airborneDashCooldownTimer.Start(); - _isWallJumpAvailable = false; - } - - public void OnInputSlideStarted() - { - } - public void OnInputSlideOngoing() - { - } - public void OnInputSlideEnded() - { - } - - public void InputDeviceChanged(bool isUsingGamepad) - { - _isUsingGamepad = isUsingGamepad; - } - - public Vector3 GetMoveInput() - { - if (_isUsingGamepad) - return _inputMove; - return _inputMoveKeyboard; - } - - public Vector3 GetGlobalMoveInput() - { - return Transform.Basis * HeadSystem.Transform.Basis * GetMoveInput(); - } - - public Vector3 ComputeHVelocity(float delta, float accelerationFactor, float decelerationFactor, Vector3? direction = null) - { - var dir = direction ?? GetGlobalMoveInput(); - - 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); - } - - private Path _mantlePath; - private bool _customMantle; - private Transform3D _customMantleStartTransform; - private Curve3D _customMantleCurve; - private Vector3 _mantleStartPosition; - - private Vector3 _velocityOnMantleStarted = Vector3.Zero; - - private bool _mantleEndedOnOtherSideOfWall; - private bool _mantleFoundGround; - public void OnMantleStarted() - { - HeadSystem.OnMantle(); - - _mantlePath = MantlePath.Instantiate() as Path; - if (_mantlePath == null) - { - GD.PrintErr("Failed to instantiate MantlePath"); - return; - } - - _velocityOnMantleStarted = Velocity; - - var transform = _customMantle ? _customMantleStartTransform : MantleSystem.GlobalTransform; - var curve = _customMantle ? _customMantleCurve : MantleSystem.MantleCurve; - _mantleEndedOnOtherSideOfWall = _customMantle ? _mantleEndedOnOtherSideOfWall : MantleSystem.EndedOnOtherSideOfWall; - _mantleFoundGround = _customMantle ? _mantleFoundGround : MantleSystem.FoundGround; - GetTree().GetRoot().AddChild(_mantlePath); - _mantlePath.Setup(transform, curve); - _mantleStartPosition = GlobalPosition; - - var curveLength = curve.GetBakedLength(); - var tween = GetTree().CreateTween(); - tween.SetTrans(Tween.TransitionType.Linear); - tween.SetEase(Tween.EaseType.In); - tween.TweenProperty(_mantlePath.PathFollow, "progress_ratio", 1, MantleTime*curveLength); - tween.Finished += MantleFinished; - } - - public void HandleMantling(float delta) - { - GlobalPosition = _mantlePath.Target.GlobalPosition; - } - - public void SimpleDashInDirection(Vector3 direction, float strength = -1) - { - if (strength < 0) strength = SimpleDashStrength; - SetVelocity(direction * strength); - } - - public void SimpleDash(float strength = -1) - { - SimpleDashInDirection(GetInputGlobalHDirection(), strength); - } - - public void MantleFinished() - { - _mantlePath.Teardown(); - - // SetVelocity(_finalCurveDirection.Normalized() * _speedOverCurve); - - var isThereMovementInput = GetMoveInput().Length() > 0; - if (isThereMovementInput) - { - // If there's a movement input on Mantle, we dash in the direction the mantle ended with - var positionDifference = GlobalPosition - _mantleStartPosition; - var directionHorizontal = new Vector3(positionDifference.X, 0, positionDifference.Z); - // SimpleDashInDirection(directionHorizontal.Normalized()); - SetVelocity(directionHorizontal.Normalized() * WalkSpeed); - } - - _customMantle = false; - _playerState.SendEvent("grounded"); - } - - public void HandleJump(float delta, float gravityFactor, int hangFrames) - { - if (IsTryingToMantle()) _playerState.SendEvent("mantle"); - - // 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 when starting to go down again or if input isn't held anymore (buffered jump) - if (_framesSinceJumpAtApex > hangFrames || !_isJumpInputPressed) - _playerState.SendEvent("jump_ended"); - } - public void HandleSimpleJump(float delta) - { - HandleJump(delta, SimpleJumpGravityLesseningFactor, SimpleJumpHangTimeInFrames); - } - public void HandleDoubleJump(float delta) - { - HandleJump(delta, DoubleJumpGravityLesseningFactor, DoubleJumpHangTimeInFrames); - } - - public void PowerRecharging(float delta) - { - var progress = (float) (_powerCooldownTimer.TimeLeft / _powerCooldownTimer.WaitTime); - PowerCooldownIndicator.SetCustomMinimumSize(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 == MaxNumberOfEmpoweredActions ? "fully_charged" : "recharge"; - _playerState.SendEvent(eventToSend); - } - - /////////////////////////// - // Input Management /////// - /////////////////////////// - - public void OnInputMoveKeyboard(Vector3 value) - { - _inputMoveKeyboard = value; - } - - 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"); - } - public void OnInputAimDown() - { - _playerState.SendEvent("aim_down"); - } - public void OnInputAimReleased() - { - _playerState.SendEvent("aim_released"); - } - public void OnInputAimCanceled() - { - _playerState.SendEvent("cancel"); - DashSystem.StopPreparingDash(); - } - public void OnInputHitPressed() - { - if (_aiming.Active) - { - ThrowWeapon(); - } - } - public void OnInputDashPressed() - { - if (_aiming.Active && CanPerformEmpoweredAction()) - { - PerformEmpoweredAction(); - _playerState.SendEvent("aimed_dash"); - _playerState.SendEvent("cancel_aim"); - return; - } - - if (_airborne.Active) - { - if (!_canDashAirborne) - return; - _canDashAirborne = false; - } - - _currentInputBufferFrames = InputBufferFrames; - _bufferedAction = _mantling.Active ? BufferedActions.MantleDash : BufferedActions.Dash; - _playerState.SendEvent("dash"); - } - - public void OnInputSlamPressed() - { - GD.Print("Slam pressed"); - } - - public void OnInputParryPressed() - { - if (WeaponSystem.FlyingState.Active) - { - DashToFlyingWeapon(); - return; - } - - if (WeaponSystem.PlantedState.Active) - { - DashToPlantedWeapon(); - return; - } - } - public void OnAimingEntered() - { - if (!CanPerformEmpoweredAction()) - return; - - DashSystem.StartPreparingDash(); - DashIndicatorMesh.Visible = true; - if (!isOnFloorCustom()) - ReduceTimeScaleWhileAiming(); - } - public void HandleAiming(float delta) - { - RotateWeaponWithPlayer(); - - DashIndicatorMeshCylinder.Height = DashSystem.PlannedLocation.DistanceTo(GlobalPosition); - DashIndicatorNode.LookAt(DashSystem.PlannedLocation); - - if (CanPerformEmpoweredAction()) - DashSystem.PrepareDash(); - } - public void OnAimingExited() - { - DashSystem.StopPreparingDash(); - - DashIndicatorMesh.Visible = false; - } - - public void DashToFlyingWeapon() - { - _playerState.SendEvent("cancel_aim"); - _playerState.SendEvent("weapon_dash"); - PerformEmpoweredAction(); - - DashSystem.ShouldMantle = false; - _dashDirection = (WeaponSystem.GlobalPosition - GlobalPosition).Normalized(); - - var dashTween = CreatePositionTween(WeaponSystem.GlobalPosition, AimedDashTime); - dashTween.Finished += DashToFlyingWeaponTweenEnded; - } - - public void DashToFlyingWeaponTweenEnded() - { - RecoverWeapon(); - - var vel = _dashDirection * PostDashSpeed; - SetVelocity(vel); - _playerState.SendEvent("dash_finished"); - } - - public void DashToPlantedWeapon() - { - _playerState.SendEvent("cancel_aim"); - _playerState.SendEvent("weapon_dash"); - PerformEmpoweredAction(); - - DashSystem.ShouldMantle = false; - var dashLocation = WeaponSystem.PlantLocation; - if (WeaponSystem.IsPlantedInWall()) - dashLocation += WeaponSystem.PlantNormal * _playerRadius; - if (WeaponSystem.IsPlantedUnderPlatform()) - dashLocation += Vector3.Down * _playerHeight; - - _wallHugStartNormal = WeaponSystem.PlantNormal; - _currentWallContactPoint = WeaponSystem.PlantLocation; - _wallHugStartLocation = dashLocation; - _wallHugStartProjectedVelocity = Velocity.Slide(_wallHugStartNormal); - - var dashTween = CreatePositionTween(dashLocation, AimedDashTime); - dashTween.Finished += DashToPlantedWeaponTweenEnded; - } - - public void RecoverWeapon() - { - RecoverChildNode(WeaponRoot); - WeaponSystem.ResetWeapon(); - } - - public void DashToPlantedWeaponTweenEnded() - { - // Store the weapon state before resetting it - var isPlantedOnWall = WeaponSystem.IsPlantedInWall(); - var isPlantedUnderPlatform = WeaponSystem.IsPlantedUnderPlatform(); - var shouldDashToHanging = isPlantedOnWall || isPlantedUnderPlatform; - - RecoverWeapon(); - - var resultingEvent = shouldDashToHanging ? "dash_to_planted" : "dash_finished"; - _playerState.SendEvent(resultingEvent); - } - - private Vector3 _preDashVelocity = Vector3.Zero; - public void OnAimedDashStarted() - { - // Adjusting for player height, where the middle of the capsule should get to the dash location instead of the - // feet of the capsule - var correction = DashSystem.CollisionNormal == Vector3.Down ? _playerHeight : DashSystem.DashCastRadius; - var correctedLocation = DashSystem.PlannedLocation + Vector3.Down * correction; - - _preDashVelocity = Velocity; - _dashDirection = (correctedLocation - GlobalPosition).Normalized(); - - var dashTween = CreatePositionTween(correctedLocation, AimedDashTime); - // dashTween.TweenMethod(Callable.From(AimedDashTweenOngoing), 0.0f, 1.0f, AimedDashTime); - dashTween.Finished += AimedDashTweenEnded; - - _customMantle = DashSystem.ShouldMantle; - _customMantleCurve = DashSystem.MantleSystem.MantleCurve; - _customMantleStartTransform = DashSystem.MantleSystem.GlobalTransform; - _mantleEndedOnOtherSideOfWall = DashSystem.MantleSystem.EndedOnOtherSideOfWall; - _mantleFoundGround = DashSystem.MantleSystem.FoundGround; - } - - Tween CreatePositionTween(Vector3 targetLocation, float tweenTime) - { - var tween = GetTree().CreateTween(); - tween.SetParallel(); - tween.SetTrans(Tween.TransitionType.Cubic); - tween.SetEase(Tween.EaseType.InOut); - tween.TweenProperty(this, "global_position", targetLocation, tweenTime); - - return tween; - } - - public void AimedDashTweenEnded() - { - _playerState.SendEvent("dash_finished"); - } - - public void OnAimedDashFinished() - { - var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? PostDashSpeed : _preDashVelocity.Length(); - Velocity = _dashDirection * postDashVelocity; - if (_customMantle) _playerState.SendEvent("mantle"); - } - - public void PlaceWeaponForTutorial() - { - if (TutorialDone) - return; - - RemoveChild(WeaponRoot); - GetTree().GetRoot().CallDeferred(Node.MethodName.AddChild, WeaponRoot); - WeaponRoot.CallDeferred(Node3D.MethodName.SetGlobalPosition, TutorialWeaponTarget.GlobalPosition); - WeaponSystem.CallDeferred(WeaponSystem.MethodName.PlaceWeaponForTutorial, TutorialWeaponTarget.GlobalPosition); - } - - public void RemoveChildNode(Node3D node) - { - RemoveChild(node); - GetTree().GetRoot().AddChild(node); - node.SetGlobalPosition(GlobalPosition); - } - - public void RecoverChildNode(Node3D node) - { - GetTree().GetRoot().RemoveChild(node); - AddChild(node); - node.SetGlobalPosition(GlobalPosition); - } - - public void ThrowWeapon() - { - _playerState.SendEvent("cancel_aim"); - RemoveChildNode(WeaponRoot); - - var weaponTargetLocation = DashSystem.HasHit ? DashSystem.CollisionPoint : DashSystem.PlannedLocation; - WeaponSystem.ThrowWeapon( - weaponTargetLocation, - DashSystem.HasHit, - DashSystem.CollisionPoint, - DashSystem.CollisionNormal); - } - - public void OnSimpleDashStarted() - { - if (!_canDash) - return; - _canDash = false; - SimpleDash(); - } - - public void HandleSimpleDash(float delta) - { - _playerState.SendEvent("dash_finished"); - } - - public Vector3 GetInputGlobalHDirection() - { - var direction = Transform.Basis * HeadSystem.Transform.Basis * GetMoveInput(); - return new Vector3(direction.X, 0, direction.Z).Normalized(); - } - - public Vector3 GetInputLocalHDirection() - { - var direction = GetMoveInput(); - return new Vector3(direction.X, 0, direction.Z).Normalized(); - } - - public bool CanPerformEmpoweredAction() - { - return EmpoweredActionsLeft > 0 && TutorialDone; - } - - public void PerformEmpoweredAction() - { - _isWallJumpAvailable = true; - EmpoweredActionsLeft--; - _playerState.SendEvent(EmpoweredActionsLeft <= 0 ? "expired" : "power_used"); - } - - public void StartCoyoteTime() - { - GetTree().CreateTimer(CoyoteTime).Timeout += CoyoteExpired; - } - public void CoyoteExpired() - { - _playerState.SendEvent("coyote_expired"); - } - - /////////////////////////// - // Stateless logic //////// - /////////////////////////// - private void LookAround(double delta) - { - Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); - var lookSensitivity = _isUsingGamepad ? _lookSensitivityMultiplier : _mouseSensitivityMultiplier; - - var wallHugContactPoint = _onWallRunning.Active ? _currentWallContactPoint : Vector3.Zero; - var playerVelocity = GetGlobalMoveInput(); - HeadSystem.LookAround(delta, inputLookDir, playerVelocity, wallHugContactPoint, lookSensitivity); - } - public void MoveOnGround(double delta) { var horizontalVelocity = ComputeHVelocityGround((float) delta); Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z); } - - public void MoveInAir(double delta, bool isGroundLike = false) - { - var horizontalVelocity = isGroundLike ? ComputeHVelocityGround((float) delta) : ComputeHVelocityAir((float) delta); - var verticalVelocity = Velocity.Y - (CalculateGravityForce() * (float)delta); - Velocity = new Vector3(horizontalVelocity.X, verticalVelocity, horizontalVelocity.Z); - } private void HandleStairs(float delta) { @@ -1285,7 +550,152 @@ public partial class PlayerController : CharacterBody3D }; StairsSystem.SlideCameraSmoothBackToOrigin(slideCameraParams); } + 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(); + } + + /////////////////////////// + // Airborne management // + /////////////////////////// + public void HandleAirborne(float delta) + { + var isGroundLike = GroundDetector.GetCollisionResult().Count > 0; + MoveInAir(delta, isGroundLike); + if (isOnFloorCustom()) + _playerState.SendEvent("grounded"); + + if (IsTryingToMantle()) _playerState.SendEvent("mantle"); + + if (!WallHugSystem.IsWallHugging()) + { + _isWallJumpAvailable = true; // reset wall jump if we left the wall + return; + } + + // Going upwards, we stay simply airborne + if (Velocity.AngleTo(Vector3.Up) < Math.PI / 4) + return; + + // Should we start a wall run + var wallNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Zero); + var isIndeedWall = wallNormal.Y < 0.1; + var hvel = new Vector3(Velocity.X, 0, Velocity.Z); + var hvelProjected = hvel.Slide(_wallHugStartNormal); + var haveEnoughSpeed = hvelProjected.Length() > WallRunSpeedThreshold; + var isCoplanarEnough = Velocity.AngleTo(wallNormal) > Math.PI/4 && Velocity.AngleTo(wallNormal) < 3*Math.PI/4; + var isGoingDownwards = Velocity.AngleTo(Vector3.Down) < Math.PI/4; + if (haveEnoughSpeed && isCoplanarEnough && !isGoingDownwards && isIndeedWall && !_coyoteEnabled.Active) + { + SetVerticalVelocity(WallRunUpwardVelocity); + _playerState.SendEvent("wall_run"); + return; + } + + // If all else fail and we go down, we hug + if (Velocity.Y < 0 && !_coyoteEnabled.Active) + { + _playerState.SendEvent("wall_hug"); + } + } + public void MoveInAir(double delta, bool isGroundLike = false) + { + var horizontalVelocity = isGroundLike ? ComputeHVelocityGround((float) delta) : ComputeHVelocityAir((float) delta); + var verticalVelocity = Velocity.Y - (CalculateGravityForce() * (float)delta); + Velocity = new Vector3(horizontalVelocity.X, verticalVelocity, horizontalVelocity.Z); + } + + /////////////////////////// + // Movement input // + /////////////////////////// + public bool IsPlayerInputtingForward() + { + return GetMoveInput().Z < -0.5f; + } + public void InputDeviceChanged(bool isUsingGamepad) + { + _isUsingGamepad = isUsingGamepad; + } + + public Vector3 GetMoveInput() + { + if (_isUsingGamepad) + return _inputMove; + return _inputMoveKeyboard; + } + public Vector3 GetGlobalMoveInput() + { + return Transform.Basis * HeadSystem.Transform.Basis * GetMoveInput(); + } + public Vector3 GetInputGlobalHDirection() + { + var direction = Transform.Basis * HeadSystem.Transform.Basis * GetMoveInput(); + return new Vector3(direction.X, 0, direction.Z).Normalized(); + } + public Vector3 GetInputLocalHDirection() + { + var direction = GetMoveInput(); + return new Vector3(direction.X, 0, direction.Z).Normalized(); + } + + public void OnInputMoveKeyboard(Vector3 value) + { + _inputMoveKeyboard = value; + } + public void OnInputMove(Vector3 value) + { + _inputMove = value; + } + public void OnInputRotateY(float value) + { + _inputRotateY = value; + } + public void OnInputRotateFloorplane(float value) + { + _inputRotateFloorplane = value; + } + + public bool IsTryingToMantle() + { + return MantleSystem.IsMantlePossible && IsPlayerInputtingForward() && _isJumpInputPressed; + } + + /////////////////////////// + // Utilities // + /////////////////////////// + + public float CalculateGravityForce() => _gravity * Weight; + + // Camera stuff + private void LookAround(double delta) + { + Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); + var lookSensitivity = _isUsingGamepad ? _lookSensitivityMultiplier : _mouseSensitivityMultiplier; + + var wallHugContactPoint = _onWallRunning.Active ? _currentWallContactPoint : Vector3.Zero; + var playerVelocity = GetGlobalMoveInput(); + HeadSystem.LookAround(delta, inputLookDir, playerVelocity, wallHugContactPoint, lookSensitivity); + } + public void RotateWeaponWithPlayer() + { + WeaponRoot.SetRotation(HeadSystem.Rotation); + } + public Vector3 GetGlobalForwardFacingVector() + { + return Transform.Basis * HeadSystem.Transform.Basis * Vector3.Forward; + } private void CameraModifications(float delta) { Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams @@ -1307,12 +717,461 @@ public partial class PlayerController : CharacterBody3D }; FieldOfView.PerformFovAdjustment(fovParams); } - - /////////////////////////// - // Helpers //////////////// - /////////////////////////// - public float CalculateGravityForce() => _gravity * Weight; + // Horizontal velocity computing + public Vector3 ComputeHVelocity(float delta, float accelerationFactor, float decelerationFactor, Vector3? direction = null) + { + var dir = direction ?? GetGlobalMoveInput(); + + 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); + } + + // Velocity setters + 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); + } + + // Tweens + Tween CreatePositionTween(Vector3 targetLocation, float tweenTime) + { + var tween = GetTree().CreateTween(); + tween.SetParallel(); + tween.SetTrans(Tween.TransitionType.Cubic); + tween.SetEase(Tween.EaseType.InOut); + tween.TweenProperty(this, "global_position", targetLocation, tweenTime); + + return tween; + } + + // Child management + public void RemoveChildNode(Node3D node) + { + RemoveChild(node); + GetTree().GetRoot().AddChild(node); + node.SetGlobalPosition(GlobalPosition); + } + public void RecoverChildNode(Node3D node) + { + GetTree().GetRoot().RemoveChild(node); + AddChild(node); + node.SetGlobalPosition(GlobalPosition); + } + + /////////////////////////// + // Dash management // + /////////////////////////// + public void DashCooldownTimeout() + { + _canDash = true; + } + public void AirborneDashCooldownTimeout() + { + _canDashAirborne = true; + } + + public void OnInputDashPressed() + { + if (_aiming.Active && CanPerformEmpoweredAction()) + { + PerformEmpoweredAction(); + _playerState.SendEvent("aimed_dash"); + _playerState.SendEvent("cancel_aim"); + return; + } + + if (_airborne.Active) + { + if (!_canDashAirborne) + return; + _canDashAirborne = false; + } + + _currentInputBufferFrames = InputBufferFrames; + _bufferedAction = _mantling.Active ? BufferedActions.MantleDash : BufferedActions.Dash; + _playerState.SendEvent("dash"); + } + public void SimpleDashInDirection(Vector3 direction, float strength = -1) + { + if (strength < 0) strength = SimpleDashStrength; + SetVelocity(direction * strength); + } + public void SimpleDash(float strength = -1) + { + SimpleDashInDirection(GetInputGlobalHDirection(), strength); + } + + public void OnSimpleDashStarted() + { + if (!_canDash) + return; + _canDash = false; + SimpleDash(); + } + public void HandleSimpleDash(float delta) + { + _playerState.SendEvent("dash_finished"); + } + + /////////////////////////// + // On wall management // + /////////////////////////// + public void HandleOnWall(float delta) + { + if (IsTryingToMantle()) _playerState.SendEvent("mantle"); + } + public void OnWallDetected() + { + if (!_onWall.Active) + return; + + var newWallNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); + if (newWallNormal.AngleTo(_wallHugStartNormal) > Mathf.Pi/4) return; + _wallHugStartNormal = newWallNormal; + } + public void OnWallStarted() + { + if (!WallHugSystem.IsWallHugging()) + return; + + _wallHugStartNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); + _currentWallContactPoint = WallHugSystem.WallHugLocation.UnwrapOr(Vector3.Zero); + _wallHugStartLocation = _currentWallContactPoint + _wallHugStartNormal * _playerRadius; + _wallHugStartProjectedVelocity = Velocity.Slide(_wallHugStartNormal); + } + public void OnWallStopped() + { + } + public void OnLeaveWallFromRun() + { + SimpleDashInDirection(Velocity.Normalized()); + } + public void HandleWallHugging(float delta) + { + _canDash = true; + _canDashAirborne = true; + + WallHug(delta); + if (isOnFloorCustom()) + _playerState.SendEvent("grounded"); + if (!WallHugSystem.IsWallHugging()) + { + _playerState.SendEvent("start_falling"); + } + } + public void HandleWallHanging(float delta) + { + WallHang(delta); + } + public void HandleWallRunning(float delta) + { + _canDash = false; + _canDashAirborne = false; + + // Find horizontal velocity projected on the current wall + var hvel = new Vector3(Velocity.X, 0, Velocity.Z); + var hvelProjected = hvel.Slide(_wallHugStartNormal); + + // Reorient horizontal velocity so we keep it coplanar with the wall without losing speed + var finalHVel = hvelProjected.Normalized() * hvel.Length(); + + // Adapt vertical speed + var verticalSpeed = Velocity.Y - WallRunAltitudeLossSpeed * delta; + Velocity = finalHVel + Vector3.Up*verticalSpeed; + Velocity *= 0.999f; + + _currentWallContactPoint = WallHugSystem.WallHugLocation.UnwrapOr(Vector3.Zero); + + if (isOnFloorCustom()) + _playerState.SendEvent("grounded"); + if (!WallHugSystem.IsWallHugging()) + _playerState.SendEvent("start_falling"); + } + public void WallHug(float delta) + { + var hvel = ComputeHVelocity(delta, WallHugHorizontalDeceleration, WallHugHorizontalDeceleration); + var hvelProjected = hvel.Slide(_wallHugStartNormal); + var vvel = Velocity.Y - (CalculateGravityForce() * delta / WallHugGravityLesseningFactor); + vvel = Math.Abs(vvel) > WallHugDownwardMaxSpeed ? -WallHugDownwardMaxSpeed : vvel; + Velocity = hvelProjected + vvel*Vector3.Up; + } + public void WallHang(float delta) + { + Velocity = Vector3.Zero; + GlobalPosition = _wallHugStartLocation; + } + public bool IsFacingWall() + { + return _wallHugStartNormal.Dot(GetGlobalForwardFacingVector()) < -0.5f; + } + + /////////////////////////// + // Jump management // + /////////////////////////// + public void StartCoyoteTime() + { + GetTree().CreateTimer(CoyoteTime).Timeout += CoyoteExpired; + } + public void CoyoteExpired() + { + _playerState.SendEvent("coyote_expired"); + } + + public void OnInputJumpStarted() + { + _currentInputBufferFrames = InputBufferFrames; + _bufferedAction = _mantling.Active ? BufferedActions.MantleJump : BufferedActions.Jump; + _isJumpInputPressed = true; + + PerformJump(); + } + public void OnInputJumpOngoing() + { + } + public void OnInputJumpEnded() + { + _isJumpInputPressed = false; + _playerState.SendEvent("jump_ended"); + } + + public void PerformJump() + { + if (MantleSystem.IsMantlePossible) + { + _playerState.SendEvent("mantle"); + return; + } + + _playerState.SendEvent("jump"); + } + + public void OnJumpStarted(float verticalVelocity) + { + _framesSinceJumpAtApex = 0; + SetVerticalVelocity(verticalVelocity); + } + public void OnSimpleJumpStarted() + { + OnJumpStarted(SimpleJumpStartVelocity); + } + public void OnDoubleJumpStarted() + { + _canDash = true; + // _canDashAirborne = true; + OnJumpStarted(DoubleJumpStartVelocity); + } + + public void HandleJump(float delta, float gravityFactor, int hangFrames) + { + if (IsTryingToMantle()) _playerState.SendEvent("mantle"); + + // 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 when starting to go down again or if input isn't held anymore (buffered jump) + if (_framesSinceJumpAtApex > hangFrames || !_isJumpInputPressed) + _playerState.SendEvent("jump_ended"); + } + public void HandleSimpleJump(float delta) + { + HandleJump(delta, SimpleJumpGravityLesseningFactor, SimpleJumpHangTimeInFrames); + } + public void HandleDoubleJump(float delta) + { + HandleJump(delta, DoubleJumpGravityLesseningFactor, DoubleJumpHangTimeInFrames); + } + + // Jump and wall stuff + public void ComputeJumpFromWallHSpeed(float jumpStrength) + { + var wallNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); + var jumpVector = wallNormal * jumpStrength; + + var currentHorizontalVelocity = new Vector2(Velocity.X, Velocity.Z); + var wallJumpHorizontalVelocity = new Vector2(jumpVector.X, jumpVector.Z); + + SetHorizontalVelocity(currentHorizontalVelocity + wallJumpHorizontalVelocity); + } + public void OnJumpFromWall() + { + if (!IsFacingWall() || (!_isWallJumpAvailable && IsFacingWall())) + { + ComputeJumpFromWallHSpeed(WallJumpStartVelocity); + } + // Remove the ability to dash straight away so you cannot scale up the wall + _canDashAirborne = false; + _airborneDashCooldownTimer.Start(); + _isWallJumpAvailable = false; + } + + /////////////////////////// + // Mantle management // + /////////////////////////// + public void OnMantleStarted() + { + HeadSystem.OnMantle(); + + _mantlePath = MantlePath.Instantiate() as Path; + if (_mantlePath == null) + { + GD.PrintErr("Failed to instantiate MantlePath"); + return; + } + + _velocityOnMantleStarted = Velocity; + + var transform = _customMantle ? _customMantleStartTransform : MantleSystem.GlobalTransform; + var curve = _customMantle ? _customMantleCurve : MantleSystem.MantleCurve; + GetTree().GetRoot().AddChild(_mantlePath); + _mantlePath.Setup(transform, curve); + _mantleStartPosition = GlobalPosition; + + var curveLength = curve.GetBakedLength(); + var tween = GetTree().CreateTween(); + tween.SetTrans(Tween.TransitionType.Linear); + tween.SetEase(Tween.EaseType.In); + tween.TweenProperty(_mantlePath.PathFollow, "progress_ratio", 1, MantleTime*curveLength); + tween.Finished += MantleFinished; + } + public void HandleMantling(float delta) + { + GlobalPosition = _mantlePath.Target.GlobalPosition; + } + public void MantleFinished() + { + _mantlePath.Teardown(); + + // SetVelocity(_finalCurveDirection.Normalized() * _speedOverCurve); + + var isThereMovementInput = GetMoveInput().Length() > 0; + if (isThereMovementInput) + { + // If there's a movement input on Mantle, we dash in the direction the mantle ended with + var positionDifference = GlobalPosition - _mantleStartPosition; + var directionHorizontal = new Vector3(positionDifference.X, 0, positionDifference.Z); + // SimpleDashInDirection(directionHorizontal.Normalized()); + SetVelocity(directionHorizontal.Normalized() * WalkSpeed); + } + + _customMantle = false; + _playerState.SendEvent("grounded"); + } + + + /////////////////////////// + // Slide management // + /////////////////////////// + public void OnInputSlideStarted() + { + } + public void OnInputSlideOngoing() + { + } + public void OnInputSlideEnded() + { + } + + /////////////////////////// + // Empowerement management // + /////////////////////////// + public void PowerRecharging(float delta) + { + var progress = (float) (_powerCooldownTimer.TimeLeft / _powerCooldownTimer.WaitTime); + PowerCooldownIndicator.SetCustomMinimumSize(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 == MaxNumberOfEmpoweredActions ? "fully_charged" : "recharge"; + _playerState.SendEvent(eventToSend); + } + + public bool CanPerformEmpoweredAction() + { + return EmpoweredActionsLeft > 0 && TutorialDone; + } + public void PerformEmpoweredAction() + { + _isWallJumpAvailable = true; + EmpoweredActionsLeft--; + _playerState.SendEvent(EmpoweredActionsLeft <= 0 ? "expired" : "power_used"); + } + + /////////////////////////// + // Aim Management /////// + /////////////////////////// + public void OnInputAimPressed() + { + _playerState.SendEvent("aim_pressed"); + } + public void OnInputAimDown() + { + _playerState.SendEvent("aim_down"); + } + public void OnInputAimReleased() + { + _playerState.SendEvent("aim_released"); + } + public void OnInputAimCanceled() + { + _playerState.SendEvent("cancel"); + DashSystem.StopPreparingDash(); + } public void ReduceTimeScaleWhileAiming() { @@ -1324,32 +1183,174 @@ public partial class PlayerController : CharacterBody3D Engine.SetTimeScale(1); } - private bool IsHeadTouchingCeiling() + public void OnAimingEntered() { - for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++) + if (!CanPerformEmpoweredAction()) + return; + + DashSystem.StartPreparingDash(); + DashIndicatorMesh.Visible = true; + if (!isOnFloorCustom()) + ReduceTimeScaleWhileAiming(); + } + public void HandleAiming(float delta) + { + RotateWeaponWithPlayer(); + + DashIndicatorMeshCylinder.Height = DashSystem.PlannedLocation.DistanceTo(GlobalPosition); + DashIndicatorNode.LookAt(DashSystem.PlannedLocation); + + if (CanPerformEmpoweredAction()) + DashSystem.PrepareDash(); + } + public void OnAimingExited() + { + DashSystem.StopPreparingDash(); + + DashIndicatorMesh.Visible = false; + } + + /////////////////////////// + // Hit Management /////// + /////////////////////////// + public void OnInputHitPressed() + { + if (_aiming.Active) { - if (_headCollisionDetectors[i].IsColliding()) - { - return true; - } + ThrowWeapon(); + } + } + + /////////////////////////// + // Slam Management /////// + /////////////////////////// + public void OnInputSlamPressed() + { + GD.Print("Slam pressed"); + } + + /////////////////////////// + // Parry Management /////// + /////////////////////////// + public void OnInputParryPressed() + { + if (WeaponSystem.FlyingState.Active) + { + DashToFlyingWeapon(); + return; } - return false; + if (WeaponSystem.PlantedState.Active) + { + DashToPlantedWeapon(); + } } - - private bool isOnFloorCustom() + + /////////////////////////// + // Powered dash /////// + /////////////////////////// + public void OnAimedDashStarted() { - return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame(); + // Adjusting for player height, where the middle of the capsule should get to the dash location instead of the + // feet of the capsule + var correction = DashSystem.CollisionNormal == Vector3.Down ? _playerHeight : DashSystem.DashCastRadius; + var correctedLocation = DashSystem.PlannedLocation + Vector3.Down * correction; + + _preDashVelocity = Velocity; + _dashDirection = (correctedLocation - GlobalPosition).Normalized(); + + var dashTween = CreatePositionTween(correctedLocation, AimedDashTime); + // dashTween.TweenMethod(Callable.From(AimedDashTweenOngoing), 0.0f, 1.0f, AimedDashTime); + dashTween.Finished += AimedDashTweenEnded; + + _customMantle = DashSystem.ShouldMantle; + _customMantleCurve = DashSystem.MantleSystem.MantleCurve; + _customMantleStartTransform = DashSystem.MantleSystem.GlobalTransform; + } + public void AimedDashTweenEnded() + { + _playerState.SendEvent("dash_finished"); + } + public void OnAimedDashFinished() + { + var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? PostDashSpeed : _preDashVelocity.Length(); + Velocity = _dashDirection * postDashVelocity; + if (_customMantle) _playerState.SendEvent("mantle"); + } + + // Weapon dashing + + public void ThrowWeapon() + { + _playerState.SendEvent("cancel_aim"); + RemoveChildNode(WeaponRoot); + + var weaponTargetLocation = DashSystem.HasHit ? DashSystem.CollisionPoint : DashSystem.PlannedLocation; + WeaponSystem.ThrowWeapon( + weaponTargetLocation, + DashSystem.HasHit, + DashSystem.CollisionPoint, + DashSystem.CollisionNormal); + } + public void RecoverWeapon() + { + RecoverChildNode(WeaponRoot); + WeaponSystem.ResetWeapon(); + } + + public void DashToFlyingWeapon() + { + _playerState.SendEvent("cancel_aim"); + _playerState.SendEvent("weapon_dash"); + PerformEmpoweredAction(); + + DashSystem.ShouldMantle = false; + _dashDirection = (WeaponSystem.GlobalPosition - GlobalPosition).Normalized(); + + var dashTween = CreatePositionTween(WeaponSystem.GlobalPosition, AimedDashTime); + dashTween.Finished += DashToFlyingWeaponTweenEnded; + } + public void DashToFlyingWeaponTweenEnded() + { + RecoverWeapon(); + + var vel = _dashDirection * PostDashSpeed; + SetVelocity(vel); + _playerState.SendEvent("dash_finished"); } - public void RotateWeaponWithPlayer() + public void DashToPlantedWeapon() { - WeaponRoot.SetRotation(HeadSystem.Rotation); + _playerState.SendEvent("cancel_aim"); + _playerState.SendEvent("weapon_dash"); + PerformEmpoweredAction(); + + DashSystem.ShouldMantle = false; + var dashLocation = WeaponSystem.PlantLocation; + if (WeaponSystem.IsPlantedInWall()) + dashLocation += WeaponSystem.PlantNormal * _playerRadius; + if (WeaponSystem.IsPlantedUnderPlatform()) + dashLocation += Vector3.Down * _playerHeight; + + _wallHugStartNormal = WeaponSystem.PlantNormal; + _currentWallContactPoint = WeaponSystem.PlantLocation; + _wallHugStartLocation = dashLocation; + _wallHugStartProjectedVelocity = Velocity.Slide(_wallHugStartNormal); + + var dashTween = CreatePositionTween(dashLocation, AimedDashTime); + dashTween.Finished += DashToPlantedWeaponTweenEnded; } - - public Vector3 GetGlobalForwardFacingVector() + public void DashToPlantedWeaponTweenEnded() { - return Transform.Basis * HeadSystem.Transform.Basis * Vector3.Forward; + // Store the weapon state before resetting it + var isPlantedOnWall = WeaponSystem.IsPlantedInWall(); + var isPlantedUnderPlatform = WeaponSystem.IsPlantedUnderPlatform(); + var shouldDashToHanging = isPlantedOnWall || isPlantedUnderPlatform; + + RecoverWeapon(); + + var resultingEvent = shouldDashToHanging ? "dash_to_planted" : "dash_finished"; + _playerState.SendEvent(resultingEvent); } /////////////////////////// diff --git a/systems/mantle/MantleSystem.cs b/systems/mantle/MantleSystem.cs index 68c3b33e..dd115b41 100644 --- a/systems/mantle/MantleSystem.cs +++ b/systems/mantle/MantleSystem.cs @@ -81,19 +81,6 @@ public partial class MantleSystem: Node3D // Got to the other side of the wall, we stop there if (!wallProfileShapecast.IsColliding()) { - /*EndedOnOtherSideOfWall = true; - - var origin = globalTargetPosition; - var end = origin + Vector3.Down*0.51f; // We check for the ground a bit below our target - var groundQuery = PhysicsRayQueryParameters3D.Create(origin, end, wallProfileShapecast.CollisionMask); - var groundResult = spaceState.IntersectRay(groundQuery); - if (groundResult.Count > 0) - { - // We found the ground, this is our final location - FoundGround = true; - Vector3 position = (Vector3) groundResult["position"]; - MantleCurve.AddPoint(ToLocal(position)); - }*/ break; }