using System; using Godot; using RustyOptions; namespace Movementtests.systems; [GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_face.png")] public partial class HeadSystem : Node3D { [Signal] public delegate void HitboxActivatedEventHandler(); [Signal] public delegate void HitboxDeactivatedEventHandler(); [Signal] public delegate void ParryboxActivatedEventHandler(); [Signal] public delegate void ParryboxDeactivatedEventHandler(); [Signal] public delegate void HitTargetEventHandler(); [Signal] public delegate void GotHitEventHandler(); [Signal] public delegate void DeathAnimationFinishedEventHandler(); [Signal] public delegate void StepFootEventHandler(); public record CameraParameters( double Delta, Vector2 LookDir, Vector3 PlayerInput, Vector3 PlayerVelocity, Vector3 WallContactPoint, float SensitivitMultiplier, bool WithCameraJitter, bool WithCameraBobbing, float BobbingMultiplier, float FovMultiplier); internal Camera3D Camera = null!; internal Node3D CameraAnchor = null!; internal AnimationPlayer AnimationPlayer = null!; internal AnimationTree AnimationTree = null!; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float LookSensitivity { get; set; } = 1f; [ExportGroup("Camera incline")] [Export(PropertyHint.Range, "0.1,50,0.1,or_greater")] public double CameraInclineAcceleration { get; set; } = 10f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float WallRunCameraIncline { get; set; } = 5f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float GroundedCameraIncline { get; set; } = 5f; [ExportGroup("Sliding")] [Export(PropertyHint.Range, "0,2,0.1,or_greater")] public float SlidingCameraHeightOffset { get; set; } = 1.0f; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float SlidingJitterFrequency { get; set; } = 0.01f; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float SlidingJitterAmplitude { get; set; } = 0.1f; internal FastNoiseLite _slidingNoise = new FastNoiseLite(); [ExportGroup("Bobbing")] internal float _bobbingAccumulator; // Constantly increases when player moves in X or/and Z axis [Export(PropertyHint.Range, "0,10,0.01,or_greater")] public float BobbingFrequency { set; get; } = 2.4f; [Export(PropertyHint.Range, "0,0.4,0.01,or_greater")] public float BobbingAmplitude { set; get; } = 0.08f; [ExportGroup("FOV")] [Export(PropertyHint.Range, "0,180,0.1,degrees")] public float BaseFov { get; set; } = 75.0f; [Export(PropertyHint.Range, "0,10,0.01,or_greater")] public float FovChangeFactor { get; set; } = 1.2f; [Export(PropertyHint.Range, "0,10,0.01,or_greater")] public float FovChangeSpeed { get; set; } = 6.25f; [Export(PropertyHint.Range, "0,100,1,or_greater")] public float FovMaxedOutSpeed { get; set; } = 20f; [ExportGroup("First Person rig")] internal Node3D FpRig = null!; internal Node3D RightHandedWeapon = null!; internal Vector3 RightHandedWeaponInitialRotation = Vector3.Zero; internal Node3D LeftHandedWeapon = null!; internal Vector3 LeftHandedWeaponInitialRotation = Vector3.Zero; [Export(PropertyHint.Range, "0,20,1,or_greater")] public float WeaponSway { get; set; } = 15f; [Export(PropertyHint.Range, "0,200,1,or_greater")] public float WeaponMoveRotation { get; set; } = 80f; [Export(PropertyHint.Range, "0,20,1,or_greater")] public float WeaponAdjustmentSpeed { get; set; } = 1f; [Export(PropertyHint.Range, "0,2,0.01,or_greater")] public float DisplacedWeaponSway { get; set; } = 0.8f; [Export(PropertyHint.Range, "0,0.5,0.01,or_greater")] public float DisplacedWeaponMoveRotation { get; set; } = 0.1f; [Export(PropertyHint.Range, "0,20,1,or_greater")] public float DisplacedWeaponAdjustmentSpeed { get; set; } = 12f; public void Init() { IsPlayingForcingAnim = false; Input.SetMouseMode(Input.MouseModeEnum.Captured); Camera = GetNode("CameraSmooth/Camera3D"); CameraAnchor = GetNode("CameraSmooth/CameraAnchor"); //_cameraAnchor = GetNode("CameraSmooth/Camera3D"); AnimationPlayer = GetNode("AnimationPlayer"); AnimationTree = GetNode("AnimationTree"); FpRig = GetNode("FPRig"); RightHandedWeapon = GetNode("FPRig/Sword/SwordMesh"); RightHandedWeaponInitialRotation = RightHandedWeapon.Rotation; LeftHandedWeapon = GetNode("FPRig/Parry/ParryMesh"); LeftHandedWeaponInitialRotation = LeftHandedWeapon.Rotation; _slidingNoise.NoiseType = FastNoiseLite.NoiseTypeEnum.Perlin; _slidingNoise.SetFrequency(SlidingJitterFrequency); } public void SetWeaponsVisible(bool swordVisible, bool parryVisible) { RightHandedWeapon.Visible = swordVisible; LeftHandedWeapon.Visible = parryVisible; } public void OnMantle() { AnimationTree.Set("parameters/OnMantle/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnJumpStarted() { AnimationTree.Set("parameters/OnJumpStart/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnJumpEnded() { AnimationTree.Set("parameters/OnJumpEnd/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnHit() { AnimationTree.Set("parameters/OnHit/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnParry() { AnimationTree.Set("parameters/OnParry/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnStartDeathAnimation() { IsPlayingForcingAnim = true; AnimationTree.Set("parameters/OnDie/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnDeathAnimationFinished() { EmitSignalDeathAnimationFinished(); } public void OnHitTarget() { EmitSignalHitTarget(); } public void OnGetHit() { EmitSignalGotHit(); } public void OnHitboxActivated() { EmitSignalHitboxActivated(); } public void OnHitboxDeactivated() { EmitSignalHitboxDeactivated(); } public void OnParryboxActivated() { EmitSignalHitboxActivated(); } public void OnParryboxDeactivated() { EmitSignalHitboxDeactivated(); } internal bool FootstepEmitted; internal bool IsPlayingForcingAnim; public void ResetHeadBobbing() { _bobbingAccumulator = 0; } public void LookAround(CameraParameters inputs) { if (IsPlayingForcingAnim) { Camera.Position = Vector3.Zero; Camera.Rotation = Vector3.Zero; return; } var (delta, lookDir, playerInput, playerVelocity, wallContactPoint, sensitivitMultiplier, withCameraJitter, withCameraBobbing, bobbingMultiplier, fovMultiplier) = inputs; // Horizontal movement of head float angleForHorizontalRotation = lookDir.X * LookSensitivity * sensitivitMultiplier; RotateY(angleForHorizontalRotation); // Vertical movement of head Vector3 currentCameraRotation = CameraAnchor.Rotation; currentCameraRotation.X += Convert.ToSingle(lookDir.Y * LookSensitivity * sensitivitMultiplier); currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f)); // Camera incline on Wall and more var isWallRunning = wallContactPoint.Length() > Mathf.Epsilon; float cameraIncline; if (isWallRunning) { var directionToWall = (wallContactPoint - GlobalPosition).Normalized(); var cameraInclineFactor = ComputeCameraInclineFactor(directionToWall); cameraIncline = Mathf.DegToRad(WallRunCameraIncline * cameraInclineFactor); } else { var cameraInclineFactor = ComputeCameraInclineFactor(playerInput); cameraIncline = Mathf.DegToRad(GroundedCameraIncline * cameraInclineFactor * -1.0f); } currentCameraRotation.Z = (float) Mathf.Lerp(currentCameraRotation.Z, cameraIncline, delta * CameraInclineAcceleration); CameraAnchor.Rotation = currentCameraRotation; if (withCameraJitter) { CameraAnchor.Position = Vector3.Down*SlidingCameraHeightOffset; float noise1D = _slidingNoise.GetNoise1D(Time.GetTicksMsec()); float noiseAmplitude = SlidingJitterAmplitude*Mathf.Clamp(playerVelocity.Length(), 0f, 1f); CameraAnchor.Position += Vector3.Up*noise1D*noiseAmplitude; } else { CameraAnchor.Position = Vector3.Zero; } Vector3 newPositionForCamera = Vector3.Zero; Vector3 newPositionForRig = Vector3.Zero; if (withCameraBobbing) { _bobbingAccumulator += (float) delta * playerVelocity.Length(); // As the _bobbingAccumulator increases we're changing values for sin and cos functions. // Because both of them are just waves, we will be slide up with y and then slide down with y // creating bobbing effect. The same works for cos. As the _bobbingAccumulator increases the cos decreases and then increases newPositionForCamera.Y = Mathf.Sin(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude * bobbingMultiplier; newPositionForCamera.X = Mathf.Cos(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier; if (newPositionForCamera.Y < -0.07 * bobbingMultiplier && !FootstepEmitted) Footstep(); if (newPositionForCamera.Y > 0) FootstepEmitted = false; // Offset bobbing for weapon rig newPositionForRig.Y = Mathf.Cos(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude * bobbingMultiplier * 0.2f; newPositionForRig.X = Mathf.Sin(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier * 0.2f; } CameraAnchor.Position += newPositionForCamera; Camera.GlobalTransform = CameraAnchor.GetGlobalTransformInterpolated(); // First person rig adjustments FpRig.GlobalTransform = Camera.GlobalTransform; // Apply bobbing FpRig.Position += newPositionForRig; // Rotate the whole rig based on movement input var newRigRotation = FpRig.Rotation; var camTilt = Mathf.Lerp(FpRig.Rotation.Z, CameraAnchor.Rotation.Z*WeaponMoveRotation, delta * WeaponAdjustmentSpeed); newRigRotation.Z = (float) camTilt; // Rotate the whole rig based on camera rotation input newRigRotation.X = Mathf.Lerp(newRigRotation.X, -lookDir.Y*WeaponSway, (float) delta * WeaponAdjustmentSpeed); newRigRotation.Y = Mathf.Lerp(newRigRotation.Y, -lookDir.X*WeaponSway, (float) delta * WeaponAdjustmentSpeed); // Apply FpRig.Rotation = newRigRotation; // Compute sword meshes procedural adjustments RightHandedWeapon.Rotation = ComputeRotationForFpMesh(RightHandedWeapon, RightHandedWeaponInitialRotation, playerInput, lookDir, (float) delta); LeftHandedWeapon.Rotation = ComputeRotationForFpMesh(LeftHandedWeapon, LeftHandedWeaponInitialRotation, playerInput, lookDir, (float) delta); // Camera adjustments float velocityClamped = Mathf.Clamp(playerVelocity.Length(), 0.5f, FovMaxedOutSpeed); float targetFov = BaseFov + FovChangeFactor * velocityClamped * fovMultiplier; Camera.Fov = Mathf.Lerp(Camera.Fov, targetFov, (float) delta * FovChangeSpeed); } public Vector3 ComputeRotationForFpMesh(Node3D mesh, Vector3 initialRotation, Vector3 playerInput, Vector2 lookDir, float delta) { var newMeshRotation = mesh.Rotation; var howMuchForward = ComputeHowMuchInputForward(playerInput); var howMuchSideways = ComputeHowMuchInputSideways(playerInput); var displacedCamTiltForward = Mathf.Lerp(newMeshRotation.Z, initialRotation.Z + howMuchForward*DisplacedWeaponMoveRotation, delta * DisplacedWeaponAdjustmentSpeed); var displacedCamTiltSide = Mathf.Lerp(newMeshRotation.X, initialRotation.X - howMuchSideways*DisplacedWeaponMoveRotation, delta * DisplacedWeaponAdjustmentSpeed); newMeshRotation.X = displacedCamTiltSide; newMeshRotation.Z = displacedCamTiltForward; var displacedSwayY = Mathf.Lerp(newMeshRotation.Y, initialRotation.Y - lookDir.X*DisplacedWeaponSway, delta * DisplacedWeaponAdjustmentSpeed); newMeshRotation.Y = displacedSwayY; return newMeshRotation; } public void Footstep() { FootstepEmitted = true; EmitSignalStepFoot(); } public void HideWeapon() { RightHandedWeapon.Visible = false; } public void ShowWeapon() { RightHandedWeapon.Visible = true; } public float ComputeCameraInclineFactor(Vector3 direction) { var forward = GetForwardHorizontalVector().Normalized(); var crossProduct = forward.Cross(direction); return crossProduct.Length()*Mathf.Sign(crossProduct.Y); } public float ComputeHowMuchInputForward(Vector3 playerInput) { var forwardAngle = GetForwardHorizontalVector().AngleTo(playerInput); var forwardRemapped = Mathf.Remap(forwardAngle, 0, Mathf.Pi, -1, 1); return playerInput.Length() > 0 ? forwardRemapped : 0; } public float ComputeHowMuchInputSideways(Vector3 playerInput) { var rightAngle = GetForwardHorizontalVector().Cross(Vector3.Up).Normalized().AngleTo(playerInput); var forwardRemapped = Mathf.Remap(rightAngle, 0, Mathf.Pi, -1, 1); return playerInput.Length() > 0 ? forwardRemapped : 0; } public Vector3 GetForwardHorizontalVector() { return GetGlobalTransform().Basis.Z; } public Vector3 GetGlobalForwardVector() { return Camera.GlobalBasis.Z; } public Vector3 GetGlobalLookRotation() { return new Vector3( Camera.Rotation.X, Rotation.Y, Camera.Rotation.Z); } public void SetHeight(float height) { Position = new Vector3(Position.X, height, Position.Z); } }