using Godot; namespace Movementtests.systems; public partial class DashSystem: Node3D { public record DashLocation(bool HasHit, Vector3 TargetLocation, Vector3 CollisionPoint, Vector3 CollisionNormal); [Export(PropertyHint.Range, "0,0.2,0.01,or_greater")] public float DashSpeed { get; set; } = 0.1f; [Export(PropertyHint.Range, "0,1000,1,or_greater")] public float PostDashSpeed { get; set; } = 0f; public bool HasHit { get; set; } public Vector3 TargetLocation { get; set; } public Vector3 CollisionPoint { get; set; } public Vector3 CollisionNormal { get; set; } public Vector3 PlannedLocation { get; set; } public bool ShouldMantle { get; set; } public Vector3 PlannedMantleLocation { get; set; } public MantleSystem MantleSystem { get; set; } private Node3D _head; private ShapeCast3D _dashCast3D; private Camera3D _camera; private Vector3 _dashDirection = Vector3.Zero; private ShapeCast3D _dashCastDrop; private MeshInstance3D _dashDropIndicator; private MeshInstance3D _dashDropLocationIndicator; private MeshInstance3D _dashTarget; private CpuParticles3D _dashIndicator; private AnimationPlayer _dashIndicatorAnim; [Export] public PackedScene DashIndicatorScene { get; set; } [Signal] public delegate void DashStartedEventHandler(); [Signal] public delegate void DashEndedEventHandler(); [Signal] public delegate void DashProgressEventHandler(float progress); private Vector3 _globalDashPosition = Vector3.Zero; public float DashCastRadius { get; set; } public void Init(Node3D head, Camera3D camera) { _dashCast3D = GetNode("DashCast3D"); var dashShape = _dashCast3D.GetShape() as SphereShape3D; DashCastRadius = dashShape!.Radius; _dashCastDrop = GetNode("DashCastDrop"); _dashDropIndicator = GetNode("DashDropIndicator"); _dashDropIndicator.Visible = false; _dashDropLocationIndicator = GetNode("DashDropLocationIndicator"); _dashDropLocationIndicator.Visible = false; _head = head; _camera = camera; MantleSystem = GetNode("MantleSystem"); MantleSystem.Init(); _dashTarget = GetNode("DashTarget"); _dashTarget.SetVisible(false); _dashIndicator = GetNode("DashIndicator"); _dashIndicatorAnim = GetNode("DashIndicator/AnimationPlayer"); } private DashLocation ComputeDashLocation() { var targetLocation = _dashCast3D.ToGlobal(_dashCast3D.TargetPosition); var hasHit = _dashCast3D.IsColliding(); if (!hasHit) { return new DashLocation(false, targetLocation, Vector3.Zero, Vector3.Zero); } var collisionPoint = _dashCast3D.GetCollisionPoint(0); var collisionNormal = _dashCast3D.GetCollisionNormal(0); var fraction = _dashCast3D.GetClosestCollisionSafeFraction(); var globalSweepPath = targetLocation - _dashCast3D.GlobalPosition; var locationAlongPath = _dashCast3D.GlobalPosition + globalSweepPath * fraction; return new DashLocation(true, locationAlongPath, collisionPoint, collisionNormal); } public void PrepareDash() { _dashCast3D.SetRotation(new Vector3( _camera.Rotation.X, _head.Rotation.Y, _camera.Rotation.Z)); (HasHit, PlannedLocation, CollisionPoint, CollisionNormal) = ComputeDashLocation(); // TODO: Position mantle system to planned location, aligned with ground planned and facing the same way as the dash // Then query it being careful when dashing underneath a platform and such MantleSystem.SetGlobalPosition(PlannedLocation); MantleSystem.SetRotation(new Vector3( MantleSystem.Rotation.X, _head.Rotation.Y, MantleSystem.Rotation.Z)); MantleSystem.ProcessMantle(false); ShouldMantle = MantleSystem.IsMantlePossible; // Setup dash target var targetColor = HasHit ? new Color(1f, 0.2f, 0.2f) : new Color(1f, 1f, 1f); targetColor = ShouldMantle ? new Color(0.2f, 0.2f, 1f) : targetColor; var targetMaterial = (StandardMaterial3D) _dashTarget.GetSurfaceOverrideMaterial(0); targetMaterial.SetAlbedo(targetColor); _dashTarget.SetVisible(true); var targetLocation = ShouldMantle ? MantleSystem.FirstMantleProfilePoint : PlannedLocation; _dashTarget.SetGlobalPosition(targetLocation); var shouldShowDropIndicator = !HasHit && !ShouldMantle; _dashDropIndicator.SetVisible(shouldShowDropIndicator); _dashDropLocationIndicator.SetVisible(shouldShowDropIndicator); if (shouldShowDropIndicator) { _dashCastDrop.GlobalPosition = targetLocation; // Place drop indication cast at dash location var startDropLocation = targetLocation; // Start of the drop is the dash target location // End of the drop is either max cast distance or first collision var hasDropLocationHit = _dashCastDrop.IsColliding(); var endDropLocation = hasDropLocationHit ? _dashCastDrop.GetCollisionPoint(0) : _dashCastDrop.ToGlobal(_dashCast3D.TargetPosition); // Only show drop location indicator if drop cast has hit _dashDropLocationIndicator.SetVisible(hasDropLocationHit); _dashDropLocationIndicator.SetGlobalPosition(endDropLocation); var dropLength = (endDropLocation - startDropLocation).Length(); var dropDirection = (endDropLocation - startDropLocation).Normalized(); _dashDropIndicator.SetScale(new Vector3(1, dropLength, 1)); _dashDropIndicator.SetGlobalPosition(startDropLocation + dropDirection * dropLength * 0.5f); } } public void StopPreparingDash() { _dashTarget.SetVisible(false); _dashDropIndicator.SetVisible(false); _dashDropLocationIndicator.SetVisible(false); } public void StartPreparingDash() { _dashTarget.SetVisible(true); } }