refactor file scoped namespace
parent
96fd6f5c26
commit
706354e813
|
@ -1,11 +1,10 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using SupaLidlGame.Utils;
|
using SupaLidlGame.Utils;
|
||||||
|
|
||||||
namespace SupaLidlGame.BoundingBoxes
|
namespace SupaLidlGame.BoundingBoxes;
|
||||||
|
|
||||||
|
public abstract partial class BoundingBox : Area2D, IFaction
|
||||||
{
|
{
|
||||||
public abstract partial class BoundingBox : Area2D, IFaction
|
[Export]
|
||||||
{
|
public ushort Faction { get; set; }
|
||||||
[Export]
|
|
||||||
public ushort Faction { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,71 +2,70 @@ using Godot;
|
||||||
using System;
|
using System;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
|
|
||||||
namespace SupaLidlGame.BoundingBoxes
|
namespace SupaLidlGame.BoundingBoxes;
|
||||||
|
|
||||||
|
public partial class ConnectorBox : Area2D
|
||||||
{
|
{
|
||||||
public partial class ConnectorBox : Area2D
|
[Signal]
|
||||||
|
public delegate void RequestedEnterEventHandler(
|
||||||
|
ConnectorBox box,
|
||||||
|
Player player);
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public string ToArea { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public string ToConnector { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the connector requires the user to interact to enter
|
||||||
|
/// the connector
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public bool RequiresInteraction { get; set; } = false;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public CollisionShape2D Collision { get; set; }
|
||||||
|
|
||||||
|
private Player _player = null;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
[Signal]
|
if (Collision is null)
|
||||||
public delegate void RequestedEnterEventHandler(
|
|
||||||
ConnectorBox box,
|
|
||||||
Player player);
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public string ToArea { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public string ToConnector { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public string Identifier { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the connector requires the user to interact to enter
|
|
||||||
/// the connector
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public bool RequiresInteraction { get; set; } = false;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public CollisionShape2D Collision { get; set; }
|
|
||||||
|
|
||||||
private Player _player = null;
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
{
|
||||||
if (Collision is null)
|
throw new NullReferenceException("Collision not specified");
|
||||||
{
|
|
||||||
throw new NullReferenceException("Collision not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
BodyEntered += (Node2D body) =>
|
|
||||||
{
|
|
||||||
if (body is Player player)
|
|
||||||
{
|
|
||||||
_player = player;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BodyExited += (Node2D body) =>
|
|
||||||
{
|
|
||||||
if (body is Player)
|
|
||||||
{
|
|
||||||
_player = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
BodyEntered += (Node2D body) =>
|
||||||
{
|
{
|
||||||
if (Input.IsActionJustReleased("interact"))
|
if (body is Player player)
|
||||||
{
|
{
|
||||||
if (_player is not null)
|
_player = player;
|
||||||
{
|
|
||||||
EmitSignal(SignalName.RequestedEnter, this, _player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
base._Process(delta);
|
BodyExited += (Node2D body) =>
|
||||||
|
{
|
||||||
|
if (body is Player)
|
||||||
|
{
|
||||||
|
_player = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (Input.IsActionJustReleased("interact"))
|
||||||
|
{
|
||||||
|
if (_player is not null)
|
||||||
|
{
|
||||||
|
EmitSignal(SignalName.RequestedEnter, this, _player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base._Process(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,103 +3,102 @@ using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
using SupaLidlGame.Items;
|
using SupaLidlGame.Items;
|
||||||
|
|
||||||
namespace SupaLidlGame.BoundingBoxes
|
namespace SupaLidlGame.BoundingBoxes;
|
||||||
|
|
||||||
|
public partial class Hitbox : BoundingBox
|
||||||
{
|
{
|
||||||
public partial class Hitbox : BoundingBox
|
private HashSet<BoundingBox> _ignoreList = new HashSet<BoundingBox>();
|
||||||
|
|
||||||
|
[Signal]
|
||||||
|
public delegate void HitEventHandler(BoundingBox box);
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float Damage { get; set; }
|
||||||
|
|
||||||
|
private bool _isDisabled = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Getter/setter for the CollisionShape2D's Disabled property.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public bool IsDisabled
|
||||||
{
|
{
|
||||||
private HashSet<BoundingBox> _ignoreList = new HashSet<BoundingBox>();
|
get => _collisionShape.Disabled;
|
||||||
|
set
|
||||||
[Signal]
|
|
||||||
public delegate void HitEventHandler(BoundingBox box);
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float Damage { get; set; }
|
|
||||||
|
|
||||||
private bool _isDisabled = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Getter/setter for the CollisionShape2D's Disabled property.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public bool IsDisabled
|
|
||||||
{
|
{
|
||||||
get => _collisionShape.Disabled;
|
_isDisabled = value;
|
||||||
set
|
if (_collisionShape is not null)
|
||||||
{
|
{
|
||||||
_isDisabled = value;
|
_collisionShape.Disabled = value;
|
||||||
if (_collisionShape is not null)
|
if (value)
|
||||||
{
|
{
|
||||||
_collisionShape.Disabled = value;
|
DamageStartTime = Time.GetTicksMsec();
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
DamageStartTime = Time.GetTicksMsec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
[Export]
|
||||||
public float Knockback { get; set; }
|
public float Knockback { get; set; }
|
||||||
|
|
||||||
public Character Inflictor { get; set; }
|
public Character Inflictor { get; set; }
|
||||||
|
|
||||||
public ulong DamageStartTime { get; set; }
|
public ulong DamageStartTime { get; set; }
|
||||||
|
|
||||||
private CollisionShape2D _collisionShape;
|
private CollisionShape2D _collisionShape;
|
||||||
|
|
||||||
private bool _isParrying = false;
|
private bool _isParrying = false;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
|
||||||
|
IsDisabled = _isDisabled; // sets _collisionShape.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldParry(Hitbox box)
|
||||||
|
{
|
||||||
|
Node parent = GetParent<Node>();
|
||||||
|
|
||||||
|
// if this hitbox does not even belong to a weapon, skip
|
||||||
|
if (parent is not Weapon)
|
||||||
{
|
{
|
||||||
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
|
|
||||||
IsDisabled = _isDisabled; // sets _collisionShape.Disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldParry(Hitbox box)
|
|
||||||
{
|
|
||||||
Node parent = GetParent<Node>();
|
|
||||||
|
|
||||||
// if this hitbox does not even belong to a weapon, skip
|
|
||||||
if (parent is not Weapon)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var weapon = parent as Weapon;
|
|
||||||
|
|
||||||
// if we hit a hitbox, we can parry if it can be parried
|
|
||||||
if (box.GetParent<Node>() is Weapon other)
|
|
||||||
{
|
|
||||||
return weapon.IsParryable && other.IsParryable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void _on_area_entered(Area2D area)
|
var weapon = parent as Weapon;
|
||||||
|
|
||||||
|
// if we hit a hitbox, we can parry if it can be parried
|
||||||
|
if (box.GetParent<Node>() is Weapon other)
|
||||||
{
|
{
|
||||||
if (area is BoundingBox box)
|
return weapon.IsParryable && other.IsParryable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _on_area_entered(Area2D area)
|
||||||
|
{
|
||||||
|
if (area is BoundingBox box)
|
||||||
|
{
|
||||||
|
GD.Print("hit");
|
||||||
|
// we don't want to hurt teammates
|
||||||
|
if (Faction != box.Faction)
|
||||||
{
|
{
|
||||||
GD.Print("hit");
|
if (!_ignoreList.Contains(box))
|
||||||
// we don't want to hurt teammates
|
|
||||||
if (Faction != box.Faction)
|
|
||||||
{
|
{
|
||||||
if (!_ignoreList.Contains(box))
|
_ignoreList.Add(box);
|
||||||
{
|
EmitSignal(SignalName.Hit, box);
|
||||||
_ignoreList.Add(box);
|
|
||||||
EmitSignal(SignalName.Hit, box);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetIgnoreList() => _ignoreList.Clear();
|
public void ResetIgnoreList() => _ignoreList.Clear();
|
||||||
|
|
||||||
public bool HasHit(BoundingBox box) => _ignoreList.Contains(box);
|
public bool HasHit(BoundingBox box) => _ignoreList.Contains(box);
|
||||||
|
|
||||||
public HashSet<BoundingBox> Hits
|
public HashSet<BoundingBox> Hits
|
||||||
{
|
{
|
||||||
get => _ignoreList;
|
get => _ignoreList;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,39 +2,38 @@ using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
using SupaLidlGame.Utils;
|
using SupaLidlGame.Utils;
|
||||||
|
|
||||||
namespace SupaLidlGame.BoundingBoxes
|
namespace SupaLidlGame.BoundingBoxes;
|
||||||
|
|
||||||
|
public partial class Hurtbox : BoundingBox, IFaction
|
||||||
{
|
{
|
||||||
public partial class Hurtbox : BoundingBox, IFaction
|
[Signal]
|
||||||
|
public delegate void ReceivedDamageEventHandler(
|
||||||
|
float damage,
|
||||||
|
Character inflictor,
|
||||||
|
float knockback,
|
||||||
|
Vector2 knockbackOrigin = default,
|
||||||
|
Vector2 knockbackVector = default);
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
[Signal]
|
if (GetParent() is IFaction factionEntity)
|
||||||
public delegate void ReceivedDamageEventHandler(
|
|
||||||
float damage,
|
|
||||||
Character inflictor,
|
|
||||||
float knockback,
|
|
||||||
Vector2 knockbackOrigin = default,
|
|
||||||
Vector2 knockbackVector = default);
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
{
|
||||||
if (GetParent() is IFaction factionEntity)
|
Faction = factionEntity.Faction;
|
||||||
{
|
|
||||||
Faction = factionEntity.Faction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InflictDamage(
|
|
||||||
float damage,
|
|
||||||
Character inflictor,
|
|
||||||
float knockback,
|
|
||||||
Vector2 knockbackOrigin = default,
|
|
||||||
Vector2 knockbackVector = default)
|
|
||||||
{
|
|
||||||
EmitSignal(
|
|
||||||
SignalName.ReceivedDamage,
|
|
||||||
damage,
|
|
||||||
inflictor,
|
|
||||||
knockback,
|
|
||||||
knockbackOrigin, knockbackVector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InflictDamage(
|
||||||
|
float damage,
|
||||||
|
Character inflictor,
|
||||||
|
float knockback,
|
||||||
|
Vector2 knockbackOrigin = default,
|
||||||
|
Vector2 knockbackVector = default)
|
||||||
|
{
|
||||||
|
EmitSignal(
|
||||||
|
SignalName.ReceivedDamage,
|
||||||
|
damage,
|
||||||
|
inflictor,
|
||||||
|
knockback,
|
||||||
|
knockbackOrigin, knockbackVector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,221 +4,220 @@ using SupaLidlGame.Items;
|
||||||
using SupaLidlGame.Utils;
|
using SupaLidlGame.Utils;
|
||||||
using SupaLidlGame.State.Character;
|
using SupaLidlGame.State.Character;
|
||||||
|
|
||||||
namespace SupaLidlGame.Characters
|
namespace SupaLidlGame.Characters;
|
||||||
|
|
||||||
|
public partial class Character : CharacterBody2D, IFaction
|
||||||
{
|
{
|
||||||
public partial class Character : CharacterBody2D, IFaction
|
[Export]
|
||||||
|
public float Speed { get; protected set; } = 32.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float Friction { get; protected set; } = 4.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float Mass
|
||||||
{
|
{
|
||||||
[Export]
|
get => _mass;
|
||||||
public float Speed { get; protected set; } = 32.0f;
|
set
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float Friction { get; protected set; } = 4.0f;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float Mass
|
|
||||||
{
|
{
|
||||||
get => _mass;
|
if (value > 0)
|
||||||
set
|
_mass = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float _mass = 1.0f;
|
||||||
|
|
||||||
|
public Vector2 NetImpulse { get; set; } = Vector2.Zero;
|
||||||
|
|
||||||
|
public Vector2 Direction { get; set; } = Vector2.Zero;
|
||||||
|
|
||||||
|
public Vector2 Target { get; set; } = Vector2.Zero;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float Health
|
||||||
|
{
|
||||||
|
get => _health;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!IsAlive && value < 0)
|
||||||
{
|
{
|
||||||
if (value > 0)
|
|
||||||
_mass = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected float _mass = 1.0f;
|
|
||||||
|
|
||||||
public Vector2 NetImpulse { get; set; } = Vector2.Zero;
|
|
||||||
|
|
||||||
public Vector2 Direction { get; set; } = Vector2.Zero;
|
|
||||||
|
|
||||||
public Vector2 Target { get; set; } = Vector2.Zero;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float Health
|
|
||||||
{
|
|
||||||
get => _health;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (!IsAlive && value < 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_health = value;
|
|
||||||
if (_health <= 0)
|
|
||||||
{
|
|
||||||
Die();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAlive => Health > 0;
|
|
||||||
|
|
||||||
protected float _health = 100f;
|
|
||||||
|
|
||||||
public double StunTime { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public AnimatedSprite2D Sprite { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Inventory Inventory { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public CharacterStateMachine StateMachine { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public ushort Faction { get; set; }
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
if (StateMachine != null)
|
|
||||||
{
|
|
||||||
StateMachine.Process(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sprite.FlipH = Target.X < 0;
|
|
||||||
DrawTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Input(InputEvent @event)
|
|
||||||
{
|
|
||||||
if (StateMachine != null)
|
|
||||||
{
|
|
||||||
StateMachine.Input(@event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _PhysicsProcess(double delta)
|
|
||||||
{
|
|
||||||
if (StateMachine != null)
|
|
||||||
{
|
|
||||||
StateMachine.PhysicsProcess(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Modify the <c>Character</c>'s velocity
|
|
||||||
/// </summary>
|
|
||||||
public virtual void ModifyVelocity()
|
|
||||||
{
|
|
||||||
if (StunTime > 0)
|
|
||||||
{
|
|
||||||
Velocity *= 0.25f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Die()
|
|
||||||
{
|
|
||||||
GD.Print("lol died");
|
|
||||||
QueueFree();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyImpulse(Vector2 impulse, bool resetVelocity = false)
|
|
||||||
{
|
|
||||||
// delta p = F delta t
|
|
||||||
if (resetVelocity)
|
|
||||||
Velocity = Vector2.Zero;
|
|
||||||
NetImpulse += impulse / Mass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Stun(float time)
|
|
||||||
{
|
|
||||||
StunTime += time;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void DrawTarget()
|
|
||||||
{
|
|
||||||
Vector2 target = Target;
|
|
||||||
float angle = Mathf.Atan2(target.Y, Mathf.Abs(target.X));
|
|
||||||
Vector2 scale = Inventory.Scale;
|
|
||||||
if (target.X < 0)
|
|
||||||
{
|
|
||||||
scale.Y = -1;
|
|
||||||
angle = Mathf.Pi - angle;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scale.Y = 1;
|
|
||||||
}
|
|
||||||
Inventory.Scale = scale;
|
|
||||||
Inventory.Rotation = angle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UseCurrentItem()
|
|
||||||
{
|
|
||||||
if (StunTime > 0)
|
|
||||||
{
|
|
||||||
GD.Print("tried to use weapon but stunned");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Inventory.SelectedItem is Weapon weapon)
|
_health = value;
|
||||||
|
if (_health <= 0)
|
||||||
{
|
{
|
||||||
weapon.Use();
|
Die();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LookTowardsDirection()
|
|
||||||
{
|
|
||||||
if (!Direction.IsZeroApprox())
|
|
||||||
{
|
|
||||||
Target = Direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void _on_hurtbox_received_damage(
|
|
||||||
float damage,
|
|
||||||
Character inflictor,
|
|
||||||
float knockback,
|
|
||||||
Vector2 knockbackOrigin = default,
|
|
||||||
Vector2 knockbackVector = default)
|
|
||||||
{
|
|
||||||
Health -= damage;
|
|
||||||
|
|
||||||
// create damage text
|
|
||||||
var textScene = GD.Load<PackedScene>("res://UI/FloatingText.tscn");
|
|
||||||
var instance = textScene.Instantiate<UI.FloatingText>();
|
|
||||||
instance.Text = Mathf.Round(damage).ToString();
|
|
||||||
instance.GlobalPosition = GlobalPosition;
|
|
||||||
this.GetAncestor<TileMap>().AddChild(instance);
|
|
||||||
|
|
||||||
// apply knockback
|
|
||||||
Vector2 knockbackDir = knockbackVector;
|
|
||||||
if (knockbackDir == default)
|
|
||||||
{
|
|
||||||
if (knockbackOrigin == default)
|
|
||||||
{
|
|
||||||
knockbackOrigin = inflictor.GlobalPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
knockbackDir = knockbackOrigin.DirectionTo(GlobalPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyImpulse(knockbackDir.Normalized() * knockback);
|
|
||||||
|
|
||||||
GD.Print("lol");
|
|
||||||
|
|
||||||
// play damage animation
|
|
||||||
var anim = GetNode<AnimationPlayer>("FlashAnimation");
|
|
||||||
if (anim != null)
|
|
||||||
{
|
|
||||||
anim.Stop();
|
|
||||||
anim.Play("Hurt");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if anyone involved is a player, shake their screen
|
|
||||||
Player plr = inflictor as Player ?? this as Player;
|
|
||||||
if (plr is not null)
|
|
||||||
{
|
|
||||||
plr.Camera.Shake(1, 0.4f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.GetNode("HurtSound") is AudioStreamPlayer2D sound)
|
|
||||||
{
|
|
||||||
// very small pitch deviation
|
|
||||||
sound.At(GlobalPosition).WithPitchDeviation(0.125f).Play();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsAlive => Health > 0;
|
||||||
|
|
||||||
|
protected float _health = 100f;
|
||||||
|
|
||||||
|
public double StunTime { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public AnimatedSprite2D Sprite { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Inventory Inventory { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public CharacterStateMachine StateMachine { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public ushort Faction { get; set; }
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (StateMachine != null)
|
||||||
|
{
|
||||||
|
StateMachine.Process(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite.FlipH = Target.X < 0;
|
||||||
|
DrawTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (StateMachine != null)
|
||||||
|
{
|
||||||
|
StateMachine.Input(@event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
if (StateMachine != null)
|
||||||
|
{
|
||||||
|
StateMachine.PhysicsProcess(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modify the <c>Character</c>'s velocity
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ModifyVelocity()
|
||||||
|
{
|
||||||
|
if (StunTime > 0)
|
||||||
|
{
|
||||||
|
Velocity *= 0.25f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Die()
|
||||||
|
{
|
||||||
|
GD.Print("lol died");
|
||||||
|
QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyImpulse(Vector2 impulse, bool resetVelocity = false)
|
||||||
|
{
|
||||||
|
// delta p = F delta t
|
||||||
|
if (resetVelocity)
|
||||||
|
Velocity = Vector2.Zero;
|
||||||
|
NetImpulse += impulse / Mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Stun(float time)
|
||||||
|
{
|
||||||
|
StunTime += time;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void DrawTarget()
|
||||||
|
{
|
||||||
|
Vector2 target = Target;
|
||||||
|
float angle = Mathf.Atan2(target.Y, Mathf.Abs(target.X));
|
||||||
|
Vector2 scale = Inventory.Scale;
|
||||||
|
if (target.X < 0)
|
||||||
|
{
|
||||||
|
scale.Y = -1;
|
||||||
|
angle = Mathf.Pi - angle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scale.Y = 1;
|
||||||
|
}
|
||||||
|
Inventory.Scale = scale;
|
||||||
|
Inventory.Rotation = angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UseCurrentItem()
|
||||||
|
{
|
||||||
|
if (StunTime > 0)
|
||||||
|
{
|
||||||
|
GD.Print("tried to use weapon but stunned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Inventory.SelectedItem is Weapon weapon)
|
||||||
|
{
|
||||||
|
weapon.Use();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LookTowardsDirection()
|
||||||
|
{
|
||||||
|
if (!Direction.IsZeroApprox())
|
||||||
|
{
|
||||||
|
Target = Direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void _on_hurtbox_received_damage(
|
||||||
|
float damage,
|
||||||
|
Character inflictor,
|
||||||
|
float knockback,
|
||||||
|
Vector2 knockbackOrigin = default,
|
||||||
|
Vector2 knockbackVector = default)
|
||||||
|
{
|
||||||
|
Health -= damage;
|
||||||
|
|
||||||
|
// create damage text
|
||||||
|
var textScene = GD.Load<PackedScene>("res://UI/FloatingText.tscn");
|
||||||
|
var instance = textScene.Instantiate<UI.FloatingText>();
|
||||||
|
instance.Text = Mathf.Round(damage).ToString();
|
||||||
|
instance.GlobalPosition = GlobalPosition;
|
||||||
|
this.GetAncestor<TileMap>().AddChild(instance);
|
||||||
|
|
||||||
|
// apply knockback
|
||||||
|
Vector2 knockbackDir = knockbackVector;
|
||||||
|
if (knockbackDir == default)
|
||||||
|
{
|
||||||
|
if (knockbackOrigin == default)
|
||||||
|
{
|
||||||
|
knockbackOrigin = inflictor.GlobalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
knockbackDir = knockbackOrigin.DirectionTo(GlobalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyImpulse(knockbackDir.Normalized() * knockback);
|
||||||
|
|
||||||
|
GD.Print("lol");
|
||||||
|
|
||||||
|
// play damage animation
|
||||||
|
var anim = GetNode<AnimationPlayer>("FlashAnimation");
|
||||||
|
if (anim != null)
|
||||||
|
{
|
||||||
|
anim.Stop();
|
||||||
|
anim.Play("Hurt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if anyone involved is a player, shake their screen
|
||||||
|
Player plr = inflictor as Player ?? this as Player;
|
||||||
|
if (plr is not null)
|
||||||
|
{
|
||||||
|
plr.Camera.Shake(1, 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.GetNode("HurtSound") is AudioStreamPlayer2D sound)
|
||||||
|
{
|
||||||
|
// very small pitch deviation
|
||||||
|
sound.At(GlobalPosition).WithPitchDeviation(0.125f).Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Characters
|
namespace SupaLidlGame.Characters;
|
||||||
{
|
|
||||||
public partial class Enemy : NPC
|
|
||||||
{
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Inventory.SelectedItem = Inventory.GetNode<Items.Item>("Sword");
|
|
||||||
base._Ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Die()
|
public partial class Enemy : NPC
|
||||||
{
|
{
|
||||||
base.Die();
|
public override void _Ready()
|
||||||
}
|
{
|
||||||
|
Inventory.SelectedItem = Inventory.GetNode<Items.Item>("Sword");
|
||||||
|
base._Ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Die()
|
||||||
|
{
|
||||||
|
base.Die();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,225 +3,224 @@ using SupaLidlGame.Extensions;
|
||||||
using SupaLidlGame.Items;
|
using SupaLidlGame.Items;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Characters
|
namespace SupaLidlGame.Characters;
|
||||||
|
|
||||||
|
public partial class NPC : Character
|
||||||
{
|
{
|
||||||
public partial class NPC : Character
|
/// <summary>
|
||||||
|
/// Time in seconds it takes for the NPC to think FeelsDankCube
|
||||||
|
/// </summary>
|
||||||
|
public const float ThinkTime = 0.125f;
|
||||||
|
|
||||||
|
public float[] Weights => _weights;
|
||||||
|
|
||||||
|
protected float _preferredWeightDistance = 64.0f;
|
||||||
|
protected float _maxWeightDistance = 8.0f;
|
||||||
|
protected float _preferredWeightDistanceSq = 4096.0f;
|
||||||
|
protected float _maxWeightDistanceSq = 64.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float PreferredWeightDistance
|
||||||
|
{
|
||||||
|
get => _preferredWeightDistance;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
_preferredWeightDistance = value;
|
||||||
|
_preferredWeightDistanceSq = value * value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MaxWeightDistance
|
||||||
|
{
|
||||||
|
get => _maxWeightDistance;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
_maxWeightDistance = value;
|
||||||
|
_maxWeightDistanceSq = value * value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float[] _weights = new float[16];
|
||||||
|
protected int _bestWeightIdx;
|
||||||
|
protected double _thinkTimeElapsed = 0;
|
||||||
|
protected Vector2 _blockingDir;
|
||||||
|
protected static readonly Vector2[] _weightDirs = new Vector2[16];
|
||||||
|
|
||||||
|
static NPC()
|
||||||
{
|
{
|
||||||
/// <summary>
|
for (int i = 0; i < 16; i++)
|
||||||
/// Time in seconds it takes for the NPC to think FeelsDankCube
|
|
||||||
/// </summary>
|
|
||||||
public const float ThinkTime = 0.125f;
|
|
||||||
|
|
||||||
public float[] Weights => _weights;
|
|
||||||
|
|
||||||
protected float _preferredWeightDistance = 64.0f;
|
|
||||||
protected float _maxWeightDistance = 8.0f;
|
|
||||||
protected float _preferredWeightDistanceSq = 4096.0f;
|
|
||||||
protected float _maxWeightDistanceSq = 64.0f;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float PreferredWeightDistance
|
|
||||||
{
|
|
||||||
get => _preferredWeightDistance;
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
_preferredWeightDistance = value;
|
|
||||||
_preferredWeightDistanceSq = value * value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float MaxWeightDistance
|
|
||||||
{
|
|
||||||
get => _maxWeightDistance;
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
_maxWeightDistance = value;
|
|
||||||
_maxWeightDistanceSq = value * value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected float[] _weights = new float[16];
|
|
||||||
protected int _bestWeightIdx;
|
|
||||||
protected double _thinkTimeElapsed = 0;
|
|
||||||
protected Vector2 _blockingDir;
|
|
||||||
protected static readonly Vector2[] _weightDirs = new Vector2[16];
|
|
||||||
|
|
||||||
static NPC()
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 16; i++)
|
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
|
||||||
{
|
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
|
||||||
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
|
_weightDirs[i] = new Vector2(x, y);
|
||||||
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
|
|
||||||
_weightDirs[i] = new Vector2(x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
Array.Fill(_weights, 0);
|
Array.Fill(_weights, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Draw()
|
public override void _Draw()
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
for (int i = 0; i < 16; i++)
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
Vector2 vec = _weightDirs[i] * _weights[i] * 32;
|
||||||
|
Color c = Colors.Green;
|
||||||
|
if (_bestWeightIdx == i)
|
||||||
{
|
{
|
||||||
Vector2 vec = _weightDirs[i] * _weights[i] * 32;
|
c = Colors.Blue;
|
||||||
Color c = Colors.Green;
|
|
||||||
if (_bestWeightIdx == i)
|
|
||||||
{
|
|
||||||
c = Colors.Blue;
|
|
||||||
}
|
|
||||||
else if (_weights[i] < 0)
|
|
||||||
{
|
|
||||||
c = Colors.Red;
|
|
||||||
vec = -vec;
|
|
||||||
}
|
|
||||||
DrawLine(Vector2.Zero, vec, c);
|
|
||||||
}
|
}
|
||||||
|
else if (_weights[i] < 0)
|
||||||
|
{
|
||||||
|
c = Colors.Red;
|
||||||
|
vec = -vec;
|
||||||
|
}
|
||||||
|
DrawLine(Vector2.Zero, vec, c);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
base._Draw();
|
base._Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Character FindBestTarget()
|
protected virtual Character FindBestTarget()
|
||||||
|
{
|
||||||
|
float bestDist = float.MaxValue;
|
||||||
|
Character bestChar = null;
|
||||||
|
foreach (Node node in GetParent().GetChildren())
|
||||||
{
|
{
|
||||||
float bestDist = float.MaxValue;
|
if (node is Character character && character.Faction != Faction)
|
||||||
Character bestChar = null;
|
|
||||||
foreach (Node node in GetParent().GetChildren())
|
|
||||||
{
|
{
|
||||||
if (node is Character character && character.Faction != Faction)
|
float dist = Position.DistanceTo(character.Position);
|
||||||
|
if (dist < bestDist)
|
||||||
{
|
{
|
||||||
float dist = Position.DistanceTo(character.Position);
|
bestDist = dist;
|
||||||
if (dist < bestDist)
|
bestChar = character;
|
||||||
{
|
|
||||||
bestDist = dist;
|
|
||||||
bestChar = character;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bestChar;
|
|
||||||
}
|
}
|
||||||
|
return bestChar;
|
||||||
|
}
|
||||||
|
|
||||||
public void ThinkProcess(double delta)
|
public void ThinkProcess(double delta)
|
||||||
|
{
|
||||||
|
if ((_thinkTimeElapsed += delta) > ThinkTime)
|
||||||
{
|
{
|
||||||
if ((_thinkTimeElapsed += delta) > ThinkTime)
|
_thinkTimeElapsed = 0;
|
||||||
{
|
Think();
|
||||||
_thinkTimeElapsed = 0;
|
|
||||||
Think();
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
QueueRedraw();
|
QueueRedraw();
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
Direction = _weightDirs[_bestWeightIdx];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateWeights(Vector2 pos)
|
Direction = _weightDirs[_bestWeightIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateWeights(Vector2 pos)
|
||||||
|
{
|
||||||
|
// FIXME: TODO: remove all the spaghetti
|
||||||
|
Vector2 dir = Target.Normalized();
|
||||||
|
float distSq = GlobalPosition.DistanceSquaredTo(pos);
|
||||||
|
|
||||||
|
var spaceState = GetWorld2D().DirectSpaceState;
|
||||||
|
var exclude = new Godot.Collections.Array<Godot.Rid>();
|
||||||
|
exclude.Add(this.GetRid());
|
||||||
|
|
||||||
|
// calculate weights based on distance
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
{
|
{
|
||||||
// FIXME: TODO: remove all the spaghetti
|
float directDot = _weightDirs[i].Dot(dir);
|
||||||
Vector2 dir = Target.Normalized();
|
// clamp dot from [-1, 1] to [0, 1]
|
||||||
float distSq = GlobalPosition.DistanceSquaredTo(pos);
|
directDot = (directDot + 1) / 2;
|
||||||
|
|
||||||
var spaceState = GetWorld2D().DirectSpaceState;
|
float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
|
||||||
var exclude = new Godot.Collections.Array<Godot.Rid>();
|
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
|
||||||
exclude.Add(this.GetRid());
|
strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
|
||||||
|
|
||||||
// calculate weights based on distance
|
// favor strafing when getting closer
|
||||||
for (int i = 0; i < 16; i++)
|
if (distSq > _preferredWeightDistanceSq)
|
||||||
{
|
{
|
||||||
float directDot = _weightDirs[i].Dot(dir);
|
_weights[i] = directDot;
|
||||||
// clamp dot from [-1, 1] to [0, 1]
|
|
||||||
directDot = (directDot + 1) / 2;
|
|
||||||
|
|
||||||
float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
|
|
||||||
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
|
|
||||||
strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
|
|
||||||
|
|
||||||
// favor strafing when getting closer
|
|
||||||
if (distSq > _preferredWeightDistanceSq)
|
|
||||||
{
|
|
||||||
_weights[i] = directDot;
|
|
||||||
}
|
|
||||||
else if (distSq > _maxWeightDistanceSq)
|
|
||||||
{
|
|
||||||
float dDotWeight = Mathf.Sqrt(distSq / 4096);
|
|
||||||
float sDotWeight = 1 - dDotWeight;
|
|
||||||
_weights[i] = (dDotWeight * directDot) +
|
|
||||||
(sDotWeight * strafeDot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_weights[i] = strafeDot;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (distSq > _maxWeightDistanceSq)
|
||||||
// subtract weights that collide
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
{
|
{
|
||||||
var rayParams = new PhysicsRayQueryParameters2D
|
float dDotWeight = Mathf.Sqrt(distSq / 4096);
|
||||||
{
|
float sDotWeight = 1 - dDotWeight;
|
||||||
Exclude = exclude,
|
_weights[i] = (dDotWeight * directDot) +
|
||||||
CollideWithBodies = true,
|
(sDotWeight * strafeDot);
|
||||||
From = GlobalPosition,
|
}
|
||||||
To = GlobalPosition + (_weightDirs[i] * 24),
|
else
|
||||||
CollisionMask = 1 + 2 + 16
|
{
|
||||||
};
|
_weights[i] = strafeDot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = spaceState.IntersectRay(rayParams);
|
// subtract weights that collide
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
var rayParams = new PhysicsRayQueryParameters2D
|
||||||
|
{
|
||||||
|
Exclude = exclude,
|
||||||
|
CollideWithBodies = true,
|
||||||
|
From = GlobalPosition,
|
||||||
|
To = GlobalPosition + (_weightDirs[i] * 24),
|
||||||
|
CollisionMask = 1 + 2 + 16
|
||||||
|
};
|
||||||
|
|
||||||
// if we hit something
|
var result = spaceState.IntersectRay(rayParams);
|
||||||
if (result.Count > 0)
|
|
||||||
|
// if we hit something
|
||||||
|
if (result.Count > 0)
|
||||||
|
{
|
||||||
|
// then we subtract the value of this from the other weights
|
||||||
|
float oldWeight = _weights[i];
|
||||||
|
for (int j = 0; j < 16; j++)
|
||||||
{
|
{
|
||||||
// then we subtract the value of this from the other weights
|
if (i == j)
|
||||||
float oldWeight = _weights[i];
|
|
||||||
for (int j = 0; j < 16; j++)
|
|
||||||
{
|
{
|
||||||
if (i == j)
|
_weights[i] = 0;
|
||||||
{
|
}
|
||||||
_weights[i] = 0;
|
else
|
||||||
}
|
{
|
||||||
else
|
float dot = _weightDirs[i].Dot(_weightDirs[j]);
|
||||||
{
|
_weights[j] -= _weights[j] * dot;
|
||||||
float dot = _weightDirs[i].Dot(_weightDirs[j]);
|
|
||||||
_weights[j] -= _weights[j] * dot;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float bestWeight = 0;
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
{
|
|
||||||
if (_weights[i] > bestWeight)
|
|
||||||
{
|
|
||||||
_bestWeightIdx = i;
|
|
||||||
bestWeight = _weights[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Think()
|
|
||||||
{
|
|
||||||
// TODO: the entity should wander if it doesn't find a best target
|
|
||||||
Character bestTarget = FindBestTarget();
|
|
||||||
if (bestTarget is not null)
|
|
||||||
{
|
|
||||||
Vector2 pos = FindBestTarget().GlobalPosition;
|
|
||||||
Target = pos - GlobalPosition;
|
|
||||||
Vector2 dir = Target;
|
|
||||||
float dist = GlobalPosition.DistanceSquaredTo(pos);
|
|
||||||
UpdateWeights(pos);
|
|
||||||
|
|
||||||
if (dist < 1024)
|
float bestWeight = 0;
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
if (_weights[i] > bestWeight)
|
||||||
|
{
|
||||||
|
_bestWeightIdx = i;
|
||||||
|
bestWeight = _weights[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Think()
|
||||||
|
{
|
||||||
|
// TODO: the entity should wander if it doesn't find a best target
|
||||||
|
Character bestTarget = FindBestTarget();
|
||||||
|
if (bestTarget is not null)
|
||||||
|
{
|
||||||
|
Vector2 pos = FindBestTarget().GlobalPosition;
|
||||||
|
Target = pos - GlobalPosition;
|
||||||
|
Vector2 dir = Target;
|
||||||
|
float dist = GlobalPosition.DistanceSquaredTo(pos);
|
||||||
|
UpdateWeights(pos);
|
||||||
|
|
||||||
|
if (dist < 1024)
|
||||||
|
{
|
||||||
|
if (Inventory.SelectedItem is Weapon weapon)
|
||||||
{
|
{
|
||||||
if (Inventory.SelectedItem is Weapon weapon)
|
UseCurrentItem();
|
||||||
{
|
|
||||||
UseCurrentItem();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,65 +1,64 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using SupaLidlGame.Utils;
|
using SupaLidlGame.Utils;
|
||||||
|
|
||||||
namespace SupaLidlGame.Characters
|
namespace SupaLidlGame.Characters;
|
||||||
|
|
||||||
|
public partial class Player : Character
|
||||||
{
|
{
|
||||||
public partial class Player : Character
|
private AnimatedSprite2D _sprite;
|
||||||
|
private string _spriteAnim;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public PlayerCamera Camera { get; set; }
|
||||||
|
|
||||||
|
public string Animation
|
||||||
{
|
{
|
||||||
private AnimatedSprite2D _sprite;
|
get => _sprite.Animation;
|
||||||
private string _spriteAnim;
|
set
|
||||||
|
|
||||||
[Export]
|
|
||||||
public PlayerCamera Camera { get; set; }
|
|
||||||
|
|
||||||
public string Animation
|
|
||||||
{
|
{
|
||||||
get => _sprite.Animation;
|
// the Player.Animation property might be set before this node is
|
||||||
set
|
// even ready, so if _sprite is null, then we just hold the new
|
||||||
|
// animation in a temp value, which will be assigned once this node
|
||||||
|
// is ready
|
||||||
|
if (_sprite is null)
|
||||||
{
|
{
|
||||||
// the Player.Animation property might be set before this node is
|
_spriteAnim = value;
|
||||||
// even ready, so if _sprite is null, then we just hold the new
|
return;
|
||||||
// animation in a temp value, which will be assigned once this
|
|
||||||
// node is ready
|
|
||||||
if (_sprite is null)
|
|
||||||
{
|
|
||||||
_spriteAnim = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sprite.Play(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
_sprite = GetNode<AnimatedSprite2D>("Sprite");
|
|
||||||
if (_spriteAnim != default)
|
|
||||||
{
|
|
||||||
_sprite.Animation = _spriteAnim;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ModifyVelocity()
|
|
||||||
{
|
|
||||||
if (StateMachine.CurrentState is SupaLidlGame.State.Character.PlayerRollState)
|
|
||||||
{
|
|
||||||
Velocity *= 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.ModifyVelocity();
|
_sprite.Play(value);
|
||||||
}
|
|
||||||
|
|
||||||
public override void Stun(float time)
|
|
||||||
{
|
|
||||||
base.Stun(time);
|
|
||||||
Camera.Shake(2, 0.8f);
|
|
||||||
// TODO: implement visual effects for stun
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Die()
|
|
||||||
{
|
|
||||||
GD.Print("died");
|
|
||||||
//base.Die();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_sprite = GetNode<AnimatedSprite2D>("Sprite");
|
||||||
|
if (_spriteAnim != default)
|
||||||
|
{
|
||||||
|
_sprite.Animation = _spriteAnim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ModifyVelocity()
|
||||||
|
{
|
||||||
|
if (StateMachine.CurrentState is SupaLidlGame.State.Character.PlayerRollState)
|
||||||
|
{
|
||||||
|
Velocity *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.ModifyVelocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Stun(float time)
|
||||||
|
{
|
||||||
|
base.Stun(time);
|
||||||
|
Camera.Shake(2, 0.8f);
|
||||||
|
// TODO: implement visual effects for stun
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Die()
|
||||||
|
{
|
||||||
|
GD.Print("died");
|
||||||
|
//base.Die();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
namespace SupaLidlGame.Characters.Thinkers
|
namespace SupaLidlGame.Characters.Thinkers;
|
||||||
{
|
|
||||||
public class Thinker
|
public class Thinker
|
||||||
{
|
{
|
||||||
public virtual void Think()
|
public virtual void Think()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Entities
|
namespace SupaLidlGame.Entities;
|
||||||
|
|
||||||
|
public partial class Campfire : StaticBody2D
|
||||||
{
|
{
|
||||||
public partial class Campfire : StaticBody2D
|
private PointLight2D _light;
|
||||||
|
|
||||||
|
[Signal]
|
||||||
|
public delegate void OnCampfireUseEventHandler();
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
private PointLight2D _light;
|
_light = GetNode<PointLight2D>("PointLight2D");
|
||||||
|
}
|
||||||
|
|
||||||
[Signal]
|
public override void _Process(double delta)
|
||||||
public delegate void OnCampfireUseEventHandler();
|
{
|
||||||
|
_light.Energy += (GD.Randf() - 0.5f) * 8 * (float)delta;
|
||||||
public override void _Ready()
|
_light.Energy = Math.Clamp(_light.Energy, 1.2f, 2.0f);
|
||||||
{
|
|
||||||
_light = GetNode<PointLight2D>("PointLight2D");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
_light.Energy += (GD.Randf() - 0.5f) * 8 * (float)delta;
|
|
||||||
_light.Energy = Math.Clamp(_light.Energy, 1.2f, 2.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,51 +2,50 @@ using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
using SupaLidlGame.BoundingBoxes;
|
using SupaLidlGame.BoundingBoxes;
|
||||||
|
|
||||||
namespace SupaLidlGame.Entities
|
namespace SupaLidlGame.Entities;
|
||||||
|
|
||||||
|
public partial class Projectile : RigidBody2D
|
||||||
{
|
{
|
||||||
public partial class Projectile : RigidBody2D
|
public Vector2 Velocity => Direction * Speed;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float Speed { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Vector2 Direction { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Hitbox Hitbox { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public double Lifetime { get; set; } = 10;
|
||||||
|
|
||||||
|
public Character Character { get; set; }
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
public Vector2 Velocity => Direction * Speed;
|
if ((Lifetime -= delta) <= 0)
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float Speed { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Vector2 Direction { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Hitbox Hitbox { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public double Lifetime { get; set; } = 10;
|
|
||||||
|
|
||||||
public Character Character { get; set; }
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
{
|
||||||
if ((Lifetime -= delta) <= 0)
|
QueueFree();
|
||||||
{
|
|
||||||
QueueFree();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void _PhysicsProcess(double delta)
|
public override void _PhysicsProcess(double delta)
|
||||||
{
|
{
|
||||||
Vector2 velocity = Velocity;
|
Vector2 velocity = Velocity;
|
||||||
MoveAndCollide(velocity * (float)delta);
|
MoveAndCollide(velocity * (float)delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void _on_hitbox_hit(BoundingBox box)
|
public void _on_hitbox_hit(BoundingBox box)
|
||||||
|
{
|
||||||
|
if (box is Hurtbox hurtbox)
|
||||||
{
|
{
|
||||||
if (box is Hurtbox hurtbox)
|
hurtbox.InflictDamage(
|
||||||
{
|
Hitbox.Damage,
|
||||||
hurtbox.InflictDamage(
|
Character,
|
||||||
Hitbox.Damage,
|
Hitbox.Knockback,
|
||||||
Character,
|
knockbackVector: Direction
|
||||||
Hitbox.Knockback,
|
);
|
||||||
knockbackVector: Direction
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,72 +2,71 @@ using Godot;
|
||||||
using System;
|
using System;
|
||||||
using SupaLidlGame.Utils;
|
using SupaLidlGame.Utils;
|
||||||
|
|
||||||
namespace SupaLidlGame.Extensions
|
namespace SupaLidlGame.Extensions;
|
||||||
|
|
||||||
|
public static class AudioStreamPlayer2DExtensions
|
||||||
{
|
{
|
||||||
public static class AudioStreamPlayer2DExtensions
|
public static AudioStreamPlayer2D Clone(
|
||||||
|
this AudioStreamPlayer2D audio)
|
||||||
{
|
{
|
||||||
public static AudioStreamPlayer2D Clone(
|
var clone = audio.Duplicate() as AudioStreamPlayer2D;
|
||||||
this AudioStreamPlayer2D audio)
|
clone.Finished += () =>
|
||||||
{
|
{
|
||||||
var clone = audio.Duplicate() as AudioStreamPlayer2D;
|
clone.QueueFree();
|
||||||
clone.Finished += () =>
|
};
|
||||||
{
|
return clone;
|
||||||
clone.QueueFree();
|
}
|
||||||
};
|
|
||||||
return clone;
|
public static AudioStreamPlayer2D On(
|
||||||
|
this AudioStreamPlayer2D audio,
|
||||||
|
Node parent)
|
||||||
|
{
|
||||||
|
var clone = audio.Clone();
|
||||||
|
parent.AddChild(clone);
|
||||||
|
clone.GlobalPosition = audio.GlobalPosition;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioStreamPlayer2D OnWorld(
|
||||||
|
this AudioStreamPlayer2D audio)
|
||||||
|
{
|
||||||
|
var world = audio.GetTree().Root.GetNode("World/TileMap");
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("World does not exist");
|
||||||
|
}
|
||||||
|
var clone = audio.On(world);
|
||||||
|
clone.GlobalPosition = audio.GlobalPosition;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioStreamPlayer2D At(
|
||||||
|
this AudioStreamPlayer2D audio,
|
||||||
|
Vector2 globalPosition)
|
||||||
|
{
|
||||||
|
var world = audio.GetTree().Root.GetNode("World/TileMap");
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("World does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AudioStreamPlayer2D On(
|
var parent = new Node2D();
|
||||||
this AudioStreamPlayer2D audio,
|
world.AddChild(parent);
|
||||||
Node parent)
|
parent.GlobalPosition = globalPosition;
|
||||||
|
|
||||||
|
var clone = audio.On(world);
|
||||||
|
clone.Finished += () =>
|
||||||
{
|
{
|
||||||
var clone = audio.Clone();
|
parent.QueueFree();
|
||||||
parent.AddChild(clone);
|
};
|
||||||
clone.GlobalPosition = audio.GlobalPosition;
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static AudioStreamPlayer2D OnWorld(
|
public static AudioStreamPlayer2D WithPitchDeviation(
|
||||||
this AudioStreamPlayer2D audio)
|
this AudioStreamPlayer2D audio,
|
||||||
{
|
float deviation)
|
||||||
var world = audio.GetTree().Root.GetNode("World/TileMap");
|
{
|
||||||
if (world is null)
|
audio.PitchScale = (float)GD.Randfn(audio.PitchScale, deviation);
|
||||||
{
|
return audio;
|
||||||
throw new NullReferenceException("World does not exist");
|
|
||||||
}
|
|
||||||
var clone = audio.On(world);
|
|
||||||
clone.GlobalPosition = audio.GlobalPosition;
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AudioStreamPlayer2D At(
|
|
||||||
this AudioStreamPlayer2D audio,
|
|
||||||
Vector2 globalPosition)
|
|
||||||
{
|
|
||||||
var world = audio.GetTree().Root.GetNode("World/TileMap");
|
|
||||||
if (world is null)
|
|
||||||
{
|
|
||||||
throw new NullReferenceException("World does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
var parent = new Node2D();
|
|
||||||
world.AddChild(parent);
|
|
||||||
parent.GlobalPosition = globalPosition;
|
|
||||||
|
|
||||||
var clone = audio.On(world);
|
|
||||||
clone.Finished += () =>
|
|
||||||
{
|
|
||||||
parent.QueueFree();
|
|
||||||
};
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AudioStreamPlayer2D WithPitchDeviation(
|
|
||||||
this AudioStreamPlayer2D audio,
|
|
||||||
float deviation)
|
|
||||||
{
|
|
||||||
audio.PitchScale = (float)GD.Randfn(audio.PitchScale, deviation);
|
|
||||||
return audio;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.Extensions
|
namespace SupaLidlGame.Extensions;
|
||||||
|
|
||||||
|
public static class NodeExtensions
|
||||||
{
|
{
|
||||||
public static class NodeExtensions
|
/// <summary>
|
||||||
|
/// Iterates through each ancestor until it finds an ancestor of type
|
||||||
|
/// <c>T</c>
|
||||||
|
/// </summary>
|
||||||
|
public static T GetAncestor<T>(this Node node) where T : Node
|
||||||
{
|
{
|
||||||
/// <summary>
|
Node parent;
|
||||||
/// Iterates through each ancestor until it finds an ancestor of type
|
|
||||||
/// <c>T</c>
|
while ((parent = node.GetParent()) != null)
|
||||||
/// </summary>
|
|
||||||
public static T GetAncestor<T>(this Node node) where T : Node
|
|
||||||
{
|
{
|
||||||
Node parent;
|
if (parent is T t)
|
||||||
|
|
||||||
while ((parent = node.GetParent()) != null)
|
|
||||||
{
|
{
|
||||||
if (parent is T t)
|
return t;
|
||||||
{
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = parent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
node = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return null;
|
||||||
/// A version <c>GetNode</c> that returns null rather than cause an
|
}
|
||||||
/// exception if the node is not found or is not the same type.
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// <returns>
|
/// A version <c>GetNode</c> that returns null rather than cause an
|
||||||
/// <see langword="null">null</see> if <param>name</param> does not match
|
/// exception if the node is not found or is not the same type.
|
||||||
/// a valid Node
|
/// </summary>
|
||||||
/// </returns>
|
/// <returns>
|
||||||
public static T GetN<T>(this Node node, string name) where T : Node
|
/// <see langword="null">null</see> if <param>name</param> does not match
|
||||||
{
|
/// a valid Node
|
||||||
return node.GetNode(name) as T;
|
/// </returns>
|
||||||
}
|
public static T GetN<T>(this Node node, string name) where T : Node
|
||||||
|
{
|
||||||
|
return node.GetNode(name) as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.Extensions
|
namespace SupaLidlGame.Extensions;
|
||||||
|
|
||||||
|
public static class Node2DExtensions
|
||||||
{
|
{
|
||||||
public static class Node2DExtensions
|
public static void RayCast(this Node2D node, Vector2 ray)
|
||||||
{
|
{
|
||||||
public static void RayCast(this Node2D node, Vector2 ray)
|
//var spaceState = node.GetWorld2d().DirectSpaceState;
|
||||||
{
|
//var result = spaceState.IntersectRay();
|
||||||
//var spaceState = node.GetWorld2d().DirectSpaceState;
|
|
||||||
//var result = spaceState.IntersectRay();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.Extensions
|
namespace SupaLidlGame.Extensions;
|
||||||
|
|
||||||
|
public static class Vector2Extensions
|
||||||
{
|
{
|
||||||
public static class Vector2Extensions
|
public static Vector2 Midpoint(this Vector2 vector, Vector2 other)
|
||||||
{
|
{
|
||||||
public static Vector2 Midpoint(this Vector2 vector, Vector2 other)
|
return new Vector2((vector.X + other.X) / 2,
|
||||||
|
(vector.Y + other.Y) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 Midpoints(params Vector2[] vectors)
|
||||||
|
{
|
||||||
|
int length = vectors.Length;
|
||||||
|
float x = 0;
|
||||||
|
float y = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
return new Vector2((vector.X + other.X) / 2,
|
x += vectors[i].X;
|
||||||
(vector.Y + other.Y) / 2);
|
y += vectors[i].Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector2 Midpoints(params Vector2[] vectors)
|
return new Vector2(x / length, y / length);
|
||||||
{
|
}
|
||||||
int length = vectors.Length;
|
|
||||||
float x = 0;
|
|
||||||
float y = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
/// <summary>
|
||||||
{
|
/// Returns this vector 90 degrees counter clockwise (x, y) -> (-y, x)
|
||||||
x += vectors[i].X;
|
/// </summary>
|
||||||
y += vectors[i].Y;
|
public static Vector2 Counterclockwise90(this Vector2 vector)
|
||||||
}
|
{
|
||||||
|
return new Vector2(-vector.Y, vector.X);
|
||||||
|
}
|
||||||
|
|
||||||
return new Vector2(x / length, y / length);
|
public static Vector2 Clockwise90(this Vector2 vector)
|
||||||
}
|
{
|
||||||
|
return new Vector2(vector.Y, -vector.X);
|
||||||
/// <summary>
|
|
||||||
/// Returns this vector 90 degrees counter clockwise (x, y) -> (-y, x)
|
|
||||||
/// </summary>
|
|
||||||
public static Vector2 Counterclockwise90(this Vector2 vector)
|
|
||||||
{
|
|
||||||
return new Vector2(-vector.Y, vector.X);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Vector2 Clockwise90(this Vector2 vector)
|
|
||||||
{
|
|
||||||
return new Vector2(vector.Y, -vector.X);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,145 +2,144 @@ using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
using Godot.Collections;
|
using Godot.Collections;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items
|
namespace SupaLidlGame.Items;
|
||||||
|
|
||||||
|
public partial class Inventory : Node2D
|
||||||
{
|
{
|
||||||
public partial class Inventory : Node2D
|
public Character Character { get; private set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Array<Item> Items { get; private set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Dictionary<string, int> InventoryMap { get; set; }
|
||||||
|
|
||||||
|
public const int MaxCapacity = 32;
|
||||||
|
|
||||||
|
private Item _selectedItem;
|
||||||
|
|
||||||
|
private Item _offhandItem;
|
||||||
|
|
||||||
|
public Item SelectedItem
|
||||||
{
|
{
|
||||||
public Character Character { get; private set; }
|
get => _selectedItem;
|
||||||
|
set => EquipItem(value, ref _selectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public Item OffhandItem
|
||||||
public Array<Item> Items { get; private set; }
|
{
|
||||||
|
get => _selectedItem;
|
||||||
|
set => EquipItem(value, ref _offhandItem);
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public bool IsUsingItem => (SelectedItem?.IsUsing ?? false) ||
|
||||||
public Dictionary<string, int> InventoryMap { get; set; }
|
(OffhandItem?.IsUsing ?? false);
|
||||||
|
|
||||||
public const int MaxCapacity = 32;
|
public Inventory()
|
||||||
|
{
|
||||||
|
InventoryMap = new Dictionary<string, int>();
|
||||||
|
InventoryMap.Add("equip_1", 0);
|
||||||
|
InventoryMap.Add("equip_2", 1);
|
||||||
|
InventoryMap.Add("equip_3", 2);
|
||||||
|
}
|
||||||
|
|
||||||
private Item _selectedItem;
|
public override void _Ready()
|
||||||
|
{
|
||||||
private Item _offhandItem;
|
if (Items is null)
|
||||||
|
|
||||||
public Item SelectedItem
|
|
||||||
{
|
{
|
||||||
get => _selectedItem;
|
// instantiating a new array will prevent characters from
|
||||||
set => EquipItem(value, ref _selectedItem);
|
// sharing inventories
|
||||||
|
Items = new Array<Item>();
|
||||||
}
|
}
|
||||||
|
Character = GetParent<Character>();
|
||||||
public Item OffhandItem
|
foreach (Node child in GetChildren())
|
||||||
{
|
{
|
||||||
get => _selectedItem;
|
if (child is Item item)
|
||||||
set => EquipItem(value, ref _offhandItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsUsingItem => (SelectedItem?.IsUsing ?? false) ||
|
|
||||||
(OffhandItem?.IsUsing ?? false);
|
|
||||||
|
|
||||||
public Inventory()
|
|
||||||
{
|
|
||||||
InventoryMap = new Dictionary<string, int>();
|
|
||||||
InventoryMap.Add("equip_1", 0);
|
|
||||||
InventoryMap.Add("equip_2", 1);
|
|
||||||
InventoryMap.Add("equip_3", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
if (Items is null)
|
|
||||||
{
|
{
|
||||||
// instantiating a new array will prevent characters from
|
GD.Print("Adding item " + item.Name);
|
||||||
// sharing inventories
|
AddItem(item);
|
||||||
Items = new Array<Item>();
|
|
||||||
}
|
}
|
||||||
Character = GetParent<Character>();
|
|
||||||
foreach (Node child in GetChildren())
|
|
||||||
{
|
|
||||||
if (child is Item item)
|
|
||||||
{
|
|
||||||
GD.Print("Adding item " + item.Name);
|
|
||||||
AddItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base._Ready();
|
|
||||||
}
|
}
|
||||||
|
base._Ready();
|
||||||
|
}
|
||||||
|
|
||||||
private bool EquipItem(Item item, ref Item slot)
|
private bool EquipItem(Item item, ref Item slot)
|
||||||
|
{
|
||||||
|
if (item is not null)
|
||||||
{
|
{
|
||||||
if (item is not null)
|
if (item.IsOneHanded)
|
||||||
{
|
{
|
||||||
if (item.IsOneHanded)
|
// we can not equip this if either hand is occupied by
|
||||||
|
// two-handed item
|
||||||
|
|
||||||
|
if (_selectedItem is not null && !_selectedItem.IsOneHanded)
|
||||||
{
|
{
|
||||||
// we can not equip this if either hand is occupied by
|
return false;
|
||||||
// two-handed item
|
|
||||||
|
|
||||||
if (_selectedItem is not null && !_selectedItem.IsOneHanded)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_offhandItem is not null && !_offhandItem.IsOneHanded)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Items.Contains(item))
|
if (_offhandItem is not null && !_offhandItem.IsOneHanded)
|
||||||
{
|
{
|
||||||
GD.PrintErr("Tried to equip an item not in the inventory.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slot is not null)
|
if (!Items.Contains(item))
|
||||||
{
|
{
|
||||||
slot.Unequip(Character);
|
GD.PrintErr("Tried to equip an item not in the inventory.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
slot = item;
|
|
||||||
|
|
||||||
if (item is not null)
|
|
||||||
{
|
|
||||||
item.Equip(Character);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Item GetItemByMap(string keymap)
|
if (slot is not null)
|
||||||
{
|
{
|
||||||
if (InventoryMap.ContainsKey(keymap))
|
slot.Unequip(Character);
|
||||||
|
}
|
||||||
|
|
||||||
|
slot = item;
|
||||||
|
|
||||||
|
if (item is not null)
|
||||||
|
{
|
||||||
|
item.Equip(Character);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item GetItemByMap(string keymap)
|
||||||
|
{
|
||||||
|
if (InventoryMap.ContainsKey(keymap))
|
||||||
|
{
|
||||||
|
int idx = InventoryMap[keymap];
|
||||||
|
if (idx < Items.Count)
|
||||||
{
|
{
|
||||||
int idx = InventoryMap[keymap];
|
return Items[InventoryMap[keymap]];
|
||||||
if (idx < Items.Count)
|
|
||||||
{
|
|
||||||
return Items[InventoryMap[keymap]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else GD.Print(keymap + " does not exist");
|
}
|
||||||
|
else GD.Print(keymap + " does not exist");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item AddItem(Item item)
|
||||||
|
{
|
||||||
|
if (Items.Count >= MaxCapacity)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Item AddItem(Item item)
|
item.CharacterOwner = Character;
|
||||||
|
item.Visible = false;
|
||||||
|
if (!Items.Contains(item))
|
||||||
{
|
{
|
||||||
if (Items.Count >= MaxCapacity)
|
Items.Add(item);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.CharacterOwner = Character;
|
|
||||||
item.Visible = false;
|
|
||||||
if (!Items.Contains(item))
|
|
||||||
{
|
|
||||||
Items.Add(item);
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
public Item DropItem(Item item)
|
public Item DropItem(Item item)
|
||||||
{
|
{
|
||||||
item.CharacterOwner = null;
|
item.CharacterOwner = null;
|
||||||
item.Visible = true;
|
item.Visible = true;
|
||||||
var e = SelectedItem = item;
|
var e = SelectedItem = item;
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,52 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items
|
namespace SupaLidlGame.Items;
|
||||||
|
|
||||||
|
public abstract partial class Item : Node2D
|
||||||
{
|
{
|
||||||
public abstract partial class Item : Node2D
|
[Export]
|
||||||
|
public string ItemName { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public bool CanStack { get; set; } = false;
|
||||||
|
|
||||||
|
public int Count { get; set; } = 1;
|
||||||
|
|
||||||
|
public bool IsOneHanded { get; set; } = false;
|
||||||
|
|
||||||
|
public Character CharacterOwner { get; set; }
|
||||||
|
|
||||||
|
public virtual bool IsUsing => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if this item can directly stack with other items
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool StacksWith(Item item)
|
||||||
{
|
{
|
||||||
[Export]
|
if (!CanStack)
|
||||||
public string ItemName { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public string Description { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public bool CanStack { get; set; } = false;
|
|
||||||
|
|
||||||
public int Count { get; set; } = 1;
|
|
||||||
|
|
||||||
public bool IsOneHanded { get; set; } = false;
|
|
||||||
|
|
||||||
public Character CharacterOwner { get; set; }
|
|
||||||
|
|
||||||
public virtual bool IsUsing => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if this item can directly stack with other items
|
|
||||||
/// </summary>
|
|
||||||
public virtual bool StacksWith(Item item)
|
|
||||||
{
|
{
|
||||||
if (!CanStack)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ItemName != item.ItemName)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// several more conditions may be added soon
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void Equip(Character character);
|
if (ItemName != item.ItemName)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void Unequip(Character character);
|
// several more conditions may be added soon
|
||||||
|
|
||||||
public abstract void Use();
|
return true;
|
||||||
|
|
||||||
public abstract void Deuse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void Equip(Character character);
|
||||||
|
|
||||||
|
public abstract void Unequip(Character character);
|
||||||
|
|
||||||
|
public abstract void Use();
|
||||||
|
|
||||||
|
public abstract void Deuse();
|
||||||
}
|
}
|
||||||
|
|
195
Items/Weapon.cs
195
Items/Weapon.cs
|
@ -2,109 +2,108 @@ using Godot;
|
||||||
using SupaLidlGame.BoundingBoxes;
|
using SupaLidlGame.BoundingBoxes;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items
|
namespace SupaLidlGame.Items;
|
||||||
|
|
||||||
|
public abstract partial class Weapon : Item
|
||||||
{
|
{
|
||||||
public abstract partial class Weapon : Item
|
public double RemainingUseTime { get; protected set; } = 0;
|
||||||
|
|
||||||
|
public override bool IsUsing => RemainingUseTime > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much damage in HP that this weapon deals.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public float Damage { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time in seconds it takes for this weapon to become available
|
||||||
|
/// again after using.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public double UseTime { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The magnitude of the knockback force of the weapon.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public float Knockback { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initial velocity of any projectile the weapon may spawn.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public float InitialVelocity { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the weapon if it is idle (i.e. not being used).
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public bool ShouldHideIdle { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freezes the player's target angle if this weapon is being used.
|
||||||
|
/// </summary>
|
||||||
|
[Export]
|
||||||
|
public bool ShouldFreezeAngleOnUse { get; set; } = true;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MinDistanceHint { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MaxDistanceHint { get; set; }
|
||||||
|
|
||||||
|
public virtual bool IsParryable { get; protected set; } = false;
|
||||||
|
|
||||||
|
public bool IsParried { get; set; }
|
||||||
|
|
||||||
|
public Character Character { get; set; }
|
||||||
|
|
||||||
|
public Vector2 UseDirection { get; set; }
|
||||||
|
|
||||||
|
public override bool StacksWith(Item item) => false;
|
||||||
|
|
||||||
|
public override void Equip(Character character)
|
||||||
{
|
{
|
||||||
public double RemainingUseTime { get; protected set; } = 0;
|
if (!ShouldHideIdle || IsUsing)
|
||||||
|
|
||||||
public override bool IsUsing => RemainingUseTime > 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How much damage in HP that this weapon deals.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public float Damage { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time in seconds it takes for this weapon to become available
|
|
||||||
/// again after using.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public double UseTime { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The magnitude of the knockback force of the weapon.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public float Knockback { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The initial velocity of any projectile the weapon may spawn.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public float InitialVelocity { get; set; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hides the weapon if it is idle (i.e. not being used).
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public bool ShouldHideIdle { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Freezes the player's target angle if this weapon is being used.
|
|
||||||
/// </summary>
|
|
||||||
[Export]
|
|
||||||
public bool ShouldFreezeAngleOnUse { get; set; } = true;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float MinDistanceHint { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float MaxDistanceHint { get; set; }
|
|
||||||
|
|
||||||
public virtual bool IsParryable { get; protected set; } = false;
|
|
||||||
|
|
||||||
public bool IsParried { get; set; }
|
|
||||||
|
|
||||||
public Character Character { get; set; }
|
|
||||||
|
|
||||||
public Vector2 UseDirection { get; set; }
|
|
||||||
|
|
||||||
public override bool StacksWith(Item item) => false;
|
|
||||||
|
|
||||||
public override void Equip(Character character)
|
|
||||||
{
|
{
|
||||||
if (!ShouldHideIdle || IsUsing)
|
Visible = true;
|
||||||
|
}
|
||||||
|
Character = character;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Unequip(Character character)
|
||||||
|
{
|
||||||
|
Visible = false;
|
||||||
|
Character = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Use()
|
||||||
|
{
|
||||||
|
RemainingUseTime = UseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Deuse()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (RemainingUseTime > 0)
|
||||||
|
{
|
||||||
|
if ((RemainingUseTime -= delta) <= 0)
|
||||||
{
|
{
|
||||||
Visible = true;
|
//Deuse();
|
||||||
}
|
|
||||||
Character = character;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Unequip(Character character)
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
Character = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Use()
|
|
||||||
{
|
|
||||||
RemainingUseTime = UseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Deuse()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
if (RemainingUseTime > 0)
|
|
||||||
{
|
|
||||||
if ((RemainingUseTime -= delta) <= 0)
|
|
||||||
{
|
|
||||||
//Deuse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void _on_hitbox_hit(BoundingBox box)
|
|
||||||
{
|
|
||||||
if (box is Hurtbox hurtbox)
|
|
||||||
{
|
|
||||||
hurtbox.InflictDamage(Damage, Character, Knockback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void _on_hitbox_hit(BoundingBox box)
|
||||||
|
{
|
||||||
|
if (box is Hurtbox hurtbox)
|
||||||
|
{
|
||||||
|
hurtbox.InflictDamage(Damage, Character, Knockback);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
namespace SupaLidlGame.Items.Weapons
|
namespace SupaLidlGame.Items.Weapons;
|
||||||
|
|
||||||
|
public interface IParryable
|
||||||
{
|
{
|
||||||
public interface IParryable
|
public bool IsParryable { get; }
|
||||||
{
|
public bool IsParried { get; }
|
||||||
public bool IsParryable { get; }
|
public ulong ParryTimeOrigin { get; }
|
||||||
public bool IsParried { get; }
|
public void Stun();
|
||||||
public ulong ParryTimeOrigin { get; }
|
|
||||||
public void Stun();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using SupaLidlGame.Extensions;
|
using SupaLidlGame.Extensions;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items.Weapons
|
namespace SupaLidlGame.Items.Weapons;
|
||||||
|
|
||||||
|
public partial class Railgun : Ranged
|
||||||
{
|
{
|
||||||
public partial class Railgun : Ranged
|
public override void Attack()
|
||||||
{
|
{
|
||||||
public override void Attack()
|
// create projectile
|
||||||
{
|
PackedScene scene = GD.Load<PackedScene>("res://Entities/RailBeam.tscn");
|
||||||
// create projectile
|
GD.Print("lol");
|
||||||
PackedScene scene = GD.Load<PackedScene>("res://Entities/RailBeam.tscn");
|
var projectile = scene.Instantiate<Entities.Projectile>();
|
||||||
GD.Print("lol");
|
projectile.Hitbox.Faction = Character.Faction;
|
||||||
var projectile = scene.Instantiate<Entities.Projectile>();
|
projectile.Direction = Character.Target;
|
||||||
projectile.Hitbox.Faction = Character.Faction;
|
projectile.GlobalPosition = GlobalPosition;
|
||||||
projectile.Direction = Character.Target;
|
projectile.GlobalRotation = projectile.Direction.Angle();
|
||||||
projectile.GlobalPosition = GlobalPosition;
|
this.GetAncestor<SupaLidlGame.Scenes.Map>()
|
||||||
projectile.GlobalRotation = projectile.Direction.Angle();
|
.Entities.AddChild(projectile);
|
||||||
this.GetAncestor<SupaLidlGame.Scenes.Map>()
|
|
||||||
.Entities.AddChild(projectile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,42 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items.Weapons
|
namespace SupaLidlGame.Items.Weapons;
|
||||||
|
|
||||||
|
public abstract partial class Ranged : Weapon
|
||||||
{
|
{
|
||||||
public abstract partial class Ranged : Weapon
|
[Export]
|
||||||
|
public float AngleDeviation { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float ChargeTime { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public State.Weapon.WeaponStateMachine StateMachine { get; set; }
|
||||||
|
|
||||||
|
public override bool IsUsing => StateMachine.CurrentState
|
||||||
|
is State.Weapon.RangedFireState;
|
||||||
|
|
||||||
|
public bool IsChargeable => ChargeTime > 0;
|
||||||
|
|
||||||
|
public bool IsCharging { get; protected set; }
|
||||||
|
|
||||||
|
public override void Use()
|
||||||
{
|
{
|
||||||
[Export]
|
StateMachine.Use();
|
||||||
public float AngleDeviation { get; set; }
|
base.Use();
|
||||||
|
|
||||||
[Export]
|
|
||||||
public float ChargeTime { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public State.Weapon.WeaponStateMachine StateMachine { get; set; }
|
|
||||||
|
|
||||||
public override bool IsUsing => StateMachine.CurrentState
|
|
||||||
is State.Weapon.RangedFireState;
|
|
||||||
|
|
||||||
public bool IsChargeable => ChargeTime > 0;
|
|
||||||
|
|
||||||
public bool IsCharging { get; protected set; }
|
|
||||||
|
|
||||||
public override void Use()
|
|
||||||
{
|
|
||||||
StateMachine.Use();
|
|
||||||
base.Use();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Deuse()
|
|
||||||
{
|
|
||||||
StateMachine.Deuse();
|
|
||||||
base.Deuse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
StateMachine.Process(delta);
|
|
||||||
base._Process(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void Attack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Deuse()
|
||||||
|
{
|
||||||
|
StateMachine.Deuse();
|
||||||
|
base.Deuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
StateMachine.Process(delta);
|
||||||
|
base._Process(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Attack();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,234 +4,233 @@ using SupaLidlGame.Characters;
|
||||||
using SupaLidlGame.Extensions;
|
using SupaLidlGame.Extensions;
|
||||||
using SupaLidlGame.State.Weapon;
|
using SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
namespace SupaLidlGame.Items.Weapons
|
namespace SupaLidlGame.Items.Weapons;
|
||||||
|
|
||||||
|
public partial class Sword : Weapon, IParryable
|
||||||
{
|
{
|
||||||
public partial class Sword : Weapon, IParryable
|
public bool IsAttacking { get; protected set; }
|
||||||
|
|
||||||
|
public override bool IsUsing => StateMachine.CurrentState
|
||||||
|
is SwordAttackState;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Hitbox Hitbox { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public AnimationPlayer AnimationPlayer { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public AnimationTree AnimationTree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time frame in seconds for which the weapon will deal damage.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The value of <c>AttackTime</c> should be less than the
|
||||||
|
/// value of <c>UseTime</c>
|
||||||
|
/// </remarks>
|
||||||
|
[Export]
|
||||||
|
public double AttackTime { get; set; } = 0;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public double AttackAnimationDuration { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public CpuParticles2D ParryParticles { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public double NPCAnticipateTime { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public WeaponStateMachine StateMachine { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Node2D Anchor { get; set; }
|
||||||
|
|
||||||
|
public override bool IsParryable { get; protected set; }
|
||||||
|
|
||||||
|
public ulong ParryTimeOrigin { get; protected set; }
|
||||||
|
|
||||||
|
private Tween _currentTween;
|
||||||
|
|
||||||
|
private AnimationNodeStateMachinePlayback _playback;
|
||||||
|
|
||||||
|
public override void Equip(Character character)
|
||||||
{
|
{
|
||||||
public bool IsAttacking { get; protected set; }
|
base.Equip(character);
|
||||||
|
Hitbox.Faction = character.Faction; // character is null before base
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsUsing => StateMachine.CurrentState
|
public override void Unequip(Character character)
|
||||||
is SwordAttackState;
|
{
|
||||||
|
base.Unequip(character);
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public override void Use()
|
||||||
public Hitbox Hitbox { get; set; }
|
{
|
||||||
|
// we can't use if we're still using the weapon
|
||||||
[Export]
|
if (RemainingUseTime > 0)
|
||||||
public AnimationPlayer AnimationPlayer { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public AnimationTree AnimationTree { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time frame in seconds for which the weapon will deal damage.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The value of <c>AttackTime</c> should be less than the
|
|
||||||
/// value of <c>UseTime</c>
|
|
||||||
/// </remarks>
|
|
||||||
[Export]
|
|
||||||
public double AttackTime { get; set; } = 0;
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public double AttackAnimationDuration { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public CpuParticles2D ParryParticles { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public double NPCAnticipateTime { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public WeaponStateMachine StateMachine { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Node2D Anchor { get; set; }
|
|
||||||
|
|
||||||
public override bool IsParryable { get; protected set; }
|
|
||||||
|
|
||||||
public ulong ParryTimeOrigin { get; protected set; }
|
|
||||||
|
|
||||||
private Tween _currentTween;
|
|
||||||
|
|
||||||
private AnimationNodeStateMachinePlayback _playback;
|
|
||||||
|
|
||||||
public override void Equip(Character character)
|
|
||||||
{
|
{
|
||||||
base.Equip(character);
|
//return;
|
||||||
Hitbox.Faction = character.Faction; // character is null before base
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Unequip(Character character)
|
StateMachine.Use();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// reset state of the weapon
|
||||||
|
IsParried = false;
|
||||||
|
IsParryable = true;
|
||||||
|
ParryTimeOrigin = Time.GetTicksMsec();
|
||||||
|
|
||||||
|
_playback.Travel("use");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// play animation depending on rotation of weapon
|
||||||
|
/*
|
||||||
|
string anim = "use";
|
||||||
|
|
||||||
|
if (GetNode<Node2D>("Anchor").Rotation > Mathf.DegToRad(50))
|
||||||
{
|
{
|
||||||
base.Unequip(character);
|
anim = "use2";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Use()
|
if (Character is NPC)
|
||||||
{
|
{
|
||||||
// we can't use if we're still using the weapon
|
// NPCs have a slower attack
|
||||||
if (RemainingUseTime > 0)
|
anim += "-npc";
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationPlayer.Play(anim);
|
||||||
|
*/
|
||||||
|
|
||||||
|
base.Use();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableParry()
|
||||||
|
{
|
||||||
|
IsParried = false;
|
||||||
|
IsParryable = true;
|
||||||
|
ParryTimeOrigin = Time.GetTicksMsec();
|
||||||
|
GD.Print(Character.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableParry()
|
||||||
|
{
|
||||||
|
IsParryable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Deuse()
|
||||||
|
{
|
||||||
|
//AnimationPlayer.Stop();
|
||||||
|
Deattack();
|
||||||
|
base.Deuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Attack()
|
||||||
|
{
|
||||||
|
//RemainingAttackTime = AttackTime;
|
||||||
|
IsAttacking = true;
|
||||||
|
Hitbox.IsDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deattack()
|
||||||
|
{
|
||||||
|
IsAttacking = false;
|
||||||
|
DisableParry();
|
||||||
|
Hitbox.IsDisabled = true;
|
||||||
|
ProcessHits();
|
||||||
|
Hitbox.ResetIgnoreList();
|
||||||
|
AnimationPlayer.SpeedScale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
Hitbox.Damage = Damage;
|
||||||
|
_playback = (AnimationNodeStateMachinePlayback)AnimationTree
|
||||||
|
.Get("parameters/playback");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
StateMachine.Process(delta);
|
||||||
|
base._Process(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessHits()
|
||||||
|
{
|
||||||
|
if (IsParried)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (BoundingBox box in Hitbox.Hits)
|
||||||
|
{
|
||||||
|
GD.Print("processing hit");
|
||||||
|
if (box is Hurtbox hurtbox)
|
||||||
{
|
{
|
||||||
//return;
|
hurtbox.InflictDamage(Damage, Character, Knockback);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StateMachine.Use();
|
public void AttemptParry(Weapon otherWeapon)
|
||||||
|
{
|
||||||
/*
|
//if (IsParryable && otherWeapon.IsParryable)
|
||||||
// reset state of the weapon
|
if (otherWeapon.IsParryable &&
|
||||||
IsParried = false;
|
otherWeapon is IParryable otherParryable)
|
||||||
IsParryable = true;
|
{
|
||||||
ParryTimeOrigin = Time.GetTicksMsec();
|
ParryParticles.Emitting = true;
|
||||||
|
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin)
|
||||||
_playback.Travel("use");
|
|
||||||
*/
|
|
||||||
|
|
||||||
// play animation depending on rotation of weapon
|
|
||||||
/*
|
|
||||||
string anim = "use";
|
|
||||||
|
|
||||||
if (GetNode<Node2D>("Anchor").Rotation > Mathf.DegToRad(50))
|
|
||||||
{
|
{
|
||||||
anim = "use2";
|
// our character was parried
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (Character is NPC)
|
|
||||||
{
|
{
|
||||||
// NPCs have a slower attack
|
otherParryable.Stun();
|
||||||
anim += "-npc";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
//this.GetAncestor<TileMap>().AddChild(instance);
|
||||||
|
}
|
||||||
|
|
||||||
AnimationPlayer.Play(anim);
|
public void Stun()
|
||||||
*/
|
{
|
||||||
|
IsParried = true;
|
||||||
|
AnimationPlayer.SpeedScale = 0.25f;
|
||||||
|
Character.Stun(1.5f);
|
||||||
|
GetNode<AudioStreamPlayer2D>("ParrySound").OnWorld().Play();
|
||||||
|
}
|
||||||
|
|
||||||
base.Use();
|
public override void _on_hitbox_hit(BoundingBox box)
|
||||||
|
{
|
||||||
|
if (IsParried)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnableParry()
|
if (box is Hitbox hb)
|
||||||
{
|
{
|
||||||
IsParried = false;
|
Weapon w = hb.GetAncestor<Weapon>();
|
||||||
IsParryable = true;
|
if (w is not null)
|
||||||
ParryTimeOrigin = Time.GetTicksMsec();
|
|
||||||
GD.Print(Character.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableParry()
|
|
||||||
{
|
|
||||||
IsParryable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Deuse()
|
|
||||||
{
|
|
||||||
//AnimationPlayer.Stop();
|
|
||||||
Deattack();
|
|
||||||
base.Deuse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Attack()
|
|
||||||
{
|
|
||||||
//RemainingAttackTime = AttackTime;
|
|
||||||
IsAttacking = true;
|
|
||||||
Hitbox.IsDisabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Deattack()
|
|
||||||
{
|
|
||||||
IsAttacking = false;
|
|
||||||
DisableParry();
|
|
||||||
Hitbox.IsDisabled = true;
|
|
||||||
ProcessHits();
|
|
||||||
Hitbox.ResetIgnoreList();
|
|
||||||
AnimationPlayer.SpeedScale = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Hitbox.Damage = Damage;
|
|
||||||
_playback = (AnimationNodeStateMachinePlayback)AnimationTree
|
|
||||||
.Get("parameters/playback");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
StateMachine.Process(delta);
|
|
||||||
base._Process(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessHits()
|
|
||||||
{
|
|
||||||
if (IsParried)
|
|
||||||
{
|
{
|
||||||
return;
|
AttemptParry(w);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (BoundingBox box in Hitbox.Hits)
|
|
||||||
{
|
|
||||||
GD.Print("processing hit");
|
|
||||||
if (box is Hurtbox hurtbox)
|
|
||||||
{
|
|
||||||
hurtbox.InflictDamage(Damage, Character, Knockback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttemptParry(Weapon otherWeapon)
|
if (box is Hurtbox hurt)
|
||||||
{
|
{
|
||||||
//if (IsParryable && otherWeapon.IsParryable)
|
if (hurt.GetParent() is Character c)
|
||||||
if (otherWeapon.IsParryable &&
|
|
||||||
otherWeapon is IParryable otherParryable)
|
|
||||||
{
|
{
|
||||||
ParryParticles.Emitting = true;
|
var item = c.Inventory.SelectedItem;
|
||||||
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin)
|
if (item is Weapon w)
|
||||||
{
|
|
||||||
// our character was parried
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
otherParryable.Stun();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//this.GetAncestor<TileMap>().AddChild(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stun()
|
|
||||||
{
|
|
||||||
IsParried = true;
|
|
||||||
AnimationPlayer.SpeedScale = 0.25f;
|
|
||||||
Character.Stun(1.5f);
|
|
||||||
GetNode<AudioStreamPlayer2D>("ParrySound").OnWorld().Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _on_hitbox_hit(BoundingBox box)
|
|
||||||
{
|
|
||||||
if (IsParried)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (box is Hitbox hb)
|
|
||||||
{
|
|
||||||
Weapon w = hb.GetAncestor<Weapon>();
|
|
||||||
if (w is not null)
|
|
||||||
{
|
{
|
||||||
AttemptParry(w);
|
AttemptParry(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (box is Hurtbox hurt)
|
|
||||||
{
|
|
||||||
if (hurt.GetParent() is Character c)
|
|
||||||
{
|
|
||||||
var item = c.Inventory.SelectedItem;
|
|
||||||
if (item is Weapon w)
|
|
||||||
{
|
|
||||||
AttemptParry(w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void SetAnimationCondition(string condition, bool value)
|
|
||||||
{
|
|
||||||
AnimationTree.Set("parameters/conditions/" + condition, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void SetAnimationCondition(string condition, bool value)
|
||||||
|
{
|
||||||
|
AnimationTree.Set("parameters/conditions/" + condition, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
[ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"]
|
[ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bxtpv6jqodj4v" path="res://Scenes/Maps/Hills.tscn" id="2_juio7"]
|
[ext_resource type="PackedScene" uid="uid://bxtpv6jqodj4v" path="res://Scenes/Maps/Hills.tscn" id="2_juio7"]
|
||||||
|
|
||||||
[node name="World" type="Node2D" node_paths=PackedStringArray("CurrentPlayer")]
|
[node name="World" type="Node2D"]
|
||||||
script = ExtResource("1_1k6ew")
|
script = ExtResource("1_1k6ew")
|
||||||
StartingArea = ExtResource("2_juio7")
|
StartingArea = ExtResource("2_juio7")
|
||||||
CurrentPlayer = NodePath("")
|
|
||||||
|
|
|
@ -1,48 +1,47 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Scenes
|
namespace SupaLidlGame.Scenes;
|
||||||
|
|
||||||
|
public partial class Map : TileMap
|
||||||
{
|
{
|
||||||
public partial class Map : TileMap
|
[Export]
|
||||||
|
public Node2D Entities { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Node2D Areas { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Node2D Spawners { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Vector2 CameraLowerBound { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Vector2 CameraUpperBound { get; set; }
|
||||||
|
|
||||||
|
private bool _active;
|
||||||
|
|
||||||
|
public bool Active
|
||||||
{
|
{
|
||||||
[Export]
|
get
|
||||||
public Node2D Entities { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Node2D Areas { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Node2D Spawners { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Vector2 CameraLowerBound { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Vector2 CameraUpperBound { get; set; }
|
|
||||||
|
|
||||||
private bool _active;
|
|
||||||
|
|
||||||
public bool Active
|
|
||||||
{
|
{
|
||||||
get
|
return _active;
|
||||||
{
|
|
||||||
return _active;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_active = Visible = value;
|
|
||||||
SetProcess(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
set
|
||||||
public override void _Ready()
|
|
||||||
{
|
{
|
||||||
Active = true;
|
_active = Visible = value;
|
||||||
}
|
SetProcess(value);
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
base._Process(delta);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
base._Process(delta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,69 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public abstract partial class CharacterState : Node, IState<CharacterState>
|
||||||
{
|
{
|
||||||
public abstract partial class CharacterState : Node, IState<CharacterState>
|
[Export]
|
||||||
|
public Characters.Character Character { get; set; }
|
||||||
|
|
||||||
|
public virtual IState<CharacterState> Enter(IState<CharacterState> prev) => null;
|
||||||
|
|
||||||
|
public virtual void Exit(IState<CharacterState> next)
|
||||||
{
|
{
|
||||||
[Export]
|
|
||||||
public Characters.Character Character { get; set; }
|
|
||||||
|
|
||||||
public virtual IState<CharacterState> Enter(IState<CharacterState> prev) => null;
|
|
||||||
|
|
||||||
public virtual void Exit(IState<CharacterState> next)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual CharacterState Process(double delta)
|
|
||||||
{
|
|
||||||
if (Character.StunTime > 0)
|
|
||||||
{
|
|
||||||
Character.StunTime -= delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = Character.Inventory.SelectedItem;
|
|
||||||
var offhand = Character.Inventory.OffhandItem;
|
|
||||||
|
|
||||||
// angle towards item use angle or offhand use angle if not used
|
|
||||||
|
|
||||||
bool targetTowards(Items.Item item)
|
|
||||||
{
|
|
||||||
if (item is Items.Weapon weapon)
|
|
||||||
{
|
|
||||||
if (weapon.IsUsing)
|
|
||||||
{
|
|
||||||
Character.Target = weapon.UseDirection;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = targetTowards(item) || targetTowards(offhand);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual CharacterState PhysicsProcess(double delta)
|
|
||||||
{
|
|
||||||
Character.Velocity = Character.NetImpulse;
|
|
||||||
|
|
||||||
if (Character.NetImpulse.LengthSquared() < Mathf.Pow(Character.Speed, 2))
|
|
||||||
{
|
|
||||||
Character.Velocity += Character.Direction.Normalized()
|
|
||||||
* Character.Speed;
|
|
||||||
// we should only modify velocity that is in the player's control
|
|
||||||
Character.ModifyVelocity();
|
|
||||||
}
|
|
||||||
|
|
||||||
Character.NetImpulse = Character.NetImpulse.MoveToward(
|
|
||||||
Vector2.Zero,
|
|
||||||
(float)delta * Character.Speed * Character.Friction);
|
|
||||||
|
|
||||||
Character.MoveAndSlide();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual CharacterState Input(InputEvent @event) => null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
if (Character.StunTime > 0)
|
||||||
|
{
|
||||||
|
Character.StunTime -= delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = Character.Inventory.SelectedItem;
|
||||||
|
var offhand = Character.Inventory.OffhandItem;
|
||||||
|
|
||||||
|
// angle towards item use angle or offhand use angle if not used
|
||||||
|
|
||||||
|
bool targetTowards(Items.Item item)
|
||||||
|
{
|
||||||
|
if (item is Items.Weapon weapon)
|
||||||
|
{
|
||||||
|
if (weapon.IsUsing)
|
||||||
|
{
|
||||||
|
Character.Target = weapon.UseDirection;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = targetTowards(item) || targetTowards(offhand);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual CharacterState PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
Character.Velocity = Character.NetImpulse;
|
||||||
|
|
||||||
|
if (Character.NetImpulse.LengthSquared() < Mathf.Pow(Character.Speed, 2))
|
||||||
|
{
|
||||||
|
Character.Velocity += Character.Direction.Normalized()
|
||||||
|
* Character.Speed;
|
||||||
|
// we should only modify velocity that is in the player's control
|
||||||
|
Character.ModifyVelocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
Character.NetImpulse = Character.NetImpulse.MoveToward(
|
||||||
|
Vector2.Zero,
|
||||||
|
(float)delta * Character.Speed * Character.Friction);
|
||||||
|
|
||||||
|
Character.MoveAndSlide();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual CharacterState Input(InputEvent @event) => null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,39 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class CharacterStateMachine : StateMachine<CharacterState>
|
||||||
{
|
{
|
||||||
public partial class CharacterStateMachine : StateMachine<CharacterState>
|
[Export]
|
||||||
|
public override CharacterState InitialState { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Characters.Character Character { get; set; }
|
||||||
|
|
||||||
|
public void Process(double delta)
|
||||||
{
|
{
|
||||||
[Export]
|
var state = CurrentState.Process(delta);
|
||||||
public override CharacterState InitialState { get; set; }
|
if (state is CharacterState)
|
||||||
|
|
||||||
[Export]
|
|
||||||
public Characters.Character Character { get; set; }
|
|
||||||
|
|
||||||
public void Process(double delta)
|
|
||||||
{
|
{
|
||||||
var state = CurrentState.Process(delta);
|
ChangeState(state);
|
||||||
if (state is CharacterState)
|
|
||||||
{
|
|
||||||
ChangeState(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void PhysicsProcess(double delta)
|
public void PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
var state = CurrentState.PhysicsProcess(delta);
|
||||||
|
if (state is CharacterState)
|
||||||
{
|
{
|
||||||
var state = CurrentState.PhysicsProcess(delta);
|
ChangeState(state);
|
||||||
if (state is CharacterState)
|
|
||||||
{
|
|
||||||
ChangeState(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Input(InputEvent @event)
|
public void Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
var state = CurrentState.Input(@event);
|
||||||
|
if (state is CharacterState)
|
||||||
{
|
{
|
||||||
var state = CurrentState.Input(@event);
|
ChangeState(state);
|
||||||
if (state is CharacterState)
|
|
||||||
{
|
|
||||||
ChangeState(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class NPCIdleState : NPCState
|
||||||
{
|
{
|
||||||
public partial class NPCIdleState : NPCState
|
[Export]
|
||||||
|
public CharacterState MoveState { get; set; }
|
||||||
|
|
||||||
|
public override CharacterState Process(double delta)
|
||||||
{
|
{
|
||||||
[Export]
|
base.Process(delta);
|
||||||
public CharacterState MoveState { get; set; }
|
if (Character.Direction.LengthSquared() > 0)
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
|
||||||
{
|
{
|
||||||
base.Process(delta);
|
return MoveState;
|
||||||
if (Character.Direction.LengthSquared() > 0)
|
|
||||||
{
|
|
||||||
return MoveState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||||
{
|
{
|
||||||
Character.Sprite.Play("idle");
|
Character.Sprite.Play("idle");
|
||||||
return base.Enter(previousState);
|
return base.Enter(previousState);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class NPCMoveState : NPCState
|
||||||
{
|
{
|
||||||
public partial class NPCMoveState : NPCState
|
[Export]
|
||||||
|
public CharacterState IdleState { get; set; }
|
||||||
|
|
||||||
|
public override CharacterState Process(double delta)
|
||||||
{
|
{
|
||||||
[Export]
|
base.Process(delta);
|
||||||
public CharacterState IdleState { get; set; }
|
if (Character.Direction.LengthSquared() == 0)
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
|
||||||
{
|
{
|
||||||
base.Process(delta);
|
return IdleState;
|
||||||
if (Character.Direction.LengthSquared() == 0)
|
|
||||||
{
|
|
||||||
return IdleState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override IState<CharacterState> Enter(IState<CharacterState> prev)
|
public override IState<CharacterState> Enter(IState<CharacterState> prev)
|
||||||
{
|
{
|
||||||
Character.Sprite.Play("move");
|
Character.Sprite.Play("move");
|
||||||
return base.Enter(prev);
|
return base.Enter(prev);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
{
|
|
||||||
public abstract partial class NPCState : CharacterState
|
|
||||||
{
|
|
||||||
protected Characters.NPC _npc => Character as Characters.NPC;
|
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
public abstract partial class NPCState : CharacterState
|
||||||
{
|
{
|
||||||
_npc.ThinkProcess(delta);
|
protected Characters.NPC _npc => Character as Characters.NPC;
|
||||||
return base.Process(delta);
|
|
||||||
}
|
public override CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
_npc.ThinkProcess(delta);
|
||||||
|
return base.Process(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class PlayerIdleState : PlayerState
|
||||||
{
|
{
|
||||||
public partial class PlayerIdleState : PlayerState
|
[Export]
|
||||||
|
public CharacterState MoveState { get; set; }
|
||||||
|
|
||||||
|
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||||
{
|
{
|
||||||
[Export]
|
GD.Print("Entered idle state");
|
||||||
public CharacterState MoveState { get; set; }
|
if (previousState is not PlayerMoveState)
|
||||||
|
|
||||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
|
||||||
{
|
{
|
||||||
GD.Print("Entered idle state");
|
if (Character.Direction.LengthSquared() > 0.01f)
|
||||||
if (previousState is not PlayerMoveState)
|
|
||||||
{
|
|
||||||
if (Character.Direction.LengthSquared() > 0.01f)
|
|
||||||
{
|
|
||||||
// other states like attacking or rolling can just delegate
|
|
||||||
// the work of checking if the player is moving to this idle
|
|
||||||
// state, so they do not have to manually check if the player
|
|
||||||
// wants to move to switch to the move state.
|
|
||||||
return MoveState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_player.Animation = "idle";
|
|
||||||
return base.Enter(previousState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
|
||||||
{
|
|
||||||
base.Process(delta);
|
|
||||||
if (Character.Direction.LengthSquared() > 0)
|
|
||||||
{
|
{
|
||||||
|
// other states like attacking or rolling can just delegate
|
||||||
|
// the work of checking if the player is moving to this idle
|
||||||
|
// state, so they do not have to manually check if the player
|
||||||
|
// wants to move to switch to the move state.
|
||||||
return MoveState;
|
return MoveState;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
_player.Animation = "idle";
|
||||||
|
return base.Enter(previousState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
base.Process(delta);
|
||||||
|
if (Character.Direction.LengthSquared() > 0)
|
||||||
|
{
|
||||||
|
return MoveState;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class PlayerMoveState : PlayerState
|
||||||
{
|
{
|
||||||
public partial class PlayerMoveState : PlayerState
|
[Export]
|
||||||
|
public PlayerRollState RollState { get; set; }
|
||||||
|
|
||||||
|
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||||
{
|
{
|
||||||
[Export]
|
Godot.GD.Print("Started moving");
|
||||||
public PlayerRollState RollState { get; set; }
|
_player.Animation = "move";
|
||||||
|
return base.Enter(previousState);
|
||||||
|
}
|
||||||
|
|
||||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
public override CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
base.Process(delta);
|
||||||
|
if (Character.Direction.LengthSquared() == 0)
|
||||||
{
|
{
|
||||||
Godot.GD.Print("Started moving");
|
return IdleState;
|
||||||
_player.Animation = "move";
|
|
||||||
return base.Enter(previousState);
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
public override CharacterState Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event.IsActionPressed("roll"))
|
||||||
{
|
{
|
||||||
base.Process(delta);
|
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
|
||||||
if (Character.Direction.LengthSquared() == 0)
|
|
||||||
{
|
{
|
||||||
return IdleState;
|
if (!weapon.IsUsing)
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CharacterState Input(InputEvent @event)
|
|
||||||
{
|
|
||||||
if (@event.IsActionPressed("roll"))
|
|
||||||
{
|
|
||||||
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
|
|
||||||
{
|
|
||||||
if (!weapon.IsUsing)
|
|
||||||
{
|
|
||||||
return RollState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
return RollState;
|
return RollState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return base.Input(@event);
|
else
|
||||||
|
{
|
||||||
|
return RollState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return base.Input(@event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,38 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public partial class PlayerRollState : PlayerState
|
||||||
{
|
{
|
||||||
public partial class PlayerRollState : PlayerState
|
private double _timeLeftToRoll = 0;
|
||||||
|
|
||||||
|
private Vector2 _rollDirection = Vector2.Zero;
|
||||||
|
|
||||||
|
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||||
{
|
{
|
||||||
private double _timeLeftToRoll = 0;
|
_timeLeftToRoll = 0.5;
|
||||||
|
// roll the direction we were previously moving in
|
||||||
|
_rollDirection = Character.Direction;
|
||||||
|
Character.Target = Character.Direction;
|
||||||
|
return base.Enter(previousState);
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2 _rollDirection = Vector2.Zero;
|
public override void Exit(IState<CharacterState> nextState)
|
||||||
|
{
|
||||||
|
// we want to reset our state variables in case we are forced out of
|
||||||
|
// this state (e.g. from death)
|
||||||
|
_timeLeftToRoll = 0;
|
||||||
|
_rollDirection = Character.Direction;
|
||||||
|
base.Exit(nextState);
|
||||||
|
}
|
||||||
|
|
||||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
public override CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
Character.Direction = _rollDirection;
|
||||||
|
if ((_timeLeftToRoll -= delta) <= 0)
|
||||||
{
|
{
|
||||||
_timeLeftToRoll = 0.5;
|
return IdleState;
|
||||||
// roll the direction we were previously moving in
|
|
||||||
_rollDirection = Character.Direction;
|
|
||||||
Character.Target = Character.Direction;
|
|
||||||
return base.Enter(previousState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Exit(IState<CharacterState> nextState)
|
|
||||||
{
|
|
||||||
// we want to reset our state variables in case we are forced out of
|
|
||||||
// this state (e.g. from death)
|
|
||||||
_timeLeftToRoll = 0;
|
|
||||||
_rollDirection = Character.Direction;
|
|
||||||
base.Exit(nextState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
|
||||||
{
|
|
||||||
Character.Direction = _rollDirection;
|
|
||||||
if ((_timeLeftToRoll -= delta) <= 0)
|
|
||||||
{
|
|
||||||
return IdleState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,71 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using SupaLidlGame.Characters;
|
using SupaLidlGame.Characters;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Character
|
namespace SupaLidlGame.State.Character;
|
||||||
|
|
||||||
|
public abstract partial class PlayerState : CharacterState
|
||||||
{
|
{
|
||||||
public abstract partial class PlayerState : CharacterState
|
protected Player _player => Character as Player;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public PlayerIdleState IdleState { get; set; }
|
||||||
|
|
||||||
|
public override CharacterState Input(InputEvent @event)
|
||||||
{
|
{
|
||||||
protected Player _player => Character as Player;
|
var inventory = Character.Inventory;
|
||||||
|
|
||||||
[Export]
|
if (this is PlayerIdleState or PlayerMoveState &&
|
||||||
public PlayerIdleState IdleState { get; set; }
|
!_player.Inventory.IsUsingItem)
|
||||||
|
|
||||||
public override CharacterState Input(InputEvent @event)
|
|
||||||
{
|
{
|
||||||
var inventory = Character.Inventory;
|
if (@event.IsActionPressed("equip_1"))
|
||||||
|
|
||||||
if (this is PlayerIdleState or PlayerMoveState &&
|
|
||||||
!_player.Inventory.IsUsingItem)
|
|
||||||
{
|
{
|
||||||
if (@event.IsActionPressed("equip_1"))
|
inventory.SelectedItem = inventory.GetItemByMap("equip_1");
|
||||||
{
|
|
||||||
inventory.SelectedItem = inventory.GetItemByMap("equip_1");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Input(@event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CharacterState Process(double delta)
|
return base.Input(@event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CharacterState Process(double delta)
|
||||||
|
{
|
||||||
|
Character.Direction = Godot.Input.GetVector("ui_left", "ui_right",
|
||||||
|
"ui_up", "ui_down");
|
||||||
|
Character.LookTowardsDirection();
|
||||||
|
|
||||||
|
Vector2 mousePos = Character.GetGlobalMousePosition();
|
||||||
|
Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos);
|
||||||
|
|
||||||
|
bool targetTowards(Items.Item item)
|
||||||
{
|
{
|
||||||
Character.Direction = Godot.Input.GetVector("ui_left", "ui_right",
|
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
|
||||||
"ui_up", "ui_down");
|
|
||||||
Character.LookTowardsDirection();
|
|
||||||
|
|
||||||
Vector2 mousePos = Character.GetGlobalMousePosition();
|
|
||||||
Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos);
|
|
||||||
|
|
||||||
bool targetTowards(Items.Item item)
|
|
||||||
{
|
{
|
||||||
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
|
if (!weapon.IsUsing)
|
||||||
{
|
{
|
||||||
if (!weapon.IsUsing)
|
var isPressed = Godot.Input.IsActionPressed("attack1");
|
||||||
|
var ret = false;
|
||||||
|
|
||||||
|
if (!weapon.ShouldHideIdle || isPressed)
|
||||||
{
|
{
|
||||||
var isPressed = Godot.Input.IsActionPressed("attack1");
|
Character.Target = dirToMouse;
|
||||||
var ret = false;
|
ret = true;
|
||||||
|
|
||||||
if (!weapon.ShouldHideIdle || isPressed)
|
|
||||||
{
|
|
||||||
Character.Target = dirToMouse;
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPressed)
|
|
||||||
{
|
|
||||||
Character.UseCurrentItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPressed)
|
||||||
|
{
|
||||||
|
Character.UseCurrentItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
var item = Character.Inventory.SelectedItem;
|
|
||||||
var offhand = Character.Inventory.OffhandItem;
|
|
||||||
|
|
||||||
var _ = targetTowards(item) || targetTowards(offhand);
|
|
||||||
|
|
||||||
return base.Process(delta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var item = Character.Inventory.SelectedItem;
|
||||||
|
var offhand = Character.Inventory.OffhandItem;
|
||||||
|
|
||||||
|
var _ = targetTowards(item) || targetTowards(offhand);
|
||||||
|
|
||||||
|
return base.Process(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
namespace SupaLidlGame.State
|
namespace SupaLidlGame.State;
|
||||||
|
|
||||||
|
public interface IState<T> where T : IState<T>
|
||||||
{
|
{
|
||||||
public interface IState<T> where T : IState<T>
|
/// <summary>
|
||||||
{
|
/// Called when this state is entered
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Called when this state is entered
|
/// <remarks>
|
||||||
/// </summary>
|
/// This returns a <c>IState</c> in case a state is being
|
||||||
/// <remarks>
|
/// transitioned to but wants to transition to another state. For
|
||||||
/// This returns a <c>IState</c> in case a state is being
|
/// example, an attack state can return to an idle state, but that idle
|
||||||
/// transitioned to but wants to transition to another state. For
|
/// state can override it to the move state immediately when necessary.
|
||||||
/// example, an attack state can return to an idle state, but that idle
|
/// </remarks>
|
||||||
/// state can override it to the move state immediately when necessary.
|
public IState<T> Enter(IState<T> previousState);
|
||||||
/// </remarks>
|
|
||||||
public IState<T> Enter(IState<T> previousState);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
|
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Exit(IState<T> nextState);
|
public void Exit(IState<T> nextState);
|
||||||
|
|
||||||
public IState<T> Process(double delta) => null;
|
public IState<T> Process(double delta) => null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State
|
namespace SupaLidlGame.State;
|
||||||
|
|
||||||
|
public abstract partial class StateMachine<T> : Node where T : IState<T>
|
||||||
{
|
{
|
||||||
public abstract partial class StateMachine<T> : Node where T : IState<T>
|
public T CurrentState { get; protected set; }
|
||||||
|
|
||||||
|
public abstract T InitialState { get; set; }
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
public T CurrentState { get; protected set; }
|
ChangeState(InitialState);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract T InitialState { get; set; }
|
public virtual bool ChangeState(T nextState, bool isProxied = false)
|
||||||
|
{
|
||||||
public override void _Ready()
|
if (nextState is null)
|
||||||
{
|
{
|
||||||
ChangeState(InitialState);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool ChangeState(T nextState, bool isProxied = false)
|
if (CurrentState is not null)
|
||||||
{
|
{
|
||||||
if (nextState is null)
|
CurrentState.Exit(nextState);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentState is not null)
|
|
||||||
{
|
|
||||||
CurrentState.Exit(nextState);
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentState = nextState;
|
|
||||||
|
|
||||||
// if the next state decides it should enter a different state,
|
|
||||||
// then we enter that different state instead
|
|
||||||
var nextNextState = nextState.Enter(CurrentState);
|
|
||||||
if (nextNextState is T t)
|
|
||||||
{
|
|
||||||
return ChangeState(t, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CurrentState = nextState;
|
||||||
|
|
||||||
|
// if the next state decides it should enter a different state,
|
||||||
|
// then we enter that different state instead
|
||||||
|
var nextNextState = nextState.Enter(CurrentState);
|
||||||
|
if (nextNextState is T t)
|
||||||
|
{
|
||||||
|
return ChangeState(t, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class RangedFireState : WeaponState
|
||||||
{
|
{
|
||||||
public partial class RangedFireState : WeaponState
|
[Export]
|
||||||
|
public Items.Weapons.Ranged Weapon { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public RangedIdleState IdleState { get; set; }
|
||||||
|
|
||||||
|
private double _timeLeft = 0;
|
||||||
|
|
||||||
|
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
||||||
{
|
{
|
||||||
[Export]
|
//_timeLeft
|
||||||
public Items.Weapons.Ranged Weapon { get; set; }
|
_timeLeft = Weapon.UseTime;
|
||||||
|
Weapon.Attack();
|
||||||
|
Weapon.UseDirection = Weapon.Character.Target;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public override WeaponState Process(double delta)
|
||||||
public RangedIdleState IdleState { get; set; }
|
{
|
||||||
|
if ((_timeLeft -= delta) <= 0)
|
||||||
private double _timeLeft = 0;
|
|
||||||
|
|
||||||
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
|
||||||
{
|
{
|
||||||
//_timeLeft
|
return IdleState;
|
||||||
_timeLeft = Weapon.UseTime;
|
|
||||||
Weapon.Attack();
|
|
||||||
Weapon.UseDirection = Weapon.Character.Target;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override WeaponState Process(double delta)
|
public override void Exit(IState<WeaponState> nextState)
|
||||||
{
|
{
|
||||||
if ((_timeLeft -= delta) <= 0)
|
|
||||||
{
|
|
||||||
return IdleState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Exit(IState<WeaponState> nextState)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class RangedIdleState : WeaponState
|
||||||
{
|
{
|
||||||
public partial class RangedIdleState : WeaponState
|
[Export]
|
||||||
|
public RangedFireState FireState { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Items.Weapons.Ranged Weapon { get; set; }
|
||||||
|
|
||||||
|
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
||||||
{
|
{
|
||||||
[Export]
|
Weapon.Visible = !Weapon.ShouldHideIdle;
|
||||||
public RangedFireState FireState { get; set; }
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public override WeaponState Use()
|
||||||
public Items.Weapons.Ranged Weapon { get; set; }
|
{
|
||||||
|
return FireState;
|
||||||
|
}
|
||||||
|
|
||||||
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
public override void Exit(IState<WeaponState> nextState)
|
||||||
{
|
{
|
||||||
Weapon.Visible = !Weapon.ShouldHideIdle;
|
Weapon.Visible = true;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override WeaponState Use()
|
|
||||||
{
|
|
||||||
return FireState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Exit(IState<WeaponState> nextState)
|
|
||||||
{
|
|
||||||
Weapon.Visible = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class SwordAnticipateState : WeaponState
|
||||||
{
|
{
|
||||||
public partial class SwordAnticipateState : WeaponState
|
[Export]
|
||||||
|
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public SwordAttackState AttackState { get; set; }
|
||||||
|
|
||||||
|
private double _anticipateTime;
|
||||||
|
|
||||||
|
public override WeaponState Enter(IState<WeaponState> prevState)
|
||||||
{
|
{
|
||||||
[Export]
|
Sword.EnableParry();
|
||||||
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public SwordAttackState AttackState { get; set; }
|
|
||||||
|
|
||||||
private double _anticipateTime;
|
if (Sword.Character is SupaLidlGame.Characters.Player)
|
||||||
|
|
||||||
public override WeaponState Enter(IState<WeaponState> prevState)
|
|
||||||
{
|
{
|
||||||
Sword.EnableParry();
|
return AttackState;
|
||||||
|
|
||||||
if (Sword.Character is SupaLidlGame.Characters.Player)
|
|
||||||
{
|
|
||||||
return AttackState;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Sword.Anchor.Rotation > Mathf.DegToRad(50))
|
|
||||||
{
|
|
||||||
Sword.AnimationPlayer.Play("anticipate_alternate");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Sword.AnimationPlayer.Play("anticipate");
|
|
||||||
}
|
|
||||||
_anticipateTime = Sword.NPCAnticipateTime;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override WeaponState Process(double delta)
|
if (Sword.Anchor.Rotation > Mathf.DegToRad(50))
|
||||||
{
|
{
|
||||||
// go into attack state if anticipation time is delta
|
Sword.AnimationPlayer.Play("anticipate_alternate");
|
||||||
if ((_anticipateTime -= delta) <= 0)
|
|
||||||
{
|
|
||||||
return AttackState;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Sword.AnimationPlayer.Play("anticipate");
|
||||||
|
}
|
||||||
|
_anticipateTime = Sword.NPCAnticipateTime;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override WeaponState Process(double delta)
|
||||||
|
{
|
||||||
|
// go into attack state if anticipation time is delta
|
||||||
|
if ((_anticipateTime -= delta) <= 0)
|
||||||
|
{
|
||||||
|
return AttackState;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,88 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class SwordAttackState : WeaponState
|
||||||
{
|
{
|
||||||
public partial class SwordAttackState : WeaponState
|
[Export]
|
||||||
|
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public SwordAnticipateState AnticipateState { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public SwordIdleState IdleState { get; set; }
|
||||||
|
|
||||||
|
private double _attackDuration = 0;
|
||||||
|
|
||||||
|
private double _useDuration = 0;
|
||||||
|
|
||||||
|
private double _attackAnimDuration = 0;
|
||||||
|
|
||||||
|
private bool _isAlternate = false;
|
||||||
|
|
||||||
|
public override WeaponState Enter(IState<WeaponState> prevState)
|
||||||
{
|
{
|
||||||
[Export]
|
//Sword.AnimationPlayer.Stop();
|
||||||
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
Sword.Attack();
|
||||||
|
|
||||||
[Export]
|
if (_isAlternate)
|
||||||
public SwordAnticipateState AnticipateState { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public SwordIdleState IdleState { get; set; }
|
|
||||||
|
|
||||||
private double _attackDuration = 0;
|
|
||||||
|
|
||||||
private double _useDuration = 0;
|
|
||||||
|
|
||||||
private double _attackAnimDuration = 0;
|
|
||||||
|
|
||||||
private bool _isAlternate = false;
|
|
||||||
|
|
||||||
public override WeaponState Enter(IState<WeaponState> prevState)
|
|
||||||
{
|
{
|
||||||
//Sword.AnimationPlayer.Stop();
|
Sword.AnimationPlayer.Play("attack_alternate");
|
||||||
Sword.Attack();
|
}
|
||||||
|
else
|
||||||
if (_isAlternate)
|
{
|
||||||
{
|
Sword.AnimationPlayer.Play("attack");
|
||||||
Sword.AnimationPlayer.Play("attack_alternate");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Sword.AnimationPlayer.Play("attack");
|
|
||||||
}
|
|
||||||
|
|
||||||
_attackDuration = Sword.AttackTime;
|
|
||||||
_useDuration = Sword.UseTime;
|
|
||||||
_attackAnimDuration = Sword.AttackAnimationDuration;
|
|
||||||
|
|
||||||
Sword.Visible = true;
|
|
||||||
Sword.UseDirection = Sword.Character.Target;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Exit(IState<WeaponState> nextState)
|
_attackDuration = Sword.AttackTime;
|
||||||
|
_useDuration = Sword.UseTime;
|
||||||
|
_attackAnimDuration = Sword.AttackAnimationDuration;
|
||||||
|
|
||||||
|
Sword.Visible = true;
|
||||||
|
Sword.UseDirection = Sword.Character.Target;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Exit(IState<WeaponState> nextState)
|
||||||
|
{
|
||||||
|
Sword.Deattack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override WeaponState Use()
|
||||||
|
{
|
||||||
|
if (_useDuration <= 0)
|
||||||
{
|
{
|
||||||
Sword.Deattack();
|
// if we are still playing the current attack animation, we should alternate
|
||||||
|
if (_attackAnimDuration > 0)
|
||||||
|
{
|
||||||
|
_isAlternate = !_isAlternate;
|
||||||
|
}
|
||||||
|
return IdleState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override WeaponState Use()
|
return null;
|
||||||
{
|
}
|
||||||
if (_useDuration <= 0)
|
|
||||||
{
|
|
||||||
// if we are still playing the current attack animation, we should alternate
|
|
||||||
if (_attackAnimDuration > 0)
|
|
||||||
{
|
|
||||||
_isAlternate = !_isAlternate;
|
|
||||||
}
|
|
||||||
return IdleState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
public override WeaponState Process(double delta)
|
||||||
|
{
|
||||||
|
if (_attackDuration > 0)
|
||||||
|
{
|
||||||
|
if ((_attackDuration -= delta) <= 0)
|
||||||
|
{
|
||||||
|
Sword.Deattack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override WeaponState Process(double delta)
|
if ((_attackAnimDuration -= delta) <= 0)
|
||||||
{
|
{
|
||||||
if (_attackDuration > 0)
|
Sword.AnimationPlayer.Play("RESET");
|
||||||
{
|
_isAlternate = false;
|
||||||
if ((_attackDuration -= delta) <= 0)
|
return IdleState;
|
||||||
{
|
|
||||||
Sword.Deattack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((_attackAnimDuration -= delta) <= 0)
|
|
||||||
{
|
|
||||||
Sword.AnimationPlayer.Play("RESET");
|
|
||||||
_isAlternate = false;
|
|
||||||
return IdleState;
|
|
||||||
}
|
|
||||||
|
|
||||||
_useDuration -= delta;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_useDuration -= delta;
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,51 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class SwordIdleState : WeaponState
|
||||||
{
|
{
|
||||||
public partial class SwordIdleState : WeaponState
|
[Export]
|
||||||
|
public SwordAnticipateState AnticipateState { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
||||||
|
|
||||||
|
private double _attackCooldown;
|
||||||
|
|
||||||
|
public override WeaponState Enter(IState<WeaponState> prevState)
|
||||||
{
|
{
|
||||||
[Export]
|
if (prevState is SwordAttackState)
|
||||||
public SwordAnticipateState AnticipateState { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
|
||||||
|
|
||||||
private double _attackCooldown;
|
|
||||||
|
|
||||||
public override WeaponState Enter(IState<WeaponState> prevState)
|
|
||||||
{
|
{
|
||||||
if (prevState is SwordAttackState)
|
_attackCooldown = Sword.UseTime - Sword.AttackTime;
|
||||||
{
|
|
||||||
_attackCooldown = Sword.UseTime - Sword.AttackTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
Sword.Visible = !Sword.ShouldHideIdle;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Exit(IState<WeaponState> nextState)
|
Sword.Visible = !Sword.ShouldHideIdle;
|
||||||
{
|
|
||||||
Sword.Visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override WeaponState Use()
|
return null;
|
||||||
{
|
}
|
||||||
if (_attackCooldown <= 0)
|
|
||||||
{
|
|
||||||
return AnticipateState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public override void Exit(IState<WeaponState> nextState)
|
||||||
|
{
|
||||||
|
Sword.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override WeaponState Use()
|
||||||
|
{
|
||||||
|
if (_attackCooldown <= 0)
|
||||||
|
{
|
||||||
return AnticipateState;
|
return AnticipateState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override WeaponState Process(double delta)
|
return AnticipateState;
|
||||||
{
|
}
|
||||||
if (_attackCooldown > 0)
|
|
||||||
{
|
|
||||||
_attackCooldown -= delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
public override WeaponState Process(double delta)
|
||||||
|
{
|
||||||
|
if (_attackCooldown > 0)
|
||||||
|
{
|
||||||
|
_attackCooldown -= delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public abstract partial class WeaponState : Node, IState<WeaponState>
|
||||||
{
|
{
|
||||||
public abstract partial class WeaponState : Node, IState<WeaponState>
|
public virtual WeaponState Use() => null;
|
||||||
|
|
||||||
|
public virtual WeaponState Deuse() => null;
|
||||||
|
|
||||||
|
public abstract IState<WeaponState> Enter(IState<WeaponState> previousState);
|
||||||
|
|
||||||
|
public virtual void Exit(IState<WeaponState> nextState)
|
||||||
{
|
{
|
||||||
public virtual WeaponState Use() => null;
|
|
||||||
|
|
||||||
public virtual WeaponState Deuse() => null;
|
|
||||||
|
|
||||||
public abstract IState<WeaponState> Enter(IState<WeaponState> previousState);
|
|
||||||
|
|
||||||
public virtual void Exit(IState<WeaponState> nextState)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual IState<WeaponState> Process(double delta) => null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual IState<WeaponState> Process(double delta) => null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,36 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.State.Weapon
|
namespace SupaLidlGame.State.Weapon;
|
||||||
|
|
||||||
|
public partial class WeaponStateMachine : StateMachine<WeaponState>
|
||||||
{
|
{
|
||||||
public partial class WeaponStateMachine : StateMachine<WeaponState>
|
[Export]
|
||||||
|
public override WeaponState InitialState { get; set; }
|
||||||
|
|
||||||
|
public void Use()
|
||||||
{
|
{
|
||||||
[Export]
|
var state = CurrentState.Use();
|
||||||
public override WeaponState InitialState { get; set; }
|
if (state is WeaponState)
|
||||||
|
|
||||||
public void Use()
|
|
||||||
{
|
{
|
||||||
var state = CurrentState.Use();
|
ChangeState(state);
|
||||||
if (state is WeaponState)
|
|
||||||
{
|
|
||||||
ChangeState(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Deuse()
|
public void Deuse()
|
||||||
|
{
|
||||||
|
var state = CurrentState.Deuse();
|
||||||
|
if (state is WeaponState)
|
||||||
{
|
{
|
||||||
var state = CurrentState.Deuse();
|
ChangeState(state);
|
||||||
if (state is WeaponState)
|
|
||||||
{
|
|
||||||
ChangeState(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Process(double delta)
|
public void Process(double delta)
|
||||||
|
{
|
||||||
|
var state = CurrentState.Process(delta);
|
||||||
|
if (state is WeaponState s)
|
||||||
{
|
{
|
||||||
var state = CurrentState.Process(delta);
|
ChangeState(s);
|
||||||
if (state is WeaponState s)
|
|
||||||
{
|
|
||||||
ChangeState(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,57 +2,56 @@ using Godot;
|
||||||
using SupaLidlGame.Extensions;
|
using SupaLidlGame.Extensions;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Prototyping
|
namespace SupaLidlGame.Prototyping;
|
||||||
|
|
||||||
|
public partial class ContextBasedSteering : Node2D
|
||||||
{
|
{
|
||||||
public partial class ContextBasedSteering : Node2D
|
public float PreferredDistance { get; set; } = 256.0f;
|
||||||
|
Vector2 _direction;
|
||||||
|
|
||||||
|
// Called when the node enters the scene tree for the first time.
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
public float PreferredDistance { get; set; } = 256.0f;
|
GD.Print("Started ContextBasedSteering test");
|
||||||
Vector2 _direction;
|
}
|
||||||
|
|
||||||
// Called when the node enters the scene tree for the first time.
|
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
public override void _Ready()
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
GD.Print("Started ContextBasedSteering test");
|
_direction = GetDirection();
|
||||||
}
|
QueueRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
public Vector2 GetDirection()
|
||||||
public override void _Process(double delta)
|
{
|
||||||
{
|
float directWeight;
|
||||||
_direction = GetDirection();
|
float strafeWeight;
|
||||||
QueueRedraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector2 GetDirection()
|
Vector2 towards = GetGlobalMousePosition() - GlobalPosition;
|
||||||
{
|
float dist = towards.Length();
|
||||||
float directWeight;
|
|
||||||
float strafeWeight;
|
|
||||||
|
|
||||||
Vector2 towards = GetGlobalMousePosition() - GlobalPosition;
|
Vector2 directDir = towards.Normalized();
|
||||||
float dist = towards.Length();
|
Vector2 strafeDir = directDir.Clockwise90();
|
||||||
|
|
||||||
Vector2 directDir = towards.Normalized();
|
// weights approach 1
|
||||||
Vector2 strafeDir = directDir.Clockwise90();
|
// dy/dx = 1 - y
|
||||||
|
// y = 1 - e^(-x)
|
||||||
|
|
||||||
// weights approach 1
|
directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance));
|
||||||
// dy/dx = 1 - y
|
strafeWeight = 1 - directWeight;
|
||||||
// y = 1 - e^(-x)
|
|
||||||
|
|
||||||
directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance));
|
/*
|
||||||
strafeWeight = 1 - directWeight;
|
Vector2 midpoint = (strafeDir * strafeWeight)
|
||||||
|
.Midpoint(directDir * directWeight);
|
||||||
|
*/
|
||||||
|
Vector2 midpoint = (directDir * directWeight)
|
||||||
|
.Midpoint(strafeDir * strafeWeight);
|
||||||
|
|
||||||
/*
|
return midpoint.Normalized();
|
||||||
Vector2 midpoint = (strafeDir * strafeWeight)
|
}
|
||||||
.Midpoint(directDir * directWeight);
|
|
||||||
*/
|
|
||||||
Vector2 midpoint = (directDir * directWeight)
|
|
||||||
.Midpoint(strafeDir * strafeWeight);
|
|
||||||
|
|
||||||
return midpoint.Normalized();
|
public override void _Draw()
|
||||||
}
|
{
|
||||||
|
DrawLine(Vector2.Zero, _direction * 256, Colors.Green);
|
||||||
public override void _Draw()
|
|
||||||
{
|
|
||||||
DrawLine(Vector2.Zero, _direction * 256, Colors.Green);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,50 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.UI
|
namespace SupaLidlGame.UI;
|
||||||
|
|
||||||
|
public partial class FloatingText : Node2D
|
||||||
{
|
{
|
||||||
public partial class FloatingText : Node2D
|
private Label _label;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
private Label _label;
|
_label = GetNode<Label>("Label");
|
||||||
|
_label.Text = Text;
|
||||||
|
|
||||||
[Export]
|
Tween tween = GetTree().CreateTween()
|
||||||
public string Text { get; set; }
|
.SetEase(Tween.EaseType.Out)
|
||||||
|
.SetTrans(Tween.TransitionType.Quint)
|
||||||
|
.SetParallel();
|
||||||
|
|
||||||
public override void _Ready()
|
Random rng = new Random();
|
||||||
|
|
||||||
|
float randomFloat(float min, float max)
|
||||||
{
|
{
|
||||||
_label = GetNode<Label>("Label");
|
return (float)rng.NextDouble() * (max - min) + min;
|
||||||
_label.Text = Text;
|
|
||||||
|
|
||||||
Tween tween = GetTree().CreateTween()
|
|
||||||
.SetEase(Tween.EaseType.Out)
|
|
||||||
.SetTrans(Tween.TransitionType.Quint)
|
|
||||||
.SetParallel();
|
|
||||||
|
|
||||||
Random rng = new Random();
|
|
||||||
|
|
||||||
float randomFloat(float min, float max)
|
|
||||||
{
|
|
||||||
return (float)rng.NextDouble() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
GD.Print(GlobalPosition);
|
|
||||||
Position += new Vector2(randomFloat(-8, 8), 0);
|
|
||||||
var endPos = Position + new Vector2(0, randomFloat(-16, -8));
|
|
||||||
var endMod = new Color(1, 1, 1, 0);
|
|
||||||
var endScale = Scale * 0.5f;
|
|
||||||
|
|
||||||
tween.TweenProperty(this, "position", endPos, 0.5f);
|
|
||||||
tween.SetTrans(Tween.TransitionType.Linear);
|
|
||||||
tween.TweenProperty(this, "modulate", endMod, 0.5f).SetDelay(1.0f);
|
|
||||||
tween.TweenProperty(this, "scale", endScale, 0.5f).SetDelay(1.0f);
|
|
||||||
tween.TweenCallback(new Callable(this, nameof(OnTweenFinished)))
|
|
||||||
.SetDelay(2.5f);
|
|
||||||
|
|
||||||
base._Ready();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweenFinished()
|
GD.Print(GlobalPosition);
|
||||||
{
|
Position += new Vector2(randomFloat(-8, 8), 0);
|
||||||
QueueFree();
|
var endPos = Position + new Vector2(0, randomFloat(-16, -8));
|
||||||
}
|
var endMod = new Color(1, 1, 1, 0);
|
||||||
|
var endScale = Scale * 0.5f;
|
||||||
|
|
||||||
|
tween.TweenProperty(this, "position", endPos, 0.5f);
|
||||||
|
tween.SetTrans(Tween.TransitionType.Linear);
|
||||||
|
tween.TweenProperty(this, "modulate", endMod, 0.5f).SetDelay(1.0f);
|
||||||
|
tween.TweenProperty(this, "scale", endScale, 0.5f).SetDelay(1.0f);
|
||||||
|
tween.TweenCallback(new Callable(this, nameof(OnTweenFinished)))
|
||||||
|
.SetDelay(2.5f);
|
||||||
|
|
||||||
|
base._Ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweenFinished()
|
||||||
|
{
|
||||||
|
QueueFree();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace SupaLidlGame.Utils
|
namespace SupaLidlGame.Utils;
|
||||||
|
|
||||||
|
public interface IFaction
|
||||||
{
|
{
|
||||||
public interface IFaction
|
/// <summary>
|
||||||
{
|
/// The faction index that this entity belongs to.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// The faction index that this entity belongs to.
|
[Export]
|
||||||
/// </summary>
|
public ushort Faction { get; set; }
|
||||||
[Export]
|
|
||||||
public ushort Faction { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,49 @@
|
||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SupaLidlGame.Utils
|
namespace SupaLidlGame.Utils;
|
||||||
|
|
||||||
|
public partial class PlayerCamera : Camera2D
|
||||||
{
|
{
|
||||||
public partial class PlayerCamera : Camera2D
|
protected float _intensity;
|
||||||
|
|
||||||
|
protected double _timeLeft;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
protected float _intensity;
|
|
||||||
|
|
||||||
protected double _timeLeft;
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (_timeLeft > 0)
|
||||||
{
|
{
|
||||||
|
_timeLeft -= delta;
|
||||||
|
Offset = RandomOffset(_intensity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Offset = Vector2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
if (_intensity > 0)
|
||||||
{
|
{
|
||||||
if (_timeLeft > 0)
|
_intensity = Mathf.MoveToward(_intensity, 0.0f, (float)delta);
|
||||||
{
|
|
||||||
_timeLeft -= delta;
|
|
||||||
Offset = RandomOffset(_intensity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Offset = Vector2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_intensity > 0)
|
|
||||||
{
|
|
||||||
_intensity = Mathf.MoveToward(_intensity, 0.0f, (float)delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shake(float intensity, double time)
|
|
||||||
{
|
|
||||||
_intensity = Mathf.Max(_intensity, intensity);
|
|
||||||
_timeLeft = Math.Max(_timeLeft, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector2 RandomOffset(float intensity)
|
|
||||||
{
|
|
||||||
Vector2 ret = Vector2.Zero;
|
|
||||||
var rng = new RandomNumberGenerator();
|
|
||||||
ret.X = (rng.Randf() - 0.5f) * intensity;
|
|
||||||
ret.Y = (rng.Randf() - 0.5f) * intensity;
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Shake(float intensity, double time)
|
||||||
|
{
|
||||||
|
_intensity = Mathf.Max(_intensity, intensity);
|
||||||
|
_timeLeft = Math.Max(_timeLeft, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 RandomOffset(float intensity)
|
||||||
|
{
|
||||||
|
Vector2 ret = Vector2.Zero;
|
||||||
|
var rng = new RandomNumberGenerator();
|
||||||
|
ret.X = (rng.Randf() - 0.5f) * intensity;
|
||||||
|
ret.Y = (rng.Randf() - 0.5f) * intensity;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,58 +2,57 @@ using Godot;
|
||||||
using Godot.Collections;
|
using Godot.Collections;
|
||||||
using SupaLidlGame.Extensions;
|
using SupaLidlGame.Extensions;
|
||||||
|
|
||||||
namespace SupaLidlGame.Utils
|
namespace SupaLidlGame.Utils;
|
||||||
|
|
||||||
|
public partial class Spawner : Node2D
|
||||||
{
|
{
|
||||||
public partial class Spawner : Node2D
|
[Export]
|
||||||
|
public Area2D SpawnArea { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public PackedScene Character { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public double SpawnTime { get; set; }
|
||||||
|
|
||||||
|
protected double _timeLeftToSpawn = 0;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
[Export]
|
}
|
||||||
public Area2D SpawnArea { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
public override void _Process(double delta)
|
||||||
public PackedScene Character { get; set; }
|
{
|
||||||
|
if ((_timeLeftToSpawn -= delta) <= 0)
|
||||||
[Export]
|
|
||||||
public double SpawnTime { get; set; }
|
|
||||||
|
|
||||||
protected double _timeLeftToSpawn = 0;
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
{
|
||||||
|
_timeLeftToSpawn = SpawnTime;
|
||||||
|
Spawn();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public void Spawn()
|
||||||
|
{
|
||||||
|
var coll = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
|
||||||
|
var rect = coll.Shape as RectangleShape2D;
|
||||||
|
if (rect is not null)
|
||||||
{
|
{
|
||||||
if ((_timeLeftToSpawn -= delta) <= 0)
|
Vector2 size = rect.Size / 2;
|
||||||
{
|
Vector2 pos = GlobalPosition;
|
||||||
_timeLeftToSpawn = SpawnTime;
|
|
||||||
Spawn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Spawn()
|
double x1, x2, y1, y2;
|
||||||
{
|
x1 = pos.X - size.X;
|
||||||
var coll = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
|
x2 = pos.X + size.X;
|
||||||
var rect = coll.Shape as RectangleShape2D;
|
y1 = pos.Y - size.Y;
|
||||||
if (rect is not null)
|
y2 = pos.Y + size.Y;
|
||||||
{
|
|
||||||
Vector2 size = rect.Size / 2;
|
|
||||||
Vector2 pos = GlobalPosition;
|
|
||||||
|
|
||||||
double x1, x2, y1, y2;
|
float randX = (float)GD.RandRange(x1, x2);
|
||||||
x1 = pos.X - size.X;
|
float randY = (float)GD.RandRange(y1, y2);
|
||||||
x2 = pos.X + size.X;
|
|
||||||
y1 = pos.Y - size.Y;
|
|
||||||
y2 = pos.Y + size.Y;
|
|
||||||
|
|
||||||
float randX = (float)GD.RandRange(x1, x2);
|
Vector2 randPos = new Vector2(randX, randY);
|
||||||
float randY = (float)GD.RandRange(y1, y2);
|
|
||||||
|
|
||||||
Vector2 randPos = new Vector2(randX, randY);
|
var chr = Character.Instantiate<Characters.Character>();
|
||||||
|
chr.GlobalPosition = randPos;
|
||||||
var chr = Character.Instantiate<Characters.Character>();
|
this.GetAncestor<Scenes.Map>().Entities.AddChild(chr);
|
||||||
chr.GlobalPosition = randPos;
|
|
||||||
this.GetAncestor<Scenes.Map>().Entities.AddChild(chr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
241
Utils/World.cs
241
Utils/World.cs
|
@ -5,146 +5,145 @@ using SupaLidlGame.Extensions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SupaLidlGame.Utils
|
namespace SupaLidlGame.Utils;
|
||||||
|
|
||||||
|
public partial class World : Node2D
|
||||||
{
|
{
|
||||||
public partial class World : Node2D
|
[Export]
|
||||||
|
public PackedScene StartingArea { get; set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Map CurrentMap { get; protected set; }
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Player CurrentPlayer { get; set; }
|
||||||
|
|
||||||
|
private Dictionary<string, Map> _maps;
|
||||||
|
|
||||||
|
private string _currentConnector;
|
||||||
|
|
||||||
|
private string _currentMapResourcePath;
|
||||||
|
|
||||||
|
private const string PLAYER_PATH = "res://Characters/Player.tscn";
|
||||||
|
private PackedScene _playerScene;
|
||||||
|
|
||||||
|
public World()
|
||||||
{
|
{
|
||||||
[Export]
|
_maps = new Dictionary<string, Map>();
|
||||||
public PackedScene StartingArea { get; set; }
|
_playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
[Export]
|
public override void _Ready()
|
||||||
public Map CurrentMap { get; protected set; }
|
{
|
||||||
|
if (StartingArea is not null)
|
||||||
[Export]
|
|
||||||
public Player CurrentPlayer { get; set; }
|
|
||||||
|
|
||||||
private Dictionary<string, Map> _maps;
|
|
||||||
|
|
||||||
private string _currentConnector;
|
|
||||||
|
|
||||||
private string _currentMapResourcePath;
|
|
||||||
|
|
||||||
private const string PLAYER_PATH = "res://Characters/Player.tscn";
|
|
||||||
private PackedScene _playerScene;
|
|
||||||
|
|
||||||
public World()
|
|
||||||
{
|
{
|
||||||
_maps = new Dictionary<string, Map>();
|
LoadScene(StartingArea);
|
||||||
_playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
// spawn the player in
|
||||||
|
CreatePlayer();
|
||||||
|
|
||||||
|
base._Ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadScene(PackedScene scene)
|
||||||
|
{
|
||||||
|
GD.Print("Loading map " + scene.ResourcePath);
|
||||||
|
|
||||||
|
Map map;
|
||||||
|
if (_maps.ContainsKey(scene.ResourcePath))
|
||||||
{
|
{
|
||||||
if (StartingArea is not null)
|
map = _maps[scene.ResourcePath];
|
||||||
{
|
}
|
||||||
LoadScene(StartingArea);
|
else
|
||||||
}
|
{
|
||||||
|
map = scene.Instantiate<Map>();
|
||||||
// spawn the player in
|
_maps.Add(scene.ResourcePath, map);
|
||||||
CreatePlayer();
|
|
||||||
|
|
||||||
base._Ready();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadScene(PackedScene scene)
|
if (CurrentMap is not null)
|
||||||
{
|
{
|
||||||
GD.Print("Loading map " + scene.ResourcePath);
|
CurrentMap.Entities.RemoveChild(CurrentPlayer);
|
||||||
|
RemoveChild(CurrentMap);
|
||||||
Map map;
|
CurrentMap.Active = false;
|
||||||
if (_maps.ContainsKey(scene.ResourcePath))
|
|
||||||
{
|
|
||||||
map = _maps[scene.ResourcePath];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
map = scene.Instantiate<Map>();
|
|
||||||
_maps.Add(scene.ResourcePath, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentMap is not null)
|
|
||||||
{
|
|
||||||
CurrentMap.Entities.RemoveChild(CurrentPlayer);
|
|
||||||
RemoveChild(CurrentMap);
|
|
||||||
CurrentMap.Active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddChild(map);
|
|
||||||
InitTilemap(map);
|
|
||||||
|
|
||||||
CurrentMap = map;
|
|
||||||
CurrentMap.Active = true;
|
|
||||||
|
|
||||||
if (CurrentPlayer is not null)
|
|
||||||
{
|
|
||||||
CurrentMap.Entities.AddChild(CurrentPlayer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreatePlayer()
|
AddChild(map);
|
||||||
|
InitTilemap(map);
|
||||||
|
|
||||||
|
CurrentMap = map;
|
||||||
|
CurrentMap.Active = true;
|
||||||
|
|
||||||
|
if (CurrentPlayer is not null)
|
||||||
{
|
{
|
||||||
CurrentPlayer = _playerScene.Instantiate<Player>();
|
|
||||||
CurrentMap.Entities.AddChild(CurrentPlayer);
|
CurrentMap.Entities.AddChild(CurrentPlayer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void InitTilemap(Map map)
|
public void CreatePlayer()
|
||||||
|
{
|
||||||
|
CurrentPlayer = _playerScene.Instantiate<Player>();
|
||||||
|
CurrentMap.Entities.AddChild(CurrentPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitTilemap(Map map)
|
||||||
|
{
|
||||||
|
var children = map.Areas.GetChildren();
|
||||||
|
foreach (Node node in children)
|
||||||
{
|
{
|
||||||
var children = map.Areas.GetChildren();
|
if (node is BoundingBoxes.ConnectorBox connector)
|
||||||
foreach (Node node in children)
|
|
||||||
{
|
{
|
||||||
if (node is BoundingBoxes.ConnectorBox connector)
|
// this reconnects the EventHandler if it is connected
|
||||||
{
|
connector.RequestedEnter -= _on_area_2d_requested_enter;
|
||||||
// this reconnects the EventHandler if it is connected
|
connector.RequestedEnter += _on_area_2d_requested_enter;
|
||||||
connector.RequestedEnter -= _on_area_2d_requested_enter;
|
|
||||||
connector.RequestedEnter += _on_area_2d_requested_enter;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void MovePlayerToConnector(string name)
|
|
||||||
{
|
private void MovePlayerToConnector(string name)
|
||||||
// find the first connector with the specified name
|
{
|
||||||
var connector = CurrentMap.Areas.GetChildren().First((child) =>
|
// find the first connector with the specified name
|
||||||
{
|
var connector = CurrentMap.Areas.GetChildren().First((child) =>
|
||||||
if (child is BoundingBoxes.ConnectorBox connector)
|
{
|
||||||
{
|
if (child is BoundingBoxes.ConnectorBox connector)
|
||||||
return connector.Identifier == name;
|
{
|
||||||
}
|
return connector.Identifier == name;
|
||||||
return false;
|
}
|
||||||
}) as BoundingBoxes.ConnectorBox;
|
return false;
|
||||||
|
}) as BoundingBoxes.ConnectorBox;
|
||||||
CurrentPlayer.GlobalPosition = connector.GlobalPosition;
|
|
||||||
}
|
CurrentPlayer.GlobalPosition = connector.GlobalPosition;
|
||||||
|
}
|
||||||
public void MoveToArea(string path, string connector)
|
|
||||||
{
|
public void MoveToArea(string path, string connector)
|
||||||
_currentConnector = connector;
|
{
|
||||||
if (path != _currentMapResourcePath)
|
_currentConnector = connector;
|
||||||
{
|
if (path != _currentMapResourcePath)
|
||||||
var scene = ResourceLoader.Load<PackedScene>(path);
|
{
|
||||||
LoadScene(scene);
|
var scene = ResourceLoader.Load<PackedScene>(path);
|
||||||
_currentMapResourcePath = path;
|
LoadScene(scene);
|
||||||
}
|
_currentMapResourcePath = path;
|
||||||
|
}
|
||||||
// after finished loading, move our player to the connector
|
|
||||||
MovePlayerToConnector(connector);
|
// after finished loading, move our player to the connector
|
||||||
}
|
MovePlayerToConnector(connector);
|
||||||
|
}
|
||||||
public void _on_area_2d_requested_enter(
|
|
||||||
BoundingBoxes.ConnectorBox box,
|
public void _on_area_2d_requested_enter(
|
||||||
Player player)
|
BoundingBoxes.ConnectorBox box,
|
||||||
{
|
Player player)
|
||||||
GD.Print("Requesting to enter " + box.ToConnector);
|
{
|
||||||
MoveToArea(box.ToArea, box.ToConnector);
|
GD.Print("Requesting to enter " + box.ToConnector);
|
||||||
}
|
MoveToArea(box.ToArea, box.ToConnector);
|
||||||
|
}
|
||||||
public void SaveGame()
|
|
||||||
{
|
public void SaveGame()
|
||||||
throw new System.NotImplementedException();
|
{
|
||||||
}
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
public void LoadGame()
|
|
||||||
{
|
public void LoadGame()
|
||||||
throw new System.NotImplementedException();
|
{
|
||||||
}
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue