using System.Collections.Generic; using System.Runtime.InteropServices.JavaScript; using Godot; using RustyOptions; namespace PolarBears.PlayerControllerAddon; 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; private bool _movementEnabled = true; private bool _shouldMantle = false; private Vector3 _dashLocation = Vector3.Zero; private Vector3 _mantleLocation = Vector3.Zero; private float _lastFrameWasOnFloor = -Mathf.Inf; private const int NumOfHeadCollisionDetectors = 4; private RayCast3D[] _headCollisionDetectors; private Vector3 _inputMove = Vector3.Zero; private float _inputRotateY = 0.0f; private float _inputRotateFloorplane = 0.0f; private bool _isAiming = false; public void OnInputMove(Vector3 value) { _inputMove = value; } public void OnInputRotateY(float value) { _inputRotateY = value; } public void OnInputRotateFloorplane(float value) { _inputRotateFloorplane = value; } public void OnInputAimPressed() { _isAiming = true; GD.Print("Aim pressed"); } public void OnInputAimReleased() { _isAiming = false; GD.Print("Aim released"); } public void OnInputAimCanceled() { _isAiming = false; GD.Print("Aim canceled"); } public void OnInputJumpPressed() { bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight(); bool isPlayerDead = HealthSystem.IsDead(); if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead) MoveSystem.Jump(IsOnFloor()); } public override void _Ready() { HeadSystem = GetNode("HeadSystem"); HeadSystem.Init(); _headCollisionDetectors = new RayCast3D[NumOfHeadCollisionDetectors]; for (int i = 0; i < NumOfHeadCollisionDetectors; i++) { _headCollisionDetectors[i] = GetNode( "HeadCollisionDetectors/HeadCollisionDetector" + i); } // Getting dependencies of the components(In godot we manage this from upwards to downwards not vice versa) Camera3D camera = GetNode("HeadSystem/CameraSmooth/Camera3D"); RayCast3D stairsBelowRayCast3D = GetNode("StairsBelowRayCast3D"); RayCast3D stairsAheadRayCast3D = GetNode("StairsAheadRayCast3D"); Node3D cameraSmooth = GetNode("HeadSystem/CameraSmooth"); // Getting universal setting from GODOT editor to be in sync float gravitySetting = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); 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"); Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D; // Getting components Bobbing = GetNode("Bobbing"); Bobbing.Init(camera); FieldOfView = GetNode("FieldOfView"); FieldOfView.Init(camera); CapsuleCollider = GetNode("CapsuleCollider"); Gravity = GetNode("Gravity"); Gravity.Init(gravitySetting); MantleSystem = GetNode("MantleSystem"); MantleSystem.Init(HeadSystem); TweenQueueSystem = GetNode("TweenQueueSystem"); TweenQueueSystem.Init(this); MoveSystem = GetNode("MoveSystem"); var moveSystemParams = new MoveSystem.MoveSystemParameters(this, Gravity, MantleSystem, TweenQueueSystem, HeadSystem, CapsuleCollider); MoveSystem.Init(moveSystemParams); Stamina = GetNode("Stamina"); Stamina.SetSpeeds(MoveSystem.WalkSpeed, MoveSystem.SprintSpeed); StairsSystem = GetNode("StairsSystem"); StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth); DashSystem = GetNode("DashSystem"); DashSystem.Init(HeadSystem, camera); HealthSystem = GetNode("HealthSystem"); HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams() { Gravity = Gravity, Parent = this, Camera = camera, Head = HeadSystem, VignetteRect = vignetteRect, DistortionRect = distortionRect, BlurRect = blurRect, }; HealthSystem.Init(healthSystemParams); } public override void _PhysicsProcess(double delta) { TweenQueueSystem.ProcessTweens(); if (Input.IsActionPressed("aim_dash")) { (_shouldMantle, _dashLocation, _mantleLocation) = DashSystem.PrepareDash(); } if (Input.IsActionJustReleased("aim_dash")) { DashSystem.Dash(); TweenQueueSystem.QueueTween(_dashLocation, 0.1f); if (_shouldMantle) { TweenQueueSystem.QueueTween(_mantleLocation, 0.1f); } } var isPlayerDead = HealthSystem.IsDead(); var isHeadTouchingCeiling = IsHeadTouchingCeiling(); MoveSystem.MoveAround(delta, _inputMove, isOnFloorCustom(), isPlayerDead, isHeadTouchingCeiling); Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); HeadSystem.LookAround(inputLookDir); Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams { Delta = (float)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); 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() }; // TODO: SnapUpStairsCheck influences the ability of player to crouch because of `stepHeightY <= 0.01` part // Ideally, it should not. SnapUpStairsCheck and SnapDownStairsCheck should be called, when player is actually // on the stairs 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 bool IsHeadTouchingCeiling() { for (int i = 0; i < NumOfHeadCollisionDetectors; i++) { if (_headCollisionDetectors[i].IsColliding()) { return true; } } return false; } private bool isOnFloorCustom() { return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame(); } }