2023-08-06 01:07:30 -07:00
|
|
|
using Godot;
|
|
|
|
using SupaLidlGame.State.Character;
|
|
|
|
using SupaLidlGame.Extensions;
|
|
|
|
|
|
|
|
namespace SupaLidlGame.State.Thinker;
|
|
|
|
|
|
|
|
public partial class DashDefensive : AttackState
|
|
|
|
{
|
|
|
|
protected bool _dashedAway = false;
|
|
|
|
protected State.Character.CharacterDashState _dashState;
|
|
|
|
protected float _originalDashModifier;
|
2024-10-11 12:24:13 -07:00
|
|
|
private Callable _dodgeCallable;
|
|
|
|
|
|
|
|
[Export]
|
|
|
|
public Area2D ProjectileDetection { get; set; }
|
2023-08-06 01:07:30 -07:00
|
|
|
|
|
|
|
public override void _Ready()
|
|
|
|
{
|
|
|
|
_dashState = NPC.StateMachine.FindChildOfType<CharacterDashState>();
|
|
|
|
_originalDashModifier = _dashState.VelocityModifier;
|
2024-10-11 12:24:13 -07:00
|
|
|
_dodgeCallable = new Callable(this, MethodName.DodgeProjectile);
|
|
|
|
|
2023-08-06 01:07:30 -07:00
|
|
|
base._Ready();
|
|
|
|
}
|
|
|
|
|
2024-10-11 12:24:13 -07:00
|
|
|
public override IState<ThinkerState> Enter(IState<ThinkerState> prev)
|
|
|
|
{
|
|
|
|
ProjectileDetection?.Connect(Area2D.SignalName.AreaEntered,
|
|
|
|
_dodgeCallable);
|
|
|
|
|
|
|
|
return base.Enter(prev);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Exit(IState<ThinkerState> prev)
|
|
|
|
{
|
|
|
|
ProjectileDetection?.Disconnect(Area2D.SignalName.AreaEntered,
|
|
|
|
_dodgeCallable);
|
|
|
|
|
|
|
|
base.Exit(prev);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void DodgeProjectile(Area2D area)
|
|
|
|
{
|
|
|
|
if (area is BoundingBoxes.Hitbox hitbox)
|
|
|
|
{
|
|
|
|
if (hitbox.GetOwner() is Entities.Projectile projectile)
|
|
|
|
{
|
|
|
|
GD.Print("changing direction");
|
|
|
|
var direction = projectile.Direction;
|
2024-10-11 19:00:19 -07:00
|
|
|
var dirToChar = projectile.GlobalPosition
|
|
|
|
.DirectionTo(NPC.GlobalPosition);
|
|
|
|
var lateralDirection = Mathf.Sign(direction.Cross(dirToChar));
|
|
|
|
DashTo(direction.Rotated(lateralDirection * Mathf.Pi / 2));
|
2024-10-11 12:24:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-06 01:07:30 -07:00
|
|
|
public override ThinkerState Think()
|
|
|
|
{
|
|
|
|
Characters.Character bestTarget = NPC.FindBestTarget();
|
|
|
|
if (bestTarget is not null)
|
|
|
|
{
|
|
|
|
Vector2 pos = bestTarget.GlobalPosition;
|
|
|
|
NPC.Target = pos - NPC.GlobalPosition;
|
|
|
|
Vector2 dir = NPC.GlobalPosition.DirectionTo(pos);
|
2023-08-16 21:02:37 -07:00
|
|
|
float dist = NPC.GlobalPosition.DistanceTo(pos);
|
2023-08-06 01:07:30 -07:00
|
|
|
UpdateWeights(pos);
|
|
|
|
|
2023-08-16 21:02:37 -07:00
|
|
|
if (dist > MaxDistanceToTarget)
|
|
|
|
{
|
|
|
|
if (PursueState is not null)
|
|
|
|
{
|
|
|
|
return PursueState;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PassiveState is not null)
|
|
|
|
{
|
|
|
|
return PassiveState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-11 01:26:17 -08:00
|
|
|
if (PursueOnNoLOS && !NPC.HasLineOfSight(bestTarget))
|
|
|
|
{
|
|
|
|
return PursueState;
|
|
|
|
}
|
|
|
|
|
2023-08-06 01:07:30 -07:00
|
|
|
if (NPC.CanAttack && NPC.StunTime <= 0)
|
|
|
|
{
|
|
|
|
bool isTargetStunned = bestTarget.StunTime > 0;
|
|
|
|
|
|
|
|
bool shouldDashAway = false;
|
|
|
|
bool shouldDashTowards = false;
|
|
|
|
|
|
|
|
var currentItem = NPC.Inventory.SelectedItem;
|
|
|
|
if (currentItem is not Items.Weapons.Sword sword)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var swordState = sword.StateMachine.CurrentState;
|
|
|
|
|
|
|
|
float dot = NPC.Direction.Normalized()
|
|
|
|
.Dot(bestTarget.Direction.Normalized());
|
|
|
|
|
|
|
|
// doc will still dash if you are farther than normal but
|
|
|
|
// moving towards him
|
2024-01-06 14:13:48 -08:00
|
|
|
float distThreshold = UseItemDistance - (dot * 20);
|
2023-08-06 01:07:30 -07:00
|
|
|
|
|
|
|
// dash towards if lance in anticipate state
|
2023-08-16 21:02:37 -07:00
|
|
|
// or just directly dash towards you if you are too far
|
2023-08-06 01:07:30 -07:00
|
|
|
shouldDashTowards = (isTargetStunned || _dashedAway) &&
|
2024-01-06 14:13:48 -08:00
|
|
|
(swordState is State.Weapon.SwordAnticipateState ||
|
|
|
|
dist > MaxDistanceToTarget);
|
2023-08-06 01:07:30 -07:00
|
|
|
|
2023-08-16 21:02:37 -07:00
|
|
|
shouldDashAway = dist < distThreshold && !isTargetStunned &&
|
2024-01-06 14:13:48 -08:00
|
|
|
swordState is State.Weapon.SwordIdleState;
|
2023-08-06 01:07:30 -07:00
|
|
|
|
|
|
|
//if (!isTargetStunned && dist < 2500 && !_dashedAway)
|
|
|
|
if (shouldDashAway && !shouldDashTowards)
|
|
|
|
{
|
|
|
|
// dash away if too close
|
|
|
|
_dashState.VelocityModifier = _originalDashModifier;
|
|
|
|
DashTo(-dir);
|
|
|
|
NPC.UseCurrentItem();
|
|
|
|
_dashedAway = true;
|
|
|
|
}
|
|
|
|
else if (shouldDashTowards && !shouldDashAway)
|
|
|
|
{
|
2024-01-06 14:13:48 -08:00
|
|
|
// our required velocity is dependent on final distance to target
|
|
|
|
float maxVelocity = dist / 0.1f;
|
|
|
|
var dashSpeed = Mathf.Max(maxVelocity,
|
|
|
|
_originalDashModifier * NPC.Speed);
|
|
|
|
float ratio = dashSpeed / NPC.Speed;
|
|
|
|
_dashState.VelocityModifier = ratio;
|
|
|
|
|
2023-08-06 01:07:30 -07:00
|
|
|
// dash to player's predicted position
|
|
|
|
var newPos = Utils.Physics.PredictNewPosition(
|
|
|
|
NPC.GlobalPosition,
|
|
|
|
dashSpeed,
|
|
|
|
pos,
|
|
|
|
bestTarget.Velocity,
|
|
|
|
out float _);
|
|
|
|
DashTo(NPC.GlobalPosition.DirectionTo(newPos));
|
|
|
|
_dashedAway = false;
|
|
|
|
}
|
2023-08-16 21:02:37 -07:00
|
|
|
else if (isTargetStunned)
|
|
|
|
{
|
|
|
|
NPC.UseCurrentItem();
|
|
|
|
}
|
2023-08-06 01:07:30 -07:00
|
|
|
}
|
2024-01-06 14:13:48 -08:00
|
|
|
|
|
|
|
return null;
|
2023-08-06 01:07:30 -07:00
|
|
|
}
|
|
|
|
|
2023-08-16 21:02:37 -07:00
|
|
|
return PursueState ?? PassiveState;
|
2023-08-06 01:07:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private void DashTo(Vector2 direction)
|
|
|
|
{
|
|
|
|
var stateMachine = NPC.StateMachine;
|
|
|
|
stateMachine.ChangeState<CharacterDashState>(out var state);
|
|
|
|
state.DashDirection = direction;
|
|
|
|
}
|
|
|
|
}
|