using System; using Godot; using GodotStateCharts; using Movementtests.systems; using Movementtests.player_controller.Scripts; using RustyOptions; 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 Gravity Gravity; public HealthSystem HealthSystem; public MoveSystem MoveSystem; public TweenQueueSystem TweenQueueSystem; public Node3D WeaponRoot; public WeaponSystem WeaponSystem; public WallHugSystem WallHugSystem; public PlayerUi PlayerUi; public TextureRect DashIndicator; 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; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float TimeScaleAimInAir { get; set; } = 0.2f; [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 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 _grounded; private StateChartState _crouched; private StateChartState _standing; private StateChartState _mantling; private StateChartState _movHanging; private StateChartState _airborne; private StateChartState _coyoteEnabled; private StateChartState _doubleJumpEnabled; private StateChartState _onWall; private StateChartState _onWallHugging; private StateChartState _onWallHanging; private StateChartState _falling; public override void _Ready() { /////////////////////////// // Getting components ///// /////////////////////////// // General use stuff TweenQueueSystem = GetNode("TweenQueueSystem"); PlayerUi = GetNode("UI"); DashIndicator = GetNode("%DashIndicator"); // Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D; // Camera stuff HeadSystem = GetNode("HeadSystem"); Bobbing = GetNode("Bobbing"); FieldOfView = GetNode("FieldOfView"); Camera3D camera = GetNode("HeadSystem/CameraSmooth/Camera3D"); Node3D cameraSmooth = GetNode("HeadSystem/CameraSmooth"); ColorRect vignetteRect = GetNode( "HeadSystem/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect"); ColorRect distortionRect = GetNode( "HeadSystem/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect"); ColorRect blurRect = GetNode("HeadSystem/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect"); // Movement stuff WeaponRoot = GetNode("WeaponRoot"); WeaponSystem = GetNode("WeaponRoot/WeaponSystem"); MantleSystem = GetNode("MantleSystem"); CapsuleCollider = GetNode("CapsuleCollider"); Gravity = GetNode("Gravity"); MoveSystem = GetNode("MoveSystem"); DashSystem = GetNode("DashSystem"); StairsSystem = GetNode("StairsSystem"); WallHugSystem = GetNode("WallHugSystem"); RayCast3D stairsBelowRayCast3D = GetNode("StairsBelowRayCast3D"); RayCast3D stairsAheadRayCast3D = GetNode("StairsAheadRayCast3D"); _headCollisionDetectors = new RayCast3D[NUM_OF_HEAD_COLLISION_DETECTORS]; for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++) { _headCollisionDetectors[i] = GetNode( "HeadCollisionDetectors/HeadCollisionDetector" + i); } // RPG Stuff Stamina = GetNode("Stamina"); HealthSystem = GetNode("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")); _grounded = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded")); _standing = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded/Standing")); _crouched = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded/Crouched")); _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")); _onWallHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hanging")); _falling = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/Falling")); // State timers _coyoteTimer = GetNode("CoyoteTime"); _dashCooldownTimer = GetNode("DashCooldown"); _timeScaleAimInAirTimer = GetNode("TimeScaleAimInAir"); _timeAfterDashingTimer = GetNode("TimeAfterDashing"); _empowerTimeDownscale = GetNode("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 float gravitySetting = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); Gravity.Init(gravitySetting); MantleSystem.Init(HeadSystem); var moveSystemParams = new MoveSystem.MoveSystemParameters(this, Gravity, MantleSystem, TweenQueueSystem, HeadSystem, CapsuleCollider); MoveSystem.Init(moveSystemParams); StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth); DashSystem.Init(HeadSystem, camera, TweenQueueSystem); WeaponSystem.Init(HeadSystem, camera); WallHugSystem.Init(); // RPG Stuff HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams() { Gravity = Gravity, Parent = this, Camera = camera, Head = HeadSystem, VignetteRect = vignetteRect, DistortionRect = distortionRect, BlurRect = blurRect, }; HealthSystem.Init(healthSystemParams); Stamina.SetSpeeds(MoveSystem.WalkSpeed, MoveSystem.SprintSpeed); 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; _onWallHugging.StatePhysicsProcessing += HandleWallHugging; _coyoteEnabled.StateEntered += StartCoyoteTime; _coyoteTimer.Timeout += CoyoteExpired; _timeScaleAimInAirTimer.Timeout += ResetTimeScale; _dashing.StatePhysicsProcessing += Dashing; // _weaponThrown.StateEntered += OnWeaponThrown; _empowerOn.StateEntered += OnEmpowerStarted; _empowerOn.StateProcessing += HandleEmpower; _empowerTimeDownscale.Timeout += EmpowerStopped; } public void OnEmpowerStarted() { _empowerTimeDownscale.Start(); _isActionPerfectlyTimed = true; Engine.SetTimeScale(0.1f); } public void HandleEmpower(float delta) { var progress = (float) (_empowerTimeDownscale.TimeLeft / _empowerTimeDownscale.WaitTime); _isActionPerfectlyTimed = progress < PerfectlyTimedActionTimer; var indicatorColor = _isActionPerfectlyTimed ? Colors.Green : Colors.White; DashIndicator.SetCustomMinimumSize(Vector2.One * DashIndicatorStartSize * progress); DashIndicator.SetModulate(indicatorColor); DashIndicator.Visible = true; } public void EmpowerStopped() { DashIndicator.Visible = false; ResetTimeScale(); _isActionPerfectlyTimed = false; } /////////////////////////// // 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 (MoveSystem.CanMantle()) { Mantle(); return; } if (_grounded.Active || _coyoteEnabled.Active) if (_empowerOn.Active && CanPerformEmpoweredAction()) { PerformEmpoweredAction(); PerformJump(MoveSystem.JumpTypes.JumpFromDash); } else PerformJump(MoveSystem.JumpTypes.SimpleJump); else if (_doubleJumpEnabled.Active) if (_empowerOn.Active && CanPerformEmpoweredAction()) { PerformEmpoweredAction(); PerformJump(MoveSystem.JumpTypes.JumpFromDash); } else PerformJump(MoveSystem.JumpTypes.DoubleJump); else if (_onWall.Active) JumpFromWall(_empowerOn.Active); _playerState.SendEvent("jump"); } public void OnInputDashPressed() { _playerState.SendEvent("dash"); PerformDash(_empowerOn.Active); } public void OnInputThrowPressed() { } 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; _dashCooldownTimer.Start(); if (!_isActionPerfectlyTimed) { EmpoweredActionsLeft--; } _empowerTimeDownscale.Stop(); EmpowerStopped(); } // 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(MoveSystem.JumpTypes.JumpFromDash, jumpDirection); return; } PerformJump(MoveSystem.JumpTypes.JumpFromWall, jumpDirection); } private void PerformJump(MoveSystem.JumpTypes jumpType, Vector3? jumpDirection = null) { var effectiveJumpDirection = jumpDirection ?? Vector3.Up; var jumpVector = (effectiveJumpDirection.Normalized() + Vector3.Up).Normalized(); 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) MoveSystem.Jump(jumpType, jumpVector, (float) actualBoost); } // Mantling public void Mantle() { _playerState.SendEvent("mantle"); var optionTween = MoveSystem.Mantle(); if (optionTween.IsSome(out var tween)) tween.Finished += MantleFinished; } 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() { // _playerState.SendEvent("enable_double_jump"); // Allow for double jump after dash -- OP ? // 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) { _canDash = true; if (!isOnFloorCustom()) _playerState.SendEvent("start_falling"); } public void HandleGroundedStanding(float delta) { CapsuleCollider.UndoCrouching(delta, 1); HeadSystem.SetHeight(CapsuleCollider.GetCurrentHeight()); } public void HandleGroundedCrouched(float delta) { CapsuleCollider.Crouch(delta, 1); HeadSystem.SetHeight(CapsuleCollider.GetCurrentHeight()); } public void HandleAirborne(float delta) { if (isOnFloorCustom()) _playerState.SendEvent("grounded"); if (WallHugSystem.IsWallHugging() && Velocity.Y < 0) _playerState.SendEvent("wall_hug"); } public void HandleWallHugging(float delta) { if (isOnFloorCustom()) _playerState.SendEvent("grounded"); if (!WallHugSystem.IsWallHugging()) _playerState.SendEvent("start_falling"); } /////////////////////////// // Stateless logic //////// /////////////////////////// private void LookAround() { Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); HeadSystem.LookAround(inputLookDir); } private void MoveAround(double delta) { var moveAroundParams = new MoveSystem.MoveAroundParameters( delta, _inputMove, isOnFloorCustom(), HealthSystem.IsDead(), IsHeadTouchingCeiling(), _onWallHanging.Active, _onWallHugging.Active); MoveSystem.MoveAround(moveAroundParams); } private void HandleStairs(float delta) { StairsSystem.UpStairsCheckParams upStairsCheckParams = new StairsSystem.UpStairsCheckParams { IsOnFloorCustom = isOnFloorCustom(), IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(), CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed, 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(), // TODO: replace on IsOnFloor Custom 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 = MoveSystem._currentSpeed > MoveSystem.WalkSpeed, 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 = MoveSystem.SprintSpeed, Velocity = Velocity }; FieldOfView.PerformFovAdjustment(fovParams); } /////////////////////////// // Helpers //////////////// /////////////////////////// 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); } }