refactor file scoped namespace
parent
96fd6f5c26
commit
706354e813
|
@ -1,11 +1,10 @@
|
|||
using Godot;
|
||||
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 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]
|
||||
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)
|
||||
{
|
||||
if (Collision is null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
throw new NullReferenceException("Collision not specified");
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
BodyEntered += (Node2D body) =>
|
||||
{
|
||||
if (Input.IsActionJustReleased("interact"))
|
||||
if (body is Player player)
|
||||
{
|
||||
if (_player is not null)
|
||||
{
|
||||
EmitSignal(SignalName.RequestedEnter, this, _player);
|
||||
}
|
||||
_player = 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.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>();
|
||||
|
||||
[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;
|
||||
set
|
||||
{
|
||||
get => _collisionShape.Disabled;
|
||||
set
|
||||
_isDisabled = value;
|
||||
if (_collisionShape is not null)
|
||||
{
|
||||
_isDisabled = value;
|
||||
if (_collisionShape is not null)
|
||||
_collisionShape.Disabled = value;
|
||||
if (value)
|
||||
{
|
||||
_collisionShape.Disabled = value;
|
||||
if (value)
|
||||
{
|
||||
DamageStartTime = Time.GetTicksMsec();
|
||||
}
|
||||
DamageStartTime = Time.GetTicksMsec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public float Knockback { get; set; }
|
||||
[Export]
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
// we don't want to hurt teammates
|
||||
if (Faction != box.Faction)
|
||||
if (!_ignoreList.Contains(box))
|
||||
{
|
||||
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
|
||||
{
|
||||
get => _ignoreList;
|
||||
}
|
||||
public HashSet<BoundingBox> Hits
|
||||
{
|
||||
get => _ignoreList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,39 +2,38 @@ using Godot;
|
|||
using SupaLidlGame.Characters;
|
||||
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]
|
||||
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)
|
||||
{
|
||||
if (GetParent() is IFaction factionEntity)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,221 +4,220 @@ using SupaLidlGame.Items;
|
|||
using SupaLidlGame.Utils;
|
||||
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]
|
||||
public float Speed { get; protected set; } = 32.0f;
|
||||
|
||||
[Export]
|
||||
public float Friction { get; protected set; } = 4.0f;
|
||||
|
||||
[Export]
|
||||
public float Mass
|
||||
get => _mass;
|
||||
set
|
||||
{
|
||||
get => _mass;
|
||||
set
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (Inventory.SelectedItem is Weapon weapon)
|
||||
_health = value;
|
||||
if (_health <= 0)
|
||||
{
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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 System;
|
||||
|
||||
namespace SupaLidlGame.Characters
|
||||
{
|
||||
public partial class Enemy : NPC
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
Inventory.SelectedItem = Inventory.GetNode<Items.Item>("Sword");
|
||||
base._Ready();
|
||||
}
|
||||
namespace SupaLidlGame.Characters;
|
||||
|
||||
public override void Die()
|
||||
{
|
||||
base.Die();
|
||||
}
|
||||
public partial class Enemy : NPC
|
||||
{
|
||||
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 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
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
get => _preferredWeightDistance;
|
||||
protected set
|
||||
{
|
||||
_preferredWeightDistance = value;
|
||||
_preferredWeightDistanceSq = value * value;
|
||||
}
|
||||
_preferredWeightDistance = value;
|
||||
_preferredWeightDistanceSq = value * value;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public float MaxWeightDistance
|
||||
[Export]
|
||||
public float MaxWeightDistance
|
||||
{
|
||||
get => _maxWeightDistance;
|
||||
protected set
|
||||
{
|
||||
get => _maxWeightDistance;
|
||||
protected set
|
||||
{
|
||||
_maxWeightDistance = value;
|
||||
_maxWeightDistanceSq = value * value;
|
||||
}
|
||||
_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];
|
||||
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()
|
||||
static NPC()
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
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);
|
||||
_weightDirs[i] = new Vector2(x, y);
|
||||
}
|
||||
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
|
||||
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
|
||||
_weightDirs[i] = new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
Array.Fill(_weights, 0);
|
||||
}
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
Array.Fill(_weights, 0);
|
||||
}
|
||||
|
||||
public override void _Draw()
|
||||
{
|
||||
public override void _Draw()
|
||||
{
|
||||
#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;
|
||||
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);
|
||||
c = Colors.Blue;
|
||||
}
|
||||
else if (_weights[i] < 0)
|
||||
{
|
||||
c = Colors.Red;
|
||||
vec = -vec;
|
||||
}
|
||||
DrawLine(Vector2.Zero, vec, c);
|
||||
}
|
||||
#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;
|
||||
Character bestChar = null;
|
||||
foreach (Node node in GetParent().GetChildren())
|
||||
if (node is Character character && character.Faction != Faction)
|
||||
{
|
||||
if (node is Character character && character.Faction != Faction)
|
||||
float dist = Position.DistanceTo(character.Position);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
float dist = Position.DistanceTo(character.Position);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
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
|
||||
QueueRedraw();
|
||||
QueueRedraw();
|
||||
#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
|
||||
Vector2 dir = Target.Normalized();
|
||||
float distSq = GlobalPosition.DistanceSquaredTo(pos);
|
||||
float directDot = _weightDirs[i].Dot(dir);
|
||||
// clamp dot from [-1, 1] to [0, 1]
|
||||
directDot = (directDot + 1) / 2;
|
||||
|
||||
var spaceState = GetWorld2D().DirectSpaceState;
|
||||
var exclude = new Godot.Collections.Array<Godot.Rid>();
|
||||
exclude.Add(this.GetRid());
|
||||
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;
|
||||
|
||||
// calculate weights based on distance
|
||||
for (int i = 0; i < 16; i++)
|
||||
// favor strafing when getting closer
|
||||
if (distSq > _preferredWeightDistanceSq)
|
||||
{
|
||||
float directDot = _weightDirs[i].Dot(dir);
|
||||
// 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;
|
||||
}
|
||||
_weights[i] = directDot;
|
||||
}
|
||||
|
||||
// subtract weights that collide
|
||||
for (int i = 0; i < 16; i++)
|
||||
else if (distSq > _maxWeightDistanceSq)
|
||||
{
|
||||
var rayParams = new PhysicsRayQueryParameters2D
|
||||
{
|
||||
Exclude = exclude,
|
||||
CollideWithBodies = true,
|
||||
From = GlobalPosition,
|
||||
To = GlobalPosition + (_weightDirs[i] * 24),
|
||||
CollisionMask = 1 + 2 + 16
|
||||
};
|
||||
float dDotWeight = Mathf.Sqrt(distSq / 4096);
|
||||
float sDotWeight = 1 - dDotWeight;
|
||||
_weights[i] = (dDotWeight * directDot) +
|
||||
(sDotWeight * strafeDot);
|
||||
}
|
||||
else
|
||||
{
|
||||
_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
|
||||
if (result.Count > 0)
|
||||
var result = spaceState.IntersectRay(rayParams);
|
||||
|
||||
// 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
|
||||
float oldWeight = _weights[i];
|
||||
for (int j = 0; j < 16; j++)
|
||||
if (i == j)
|
||||
{
|
||||
if (i == j)
|
||||
{
|
||||
_weights[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dot = _weightDirs[i].Dot(_weightDirs[j]);
|
||||
_weights[j] -= _weights[j] * dot;
|
||||
}
|
||||
_weights[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 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;
|
||||
private string _spriteAnim;
|
||||
|
||||
[Export]
|
||||
public PlayerCamera Camera { get; set; }
|
||||
|
||||
public string Animation
|
||||
get => _sprite.Animation;
|
||||
set
|
||||
{
|
||||
get => _sprite.Animation;
|
||||
set
|
||||
// the Player.Animation property might be set before this node is
|
||||
// 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
|
||||
// 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)
|
||||
{
|
||||
_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;
|
||||
_spriteAnim = value;
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
_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();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public class Thinker
|
||||
{
|
||||
public virtual void Think()
|
||||
{
|
||||
namespace SupaLidlGame.Characters.Thinkers;
|
||||
|
||||
public class Thinker
|
||||
{
|
||||
public virtual void Think()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
using Godot;
|
||||
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 delegate void OnCampfireUseEventHandler();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_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);
|
||||
}
|
||||
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.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;
|
||||
|
||||
[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)
|
||||
{
|
||||
if ((Lifetime -= delta) <= 0)
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
QueueFree();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
Vector2 velocity = Velocity;
|
||||
MoveAndCollide(velocity * (float)delta);
|
||||
}
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
Vector2 velocity = Velocity;
|
||||
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,
|
||||
Character,
|
||||
Hitbox.Knockback,
|
||||
knockbackVector: Direction
|
||||
);
|
||||
}
|
||||
hurtbox.InflictDamage(
|
||||
Hitbox.Damage,
|
||||
Character,
|
||||
Hitbox.Knockback,
|
||||
knockbackVector: Direction
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,72 +2,71 @@ using Godot;
|
|||
using System;
|
||||
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(
|
||||
this AudioStreamPlayer2D audio)
|
||||
var clone = audio.Duplicate() as AudioStreamPlayer2D;
|
||||
clone.Finished += () =>
|
||||
{
|
||||
var clone = audio.Duplicate() as AudioStreamPlayer2D;
|
||||
clone.Finished += () =>
|
||||
{
|
||||
clone.QueueFree();
|
||||
};
|
||||
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(
|
||||
this AudioStreamPlayer2D audio,
|
||||
Node parent)
|
||||
var parent = new Node2D();
|
||||
world.AddChild(parent);
|
||||
parent.GlobalPosition = globalPosition;
|
||||
|
||||
var clone = audio.On(world);
|
||||
clone.Finished += () =>
|
||||
{
|
||||
var clone = audio.Clone();
|
||||
parent.AddChild(clone);
|
||||
clone.GlobalPosition = audio.GlobalPosition;
|
||||
return clone;
|
||||
}
|
||||
parent.QueueFree();
|
||||
};
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
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>
|
||||
/// 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
|
||||
Node parent;
|
||||
|
||||
while ((parent = node.GetParent()) != null)
|
||||
{
|
||||
Node parent;
|
||||
|
||||
while ((parent = node.GetParent()) != null)
|
||||
if (parent is T t)
|
||||
{
|
||||
if (parent is T t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
node = parent;
|
||||
return t;
|
||||
}
|
||||
|
||||
return null;
|
||||
node = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <returns>
|
||||
/// <see langword="null">null</see> if <param>name</param> does not match
|
||||
/// a valid Node
|
||||
/// </returns>
|
||||
public static T GetN<T>(this Node node, string name) where T : Node
|
||||
{
|
||||
return node.GetNode(name) as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <returns>
|
||||
/// <see langword="null">null</see> if <param>name</param> does not match
|
||||
/// a valid Node
|
||||
/// </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;
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
(vector.Y + other.Y) / 2);
|
||||
x += vectors[i].X;
|
||||
y += vectors[i].Y;
|
||||
}
|
||||
|
||||
public static Vector2 Midpoints(params Vector2[] vectors)
|
||||
{
|
||||
int length = vectors.Length;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
return new Vector2(x / length, y / length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
x += vectors[i].X;
|
||||
y += vectors[i].Y;
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
|
||||
return new Vector2(x / length, y / length);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
public static Vector2 Clockwise90(this Vector2 vector)
|
||||
{
|
||||
return new Vector2(vector.Y, -vector.X);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,145 +2,144 @@ using Godot;
|
|||
using SupaLidlGame.Characters;
|
||||
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 Array<Item> Items { get; private set; }
|
||||
public Item OffhandItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set => EquipItem(value, ref _offhandItem);
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Dictionary<string, int> InventoryMap { get; set; }
|
||||
public bool IsUsingItem => (SelectedItem?.IsUsing ?? false) ||
|
||||
(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;
|
||||
|
||||
private Item _offhandItem;
|
||||
|
||||
public Item SelectedItem
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Items is null)
|
||||
{
|
||||
get => _selectedItem;
|
||||
set => EquipItem(value, ref _selectedItem);
|
||||
// instantiating a new array will prevent characters from
|
||||
// sharing inventories
|
||||
Items = new Array<Item>();
|
||||
}
|
||||
|
||||
public Item OffhandItem
|
||||
Character = GetParent<Character>();
|
||||
foreach (Node child in GetChildren())
|
||||
{
|
||||
get => _selectedItem;
|
||||
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)
|
||||
if (child is Item item)
|
||||
{
|
||||
// instantiating a new array will prevent characters from
|
||||
// sharing inventories
|
||||
Items = new Array<Item>();
|
||||
GD.Print("Adding item " + item.Name);
|
||||
AddItem(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
|
||||
// two-handed item
|
||||
|
||||
if (_selectedItem is not null && !_selectedItem.IsOneHanded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_offhandItem is not null && !_offhandItem.IsOneHanded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
if (idx < Items.Count)
|
||||
{
|
||||
return Items[InventoryMap[keymap]];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public Item AddItem(Item item)
|
||||
item.CharacterOwner = Character;
|
||||
item.Visible = false;
|
||||
if (!Items.Contains(item))
|
||||
{
|
||||
if (Items.Count >= MaxCapacity)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
item.CharacterOwner = Character;
|
||||
item.Visible = false;
|
||||
if (!Items.Contains(item))
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
return item;
|
||||
Items.Add(item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public Item DropItem(Item item)
|
||||
{
|
||||
item.CharacterOwner = null;
|
||||
item.Visible = true;
|
||||
var e = SelectedItem = item;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public Item DropItem(Item item)
|
||||
{
|
||||
item.CharacterOwner = null;
|
||||
item.Visible = true;
|
||||
var e = SelectedItem = item;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,52 @@
|
|||
using Godot;
|
||||
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]
|
||||
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)
|
||||
{
|
||||
if (!CanStack)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ItemName != item.ItemName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// several more conditions may be added soon
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
public abstract void Deuse();
|
||||
return true;
|
||||
}
|
||||
|
||||
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.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;
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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);
|
||||
//Deuse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ulong ParryTimeOrigin { get; }
|
||||
public void Stun();
|
||||
}
|
||||
public bool IsParryable { get; }
|
||||
public bool IsParried { get; }
|
||||
public ulong ParryTimeOrigin { get; }
|
||||
public void Stun();
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
using Godot;
|
||||
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");
|
||||
GD.Print("lol");
|
||||
var projectile = scene.Instantiate<Entities.Projectile>();
|
||||
projectile.Hitbox.Faction = Character.Faction;
|
||||
projectile.Direction = Character.Target;
|
||||
projectile.GlobalPosition = GlobalPosition;
|
||||
projectile.GlobalRotation = projectile.Direction.Angle();
|
||||
this.GetAncestor<SupaLidlGame.Scenes.Map>()
|
||||
.Entities.AddChild(projectile);
|
||||
}
|
||||
// create projectile
|
||||
PackedScene scene = GD.Load<PackedScene>("res://Entities/RailBeam.tscn");
|
||||
GD.Print("lol");
|
||||
var projectile = scene.Instantiate<Entities.Projectile>();
|
||||
projectile.Hitbox.Faction = Character.Faction;
|
||||
projectile.Direction = Character.Target;
|
||||
projectile.GlobalPosition = GlobalPosition;
|
||||
projectile.GlobalRotation = projectile.Direction.Angle();
|
||||
this.GetAncestor<SupaLidlGame.Scenes.Map>()
|
||||
.Entities.AddChild(projectile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
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]
|
||||
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()
|
||||
{
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -4,234 +4,233 @@ using SupaLidlGame.Characters;
|
|||
using SupaLidlGame.Extensions;
|
||||
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
|
||||
is SwordAttackState;
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
base.Unequip(character);
|
||||
}
|
||||
|
||||
[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 override void Use()
|
||||
{
|
||||
// we can't use if we're still using the weapon
|
||||
if (RemainingUseTime > 0)
|
||||
{
|
||||
base.Equip(character);
|
||||
Hitbox.Faction = character.Faction; // character is null before base
|
||||
//return;
|
||||
}
|
||||
|
||||
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
|
||||
if (RemainingUseTime > 0)
|
||||
// NPCs have a slower attack
|
||||
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();
|
||||
|
||||
/*
|
||||
// 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))
|
||||
public void AttemptParry(Weapon otherWeapon)
|
||||
{
|
||||
//if (IsParryable && otherWeapon.IsParryable)
|
||||
if (otherWeapon.IsParryable &&
|
||||
otherWeapon is IParryable otherParryable)
|
||||
{
|
||||
ParryParticles.Emitting = true;
|
||||
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin)
|
||||
{
|
||||
anim = "use2";
|
||||
// our character was parried
|
||||
}
|
||||
|
||||
if (Character is NPC)
|
||||
else
|
||||
{
|
||||
// NPCs have a slower attack
|
||||
anim += "-npc";
|
||||
otherParryable.Stun();
|
||||
}
|
||||
}
|
||||
//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;
|
||||
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)
|
||||
Weapon w = hb.GetAncestor<Weapon>();
|
||||
if (w is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (BoundingBox box in Hitbox.Hits)
|
||||
{
|
||||
GD.Print("processing hit");
|
||||
if (box is Hurtbox hurtbox)
|
||||
{
|
||||
hurtbox.InflictDamage(Damage, Character, Knockback);
|
||||
}
|
||||
AttemptParry(w);
|
||||
}
|
||||
}
|
||||
|
||||
public void AttemptParry(Weapon otherWeapon)
|
||||
if (box is Hurtbox hurt)
|
||||
{
|
||||
//if (IsParryable && otherWeapon.IsParryable)
|
||||
if (otherWeapon.IsParryable &&
|
||||
otherWeapon is IParryable otherParryable)
|
||||
if (hurt.GetParent() is Character c)
|
||||
{
|
||||
ParryParticles.Emitting = true;
|
||||
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin)
|
||||
{
|
||||
// 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)
|
||||
var item = c.Inventory.SelectedItem;
|
||||
if (item is Weapon 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="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")
|
||||
StartingArea = ExtResource("2_juio7")
|
||||
CurrentPlayer = NodePath("")
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
using Godot;
|
||||
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]
|
||||
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
|
||||
{
|
||||
get
|
||||
{
|
||||
return _active;
|
||||
}
|
||||
set
|
||||
{
|
||||
_active = Visible = value;
|
||||
SetProcess(value);
|
||||
}
|
||||
return _active;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
set
|
||||
{
|
||||
Active = true;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
_active = Visible = value;
|
||||
SetProcess(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Active = true;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +1,69 @@
|
|||
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;
|
||||
|
||||
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]
|
||||
public override CharacterState InitialState { get; set; }
|
||||
|
||||
[Export]
|
||||
public Characters.Character Character { get; set; }
|
||||
|
||||
public void Process(double delta)
|
||||
var state = CurrentState.Process(delta);
|
||||
if (state is CharacterState)
|
||||
{
|
||||
var state = CurrentState.Process(delta);
|
||||
if (state is CharacterState)
|
||||
{
|
||||
ChangeState(state);
|
||||
}
|
||||
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);
|
||||
if (state is CharacterState)
|
||||
{
|
||||
ChangeState(state);
|
||||
}
|
||||
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);
|
||||
if (state is CharacterState)
|
||||
{
|
||||
ChangeState(state);
|
||||
}
|
||||
ChangeState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
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]
|
||||
public CharacterState MoveState { get; set; }
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() > 0)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() > 0)
|
||||
{
|
||||
return MoveState;
|
||||
}
|
||||
return null;
|
||||
return MoveState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||
{
|
||||
Character.Sprite.Play("idle");
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||
{
|
||||
Character.Sprite.Play("idle");
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
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]
|
||||
public CharacterState IdleState { get; set; }
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() == 0)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() == 0)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IState<CharacterState> Enter(IState<CharacterState> prev)
|
||||
{
|
||||
Character.Sprite.Play("move");
|
||||
return base.Enter(prev);
|
||||
}
|
||||
public override IState<CharacterState> Enter(IState<CharacterState> prev)
|
||||
{
|
||||
Character.Sprite.Play("move");
|
||||
return base.Enter(prev);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
namespace SupaLidlGame.State.Character
|
||||
{
|
||||
public abstract partial class NPCState : CharacterState
|
||||
{
|
||||
protected Characters.NPC _npc => Character as Characters.NPC;
|
||||
namespace SupaLidlGame.State.Character;
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
_npc.ThinkProcess(delta);
|
||||
return base.Process(delta);
|
||||
}
|
||||
public abstract partial class NPCState : CharacterState
|
||||
{
|
||||
protected Characters.NPC _npc => Character as Characters.NPC;
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
_npc.ThinkProcess(delta);
|
||||
return base.Process(delta);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,37 @@
|
|||
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]
|
||||
public CharacterState MoveState { get; set; }
|
||||
|
||||
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
|
||||
GD.Print("Entered idle state");
|
||||
if (previousState is not PlayerMoveState)
|
||||
{
|
||||
GD.Print("Entered idle state");
|
||||
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)
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
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]
|
||||
public PlayerRollState RollState { get; set; }
|
||||
Godot.GD.Print("Started moving");
|
||||
_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");
|
||||
_player.Animation = "move";
|
||||
return base.Enter(previousState);
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
public override CharacterState Input(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed("roll"))
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() == 0)
|
||||
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
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
|
||||
if (!weapon.IsUsing)
|
||||
{
|
||||
return RollState;
|
||||
}
|
||||
}
|
||||
return base.Input(@event);
|
||||
else
|
||||
{
|
||||
return RollState;
|
||||
}
|
||||
}
|
||||
return base.Input(@event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,38 @@
|
|||
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;
|
||||
// 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 IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,72 +1,71 @@
|
|||
using Godot;
|
||||
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]
|
||||
public PlayerIdleState IdleState { get; set; }
|
||||
|
||||
public override CharacterState Input(InputEvent @event)
|
||||
if (this is PlayerIdleState or PlayerMoveState &&
|
||||
!_player.Inventory.IsUsingItem)
|
||||
{
|
||||
var inventory = Character.Inventory;
|
||||
|
||||
if (this is PlayerIdleState or PlayerMoveState &&
|
||||
!_player.Inventory.IsUsingItem)
|
||||
if (@event.IsActionPressed("equip_1"))
|
||||
{
|
||||
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",
|
||||
"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 (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");
|
||||
var ret = false;
|
||||
|
||||
if (!weapon.ShouldHideIdle || isPressed)
|
||||
{
|
||||
Character.Target = dirToMouse;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
Character.UseCurrentItem();
|
||||
}
|
||||
|
||||
return ret;
|
||||
Character.Target = dirToMouse;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
Character.UseCurrentItem();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = Character.Inventory.SelectedItem;
|
||||
var offhand = Character.Inventory.OffhandItem;
|
||||
|
||||
var _ = targetTowards(item) || targetTowards(offhand);
|
||||
|
||||
return base.Process(delta);
|
||||
return false;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <remarks>
|
||||
/// This returns a <c>IState</c> in case a state is being
|
||||
/// transitioned to but wants to transition to another state. For
|
||||
/// example, an attack state can return to an idle state, but that idle
|
||||
/// state can override it to the move state immediately when necessary.
|
||||
/// </remarks>
|
||||
public IState<T> Enter(IState<T> previousState);
|
||||
/// <summary>
|
||||
/// Called when this state is entered
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This returns a <c>IState</c> in case a state is being
|
||||
/// transitioned to but wants to transition to another state. For
|
||||
/// example, an attack state can return to an idle state, but that idle
|
||||
/// state can override it to the move state immediately when necessary.
|
||||
/// </remarks>
|
||||
public IState<T> Enter(IState<T> previousState);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
|
||||
/// </summary>
|
||||
public void Exit(IState<T> nextState);
|
||||
/// <summary>
|
||||
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
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 override void _Ready()
|
||||
public virtual bool ChangeState(T nextState, bool isProxied = false)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,37 @@
|
|||
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]
|
||||
public Items.Weapons.Ranged Weapon { get; set; }
|
||||
//_timeLeft
|
||||
_timeLeft = Weapon.UseTime;
|
||||
Weapon.Attack();
|
||||
Weapon.UseDirection = Weapon.Character.Target;
|
||||
return null;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public RangedIdleState IdleState { get; set; }
|
||||
|
||||
private double _timeLeft = 0;
|
||||
|
||||
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
||||
public override WeaponState Process(double delta)
|
||||
{
|
||||
if ((_timeLeft -= delta) <= 0)
|
||||
{
|
||||
//_timeLeft
|
||||
_timeLeft = Weapon.UseTime;
|
||||
Weapon.Attack();
|
||||
Weapon.UseDirection = Weapon.Character.Target;
|
||||
return null;
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override WeaponState Process(double delta)
|
||||
{
|
||||
if ((_timeLeft -= delta) <= 0)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
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]
|
||||
public RangedFireState FireState { get; set; }
|
||||
Weapon.Visible = !Weapon.ShouldHideIdle;
|
||||
return null;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Items.Weapons.Ranged Weapon { get; set; }
|
||||
public override WeaponState Use()
|
||||
{
|
||||
return FireState;
|
||||
}
|
||||
|
||||
public override IState<WeaponState> Enter(IState<WeaponState> prev)
|
||||
{
|
||||
Weapon.Visible = !Weapon.ShouldHideIdle;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override WeaponState Use()
|
||||
{
|
||||
return FireState;
|
||||
}
|
||||
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
Weapon.Visible = true;
|
||||
}
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
Weapon.Visible = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
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]
|
||||
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
||||
Sword.EnableParry();
|
||||
|
||||
[Export]
|
||||
public SwordAttackState AttackState { get; set; }
|
||||
|
||||
private double _anticipateTime;
|
||||
|
||||
public override WeaponState Enter(IState<WeaponState> prevState)
|
||||
if (Sword.Character is SupaLidlGame.Characters.Player)
|
||||
{
|
||||
Sword.EnableParry();
|
||||
|
||||
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;
|
||||
return AttackState;
|
||||
}
|
||||
|
||||
public override WeaponState Process(double delta)
|
||||
if (Sword.Anchor.Rotation > Mathf.DegToRad(50))
|
||||
{
|
||||
// go into attack state if anticipation time is delta
|
||||
if ((_anticipateTime -= delta) <= 0)
|
||||
{
|
||||
return AttackState;
|
||||
}
|
||||
return null;
|
||||
Sword.AnimationPlayer.Play("anticipate_alternate");
|
||||
}
|
||||
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;
|
||||
|
||||
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]
|
||||
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
|
||||
//Sword.AnimationPlayer.Stop();
|
||||
Sword.Attack();
|
||||
|
||||
[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)
|
||||
if (_isAlternate)
|
||||
{
|
||||
//Sword.AnimationPlayer.Stop();
|
||||
Sword.Attack();
|
||||
|
||||
if (_isAlternate)
|
||||
{
|
||||
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;
|
||||
Sword.AnimationPlayer.Play("attack_alternate");
|
||||
}
|
||||
else
|
||||
{
|
||||
Sword.AnimationPlayer.Play("attack");
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (_useDuration <= 0)
|
||||
{
|
||||
// if we are still playing the current attack animation, we should alternate
|
||||
if (_attackAnimDuration > 0)
|
||||
{
|
||||
_isAlternate = !_isAlternate;
|
||||
}
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if ((_attackDuration -= delta) <= 0)
|
||||
{
|
||||
Sword.Deattack();
|
||||
}
|
||||
}
|
||||
|
||||
if ((_attackAnimDuration -= delta) <= 0)
|
||||
{
|
||||
Sword.AnimationPlayer.Play("RESET");
|
||||
_isAlternate = false;
|
||||
return IdleState;
|
||||
}
|
||||
|
||||
_useDuration -= delta;
|
||||
|
||||
return null;
|
||||
Sword.AnimationPlayer.Play("RESET");
|
||||
_isAlternate = false;
|
||||
return IdleState;
|
||||
}
|
||||
|
||||
_useDuration -= delta;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,51 @@
|
|||
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]
|
||||
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)
|
||||
{
|
||||
if (prevState is SwordAttackState)
|
||||
{
|
||||
_attackCooldown = Sword.UseTime - Sword.AttackTime;
|
||||
}
|
||||
|
||||
Sword.Visible = !Sword.ShouldHideIdle;
|
||||
|
||||
return null;
|
||||
_attackCooldown = Sword.UseTime - Sword.AttackTime;
|
||||
}
|
||||
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
Sword.Visible = true;
|
||||
}
|
||||
Sword.Visible = !Sword.ShouldHideIdle;
|
||||
|
||||
public override WeaponState Use()
|
||||
{
|
||||
if (_attackCooldown <= 0)
|
||||
{
|
||||
return AnticipateState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Exit(IState<WeaponState> nextState)
|
||||
{
|
||||
Sword.Visible = true;
|
||||
}
|
||||
|
||||
public override WeaponState Use()
|
||||
{
|
||||
if (_attackCooldown <= 0)
|
||||
{
|
||||
return AnticipateState;
|
||||
}
|
||||
|
||||
public override WeaponState Process(double delta)
|
||||
{
|
||||
if (_attackCooldown > 0)
|
||||
{
|
||||
_attackCooldown -= delta;
|
||||
}
|
||||
return AnticipateState;
|
||||
}
|
||||
|
||||
return null;
|
||||
public override WeaponState Process(double delta)
|
||||
{
|
||||
if (_attackCooldown > 0)
|
||||
{
|
||||
_attackCooldown -= delta;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
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;
|
||||
|
||||
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]
|
||||
public override WeaponState InitialState { get; set; }
|
||||
|
||||
public void Use()
|
||||
var state = CurrentState.Use();
|
||||
if (state is WeaponState)
|
||||
{
|
||||
var state = CurrentState.Use();
|
||||
if (state is WeaponState)
|
||||
{
|
||||
ChangeState(state);
|
||||
}
|
||||
ChangeState(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void Deuse()
|
||||
public void Deuse()
|
||||
{
|
||||
var state = CurrentState.Deuse();
|
||||
if (state is WeaponState)
|
||||
{
|
||||
var state = CurrentState.Deuse();
|
||||
if (state is WeaponState)
|
||||
{
|
||||
ChangeState(state);
|
||||
}
|
||||
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);
|
||||
if (state is WeaponState s)
|
||||
{
|
||||
ChangeState(s);
|
||||
}
|
||||
ChangeState(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,57 +2,56 @@ using Godot;
|
|||
using SupaLidlGame.Extensions;
|
||||
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;
|
||||
Vector2 _direction;
|
||||
GD.Print("Started ContextBasedSteering test");
|
||||
}
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
GD.Print("Started ContextBasedSteering test");
|
||||
}
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_direction = GetDirection();
|
||||
QueueRedraw();
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_direction = GetDirection();
|
||||
QueueRedraw();
|
||||
}
|
||||
public Vector2 GetDirection()
|
||||
{
|
||||
float directWeight;
|
||||
float strafeWeight;
|
||||
|
||||
public Vector2 GetDirection()
|
||||
{
|
||||
float directWeight;
|
||||
float strafeWeight;
|
||||
Vector2 towards = GetGlobalMousePosition() - GlobalPosition;
|
||||
float dist = towards.Length();
|
||||
|
||||
Vector2 towards = GetGlobalMousePosition() - GlobalPosition;
|
||||
float dist = towards.Length();
|
||||
Vector2 directDir = towards.Normalized();
|
||||
Vector2 strafeDir = directDir.Clockwise90();
|
||||
|
||||
Vector2 directDir = towards.Normalized();
|
||||
Vector2 strafeDir = directDir.Clockwise90();
|
||||
// weights approach 1
|
||||
// dy/dx = 1 - y
|
||||
// y = 1 - e^(-x)
|
||||
|
||||
// weights approach 1
|
||||
// dy/dx = 1 - y
|
||||
// y = 1 - e^(-x)
|
||||
directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance));
|
||||
strafeWeight = 1 - directWeight;
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
Vector2 midpoint = (strafeDir * strafeWeight)
|
||||
.Midpoint(directDir * directWeight);
|
||||
*/
|
||||
Vector2 midpoint = (directDir * directWeight)
|
||||
.Midpoint(strafeDir * strafeWeight);
|
||||
return midpoint.Normalized();
|
||||
}
|
||||
|
||||
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 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]
|
||||
public string Text { get; set; }
|
||||
Tween tween = GetTree().CreateTween()
|
||||
.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");
|
||||
_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();
|
||||
return (float)rng.NextDouble() * (max - min) + min;
|
||||
}
|
||||
|
||||
public void OnTweenFinished()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
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()
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Utils
|
||||
namespace SupaLidlGame.Utils;
|
||||
|
||||
public interface IFaction
|
||||
{
|
||||
public interface IFaction
|
||||
{
|
||||
/// <summary>
|
||||
/// The faction index that this entity belongs to.
|
||||
/// </summary>
|
||||
[Export]
|
||||
public ushort Faction { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// The faction index that this entity belongs to.
|
||||
/// </summary>
|
||||
[Export]
|
||||
public ushort Faction { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,50 +1,49 @@
|
|||
using Godot;
|
||||
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)
|
||||
{
|
||||
_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;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,58 +2,57 @@ using Godot;
|
|||
using Godot.Collections;
|
||||
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 PackedScene Character { get; set; }
|
||||
|
||||
[Export]
|
||||
public double SpawnTime { get; set; }
|
||||
|
||||
protected double _timeLeftToSpawn = 0;
|
||||
|
||||
public override void _Ready()
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if ((_timeLeftToSpawn -= delta) <= 0)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_timeLeftToSpawn = SpawnTime;
|
||||
Spawn();
|
||||
}
|
||||
}
|
||||
Vector2 size = rect.Size / 2;
|
||||
Vector2 pos = GlobalPosition;
|
||||
|
||||
public void Spawn()
|
||||
{
|
||||
var coll = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
|
||||
var rect = coll.Shape as RectangleShape2D;
|
||||
if (rect is not null)
|
||||
{
|
||||
Vector2 size = rect.Size / 2;
|
||||
Vector2 pos = GlobalPosition;
|
||||
double x1, x2, y1, y2;
|
||||
x1 = pos.X - size.X;
|
||||
x2 = pos.X + size.X;
|
||||
y1 = pos.Y - size.Y;
|
||||
y2 = pos.Y + size.Y;
|
||||
|
||||
double x1, x2, y1, y2;
|
||||
x1 = pos.X - size.X;
|
||||
x2 = pos.X + size.X;
|
||||
y1 = pos.Y - size.Y;
|
||||
y2 = pos.Y + size.Y;
|
||||
float randX = (float)GD.RandRange(x1, x2);
|
||||
float randY = (float)GD.RandRange(y1, y2);
|
||||
|
||||
float randX = (float)GD.RandRange(x1, x2);
|
||||
float randY = (float)GD.RandRange(y1, y2);
|
||||
Vector2 randPos = new Vector2(randX, randY);
|
||||
|
||||
Vector2 randPos = new Vector2(randX, randY);
|
||||
|
||||
var chr = Character.Instantiate<Characters.Character>();
|
||||
chr.GlobalPosition = randPos;
|
||||
this.GetAncestor<Scenes.Map>().Entities.AddChild(chr);
|
||||
}
|
||||
var chr = Character.Instantiate<Characters.Character>();
|
||||
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.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]
|
||||
public PackedScene StartingArea { get; set; }
|
||||
_maps = new Dictionary<string, Map>();
|
||||
_playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
|
||||
}
|
||||
|
||||
[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()
|
||||
public override void _Ready()
|
||||
{
|
||||
if (StartingArea is not null)
|
||||
{
|
||||
_maps = new Dictionary<string, Map>();
|
||||
_playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
|
||||
LoadScene(StartingArea);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LoadScene(StartingArea);
|
||||
}
|
||||
|
||||
// spawn the player in
|
||||
CreatePlayer();
|
||||
|
||||
base._Ready();
|
||||
map = _maps[scene.ResourcePath];
|
||||
}
|
||||
else
|
||||
{
|
||||
map = scene.Instantiate<Map>();
|
||||
_maps.Add(scene.ResourcePath, map);
|
||||
}
|
||||
|
||||
public void LoadScene(PackedScene scene)
|
||||
if (CurrentMap is not null)
|
||||
{
|
||||
GD.Print("Loading map " + scene.ResourcePath);
|
||||
|
||||
Map map;
|
||||
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);
|
||||
}
|
||||
CurrentMap.Entities.RemoveChild(CurrentPlayer);
|
||||
RemoveChild(CurrentMap);
|
||||
CurrentMap.Active = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
foreach (Node node in children)
|
||||
if (node is BoundingBoxes.ConnectorBox connector)
|
||||
{
|
||||
if (node is BoundingBoxes.ConnectorBox connector)
|
||||
{
|
||||
// this reconnects the EventHandler if it is connected
|
||||
connector.RequestedEnter -= _on_area_2d_requested_enter;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void MovePlayerToConnector(string name)
|
||||
{
|
||||
// find the first connector with the specified name
|
||||
var connector = CurrentMap.Areas.GetChildren().First((child) =>
|
||||
{
|
||||
if (child is BoundingBoxes.ConnectorBox connector)
|
||||
{
|
||||
return connector.Identifier == name;
|
||||
}
|
||||
return false;
|
||||
}) as BoundingBoxes.ConnectorBox;
|
||||
|
||||
CurrentPlayer.GlobalPosition = connector.GlobalPosition;
|
||||
}
|
||||
|
||||
public void MoveToArea(string path, string connector)
|
||||
{
|
||||
_currentConnector = connector;
|
||||
if (path != _currentMapResourcePath)
|
||||
{
|
||||
var scene = ResourceLoader.Load<PackedScene>(path);
|
||||
LoadScene(scene);
|
||||
_currentMapResourcePath = path;
|
||||
}
|
||||
|
||||
// after finished loading, move our player to the connector
|
||||
MovePlayerToConnector(connector);
|
||||
}
|
||||
|
||||
public void _on_area_2d_requested_enter(
|
||||
BoundingBoxes.ConnectorBox box,
|
||||
Player player)
|
||||
{
|
||||
GD.Print("Requesting to enter " + box.ToConnector);
|
||||
MoveToArea(box.ToArea, box.ToConnector);
|
||||
}
|
||||
|
||||
public void SaveGame()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void LoadGame()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void MovePlayerToConnector(string name)
|
||||
{
|
||||
// find the first connector with the specified name
|
||||
var connector = CurrentMap.Areas.GetChildren().First((child) =>
|
||||
{
|
||||
if (child is BoundingBoxes.ConnectorBox connector)
|
||||
{
|
||||
return connector.Identifier == name;
|
||||
}
|
||||
return false;
|
||||
}) as BoundingBoxes.ConnectorBox;
|
||||
|
||||
CurrentPlayer.GlobalPosition = connector.GlobalPosition;
|
||||
}
|
||||
|
||||
public void MoveToArea(string path, string connector)
|
||||
{
|
||||
_currentConnector = connector;
|
||||
if (path != _currentMapResourcePath)
|
||||
{
|
||||
var scene = ResourceLoader.Load<PackedScene>(path);
|
||||
LoadScene(scene);
|
||||
_currentMapResourcePath = path;
|
||||
}
|
||||
|
||||
// after finished loading, move our player to the connector
|
||||
MovePlayerToConnector(connector);
|
||||
}
|
||||
|
||||
public void _on_area_2d_requested_enter(
|
||||
BoundingBoxes.ConnectorBox box,
|
||||
Player player)
|
||||
{
|
||||
GD.Print("Requesting to enter " + box.ToConnector);
|
||||
MoveToArea(box.ToArea, box.ToConnector);
|
||||
}
|
||||
|
||||
public void SaveGame()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void LoadGame()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue