using Godot; using Movementtests.interfaces; namespace Movementtests.systems; [GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_wind.png")] public partial class DashSystem: Node3D { public record DashLocation(bool HasHit, Vector3 TargetLocation, Vector3 CollisionPoint, Vector3 CollisionNormal, GodotObject HitObject = null); [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 bool CanDashThroughTarget { get; set; } public Vector3 TargetLocation { get; set; } public Vector3 CollisionPoint { get; set; } public Vector3 CollisionNormal { get; set; } public GodotObject CollidedObject { get; set; } public Vector3 PlannedLocation { get; set; } public bool ShouldMantle { get; set; } public Vector3 PlannedMantleLocation { get; set; } public MantleSystem MantleSystem { get; set; } private HeadSystem _head; public 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(HeadSystem 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 collidedObject = DashCast3D.GetCollider(0); var fraction = DashCast3D.GetClosestCollisionSafeFraction(); var globalSweepPath = targetLocation - DashCast3D.GlobalPosition; var locationAlongPath = DashCast3D.GlobalPosition + globalSweepPath * fraction; return new DashLocation(true, locationAlongPath, collisionPoint, collisionNormal, collidedObject); } public void PrepareDash() { DashCast3D.SetRotation(_head.GetGlobalLookRotation()); (HasHit, PlannedLocation, CollisionPoint, CollisionNormal, CollidedObject) = ComputeDashLocation(); CanDashThroughTarget = false; if (CollidedObject is ITargetable targetable) { _dashTarget.SetVisible(false); CanDashThroughTarget = true; return; } 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); return; 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() { CanDashThroughTarget = false; _dashTarget.SetVisible(false); _dashDropIndicator.SetVisible(false); _dashDropLocationIndicator.SetVisible(false); } }