refactor file scoped namespace

pull/3/head
HumanoidSandvichDispenser 2023-06-03 18:21:46 -07:00
parent 96fd6f5c26
commit 706354e813
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
48 changed files with 2118 additions and 2166 deletions

View File

@ -1,11 +1,10 @@
using Godot; using Godot;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
namespace SupaLidlGame.BoundingBoxes namespace SupaLidlGame.BoundingBoxes;
public abstract partial class BoundingBox : Area2D, IFaction
{ {
public abstract partial class BoundingBox : Area2D, IFaction [Export]
{ public ushort Faction { get; set; }
[Export]
public ushort Faction { get; set; }
}
} }

View File

@ -2,71 +2,70 @@ using Godot;
using System; using System;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
namespace SupaLidlGame.BoundingBoxes namespace SupaLidlGame.BoundingBoxes;
public partial class ConnectorBox : Area2D
{ {
public partial class ConnectorBox : Area2D [Signal]
public delegate void RequestedEnterEventHandler(
ConnectorBox box,
Player player);
[Export]
public string ToArea { get; set; }
[Export]
public string ToConnector { get; set; }
[Export]
public string Identifier { get; set; }
/// <summary>
/// Determines if the connector requires the user to interact to enter
/// the connector
/// </summary>
[Export]
public bool RequiresInteraction { get; set; } = false;
[Export]
public CollisionShape2D Collision { get; set; }
private Player _player = null;
public override void _Ready()
{ {
[Signal] if (Collision is null)
public delegate void RequestedEnterEventHandler(
ConnectorBox box,
Player player);
[Export]
public string ToArea { get; set; }
[Export]
public string ToConnector { get; set; }
[Export]
public string Identifier { get; set; }
/// <summary>
/// Determines if the connector requires the user to interact to enter
/// the connector
/// </summary>
[Export]
public bool RequiresInteraction { get; set; } = false;
[Export]
public CollisionShape2D Collision { get; set; }
private Player _player = null;
public override void _Ready()
{ {
if (Collision is null) throw new NullReferenceException("Collision not specified");
{
throw new NullReferenceException("Collision not specified");
}
BodyEntered += (Node2D body) =>
{
if (body is Player player)
{
_player = player;
}
};
BodyExited += (Node2D body) =>
{
if (body is Player)
{
_player = null;
}
};
} }
public override void _Process(double delta) BodyEntered += (Node2D body) =>
{ {
if (Input.IsActionJustReleased("interact")) if (body is Player player)
{ {
if (_player is not null) _player = player;
{
EmitSignal(SignalName.RequestedEnter, this, _player);
}
} }
};
base._Process(delta); BodyExited += (Node2D body) =>
{
if (body is Player)
{
_player = null;
}
};
}
public override void _Process(double delta)
{
if (Input.IsActionJustReleased("interact"))
{
if (_player is not null)
{
EmitSignal(SignalName.RequestedEnter, this, _player);
}
} }
base._Process(delta);
} }
} }

View File

@ -3,103 +3,102 @@ using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
using SupaLidlGame.Items; using SupaLidlGame.Items;
namespace SupaLidlGame.BoundingBoxes namespace SupaLidlGame.BoundingBoxes;
public partial class Hitbox : BoundingBox
{ {
public partial class Hitbox : BoundingBox private HashSet<BoundingBox> _ignoreList = new HashSet<BoundingBox>();
[Signal]
public delegate void HitEventHandler(BoundingBox box);
[Export]
public float Damage { get; set; }
private bool _isDisabled = false;
/// <summary>
/// Getter/setter for the CollisionShape2D's Disabled property.
/// </summary>
[Export]
public bool IsDisabled
{ {
private HashSet<BoundingBox> _ignoreList = new HashSet<BoundingBox>(); get => _collisionShape.Disabled;
set
[Signal]
public delegate void HitEventHandler(BoundingBox box);
[Export]
public float Damage { get; set; }
private bool _isDisabled = false;
/// <summary>
/// Getter/setter for the CollisionShape2D's Disabled property.
/// </summary>
[Export]
public bool IsDisabled
{ {
get => _collisionShape.Disabled; _isDisabled = value;
set if (_collisionShape is not null)
{ {
_isDisabled = value; _collisionShape.Disabled = value;
if (_collisionShape is not null) if (value)
{ {
_collisionShape.Disabled = value; DamageStartTime = Time.GetTicksMsec();
if (value)
{
DamageStartTime = Time.GetTicksMsec();
}
} }
} }
} }
}
[Export] [Export]
public float Knockback { get; set; } public float Knockback { get; set; }
public Character Inflictor { get; set; } public Character Inflictor { get; set; }
public ulong DamageStartTime { get; set; } public ulong DamageStartTime { get; set; }
private CollisionShape2D _collisionShape; private CollisionShape2D _collisionShape;
private bool _isParrying = false; private bool _isParrying = false;
public override void _Ready() public override void _Ready()
{
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
IsDisabled = _isDisabled; // sets _collisionShape.Disabled
}
private bool ShouldParry(Hitbox box)
{
Node parent = GetParent<Node>();
// if this hitbox does not even belong to a weapon, skip
if (parent is not Weapon)
{ {
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
IsDisabled = _isDisabled; // sets _collisionShape.Disabled
}
private bool ShouldParry(Hitbox box)
{
Node parent = GetParent<Node>();
// if this hitbox does not even belong to a weapon, skip
if (parent is not Weapon)
{
return false;
}
var weapon = parent as Weapon;
// if we hit a hitbox, we can parry if it can be parried
if (box.GetParent<Node>() is Weapon other)
{
return weapon.IsParryable && other.IsParryable;
}
return false; return false;
} }
public void _on_area_entered(Area2D area) var weapon = parent as Weapon;
// if we hit a hitbox, we can parry if it can be parried
if (box.GetParent<Node>() is Weapon other)
{ {
if (area is BoundingBox box) return weapon.IsParryable && other.IsParryable;
}
return false;
}
public void _on_area_entered(Area2D area)
{
if (area is BoundingBox box)
{
GD.Print("hit");
// we don't want to hurt teammates
if (Faction != box.Faction)
{ {
GD.Print("hit"); if (!_ignoreList.Contains(box))
// we don't want to hurt teammates
if (Faction != box.Faction)
{ {
if (!_ignoreList.Contains(box)) _ignoreList.Add(box);
{ EmitSignal(SignalName.Hit, box);
_ignoreList.Add(box);
EmitSignal(SignalName.Hit, box);
}
} }
} }
} }
}
public void ResetIgnoreList() => _ignoreList.Clear(); public void ResetIgnoreList() => _ignoreList.Clear();
public bool HasHit(BoundingBox box) => _ignoreList.Contains(box); public bool HasHit(BoundingBox box) => _ignoreList.Contains(box);
public HashSet<BoundingBox> Hits public HashSet<BoundingBox> Hits
{ {
get => _ignoreList; get => _ignoreList;
}
} }
} }

View File

@ -2,39 +2,38 @@ using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
namespace SupaLidlGame.BoundingBoxes namespace SupaLidlGame.BoundingBoxes;
public partial class Hurtbox : BoundingBox, IFaction
{ {
public partial class Hurtbox : BoundingBox, IFaction [Signal]
public delegate void ReceivedDamageEventHandler(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default);
public override void _Ready()
{ {
[Signal] if (GetParent() is IFaction factionEntity)
public delegate void ReceivedDamageEventHandler(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default);
public override void _Ready()
{ {
if (GetParent() is IFaction factionEntity) Faction = factionEntity.Faction;
{
Faction = factionEntity.Faction;
}
}
public void InflictDamage(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default)
{
EmitSignal(
SignalName.ReceivedDamage,
damage,
inflictor,
knockback,
knockbackOrigin, knockbackVector);
} }
} }
public void InflictDamage(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default)
{
EmitSignal(
SignalName.ReceivedDamage,
damage,
inflictor,
knockback,
knockbackOrigin, knockbackVector);
}
} }

View File

@ -4,221 +4,220 @@ using SupaLidlGame.Items;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
using SupaLidlGame.State.Character; using SupaLidlGame.State.Character;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters;
public partial class Character : CharacterBody2D, IFaction
{ {
public partial class Character : CharacterBody2D, IFaction [Export]
public float Speed { get; protected set; } = 32.0f;
[Export]
public float Friction { get; protected set; } = 4.0f;
[Export]
public float Mass
{ {
[Export] get => _mass;
public float Speed { get; protected set; } = 32.0f; set
[Export]
public float Friction { get; protected set; } = 4.0f;
[Export]
public float Mass
{ {
get => _mass; if (value > 0)
set _mass = value;
}
}
protected float _mass = 1.0f;
public Vector2 NetImpulse { get; set; } = Vector2.Zero;
public Vector2 Direction { get; set; } = Vector2.Zero;
public Vector2 Target { get; set; } = Vector2.Zero;
[Export]
public float Health
{
get => _health;
set
{
if (!IsAlive && value < 0)
{ {
if (value > 0)
_mass = value;
}
}
protected float _mass = 1.0f;
public Vector2 NetImpulse { get; set; } = Vector2.Zero;
public Vector2 Direction { get; set; } = Vector2.Zero;
public Vector2 Target { get; set; } = Vector2.Zero;
[Export]
public float Health
{
get => _health;
set
{
if (!IsAlive && value < 0)
{
return;
}
_health = value;
if (_health <= 0)
{
Die();
}
}
}
public bool IsAlive => Health > 0;
protected float _health = 100f;
public double StunTime { get; set; }
[Export]
public AnimatedSprite2D Sprite { get; set; }
[Export]
public Inventory Inventory { get; set; }
[Export]
public CharacterStateMachine StateMachine { get; set; }
[Export]
public ushort Faction { get; set; }
public override void _Process(double delta)
{
if (StateMachine != null)
{
StateMachine.Process(delta);
}
Sprite.FlipH = Target.X < 0;
DrawTarget();
}
public override void _Input(InputEvent @event)
{
if (StateMachine != null)
{
StateMachine.Input(@event);
}
}
public override void _PhysicsProcess(double delta)
{
if (StateMachine != null)
{
StateMachine.PhysicsProcess(delta);
}
}
/// <summary>
/// Modify the <c>Character</c>'s velocity
/// </summary>
public virtual void ModifyVelocity()
{
if (StunTime > 0)
{
Velocity *= 0.25f;
}
}
public virtual void Die()
{
GD.Print("lol died");
QueueFree();
}
public void ApplyImpulse(Vector2 impulse, bool resetVelocity = false)
{
// delta p = F delta t
if (resetVelocity)
Velocity = Vector2.Zero;
NetImpulse += impulse / Mass;
}
public virtual void Stun(float time)
{
StunTime += time;
}
protected void DrawTarget()
{
Vector2 target = Target;
float angle = Mathf.Atan2(target.Y, Mathf.Abs(target.X));
Vector2 scale = Inventory.Scale;
if (target.X < 0)
{
scale.Y = -1;
angle = Mathf.Pi - angle;
}
else
{
scale.Y = 1;
}
Inventory.Scale = scale;
Inventory.Rotation = angle;
}
public void UseCurrentItem()
{
if (StunTime > 0)
{
GD.Print("tried to use weapon but stunned");
return; return;
} }
if (Inventory.SelectedItem is Weapon weapon) _health = value;
if (_health <= 0)
{ {
weapon.Use(); Die();
}
}
public void LookTowardsDirection()
{
if (!Direction.IsZeroApprox())
{
Target = Direction;
}
}
public virtual void _on_hurtbox_received_damage(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default)
{
Health -= damage;
// create damage text
var textScene = GD.Load<PackedScene>("res://UI/FloatingText.tscn");
var instance = textScene.Instantiate<UI.FloatingText>();
instance.Text = Mathf.Round(damage).ToString();
instance.GlobalPosition = GlobalPosition;
this.GetAncestor<TileMap>().AddChild(instance);
// apply knockback
Vector2 knockbackDir = knockbackVector;
if (knockbackDir == default)
{
if (knockbackOrigin == default)
{
knockbackOrigin = inflictor.GlobalPosition;
}
knockbackDir = knockbackOrigin.DirectionTo(GlobalPosition);
}
ApplyImpulse(knockbackDir.Normalized() * knockback);
GD.Print("lol");
// play damage animation
var anim = GetNode<AnimationPlayer>("FlashAnimation");
if (anim != null)
{
anim.Stop();
anim.Play("Hurt");
}
// if anyone involved is a player, shake their screen
Player plr = inflictor as Player ?? this as Player;
if (plr is not null)
{
plr.Camera.Shake(1, 0.4f);
}
if (this.GetNode("HurtSound") is AudioStreamPlayer2D sound)
{
// very small pitch deviation
sound.At(GlobalPosition).WithPitchDeviation(0.125f).Play();
} }
} }
} }
public bool IsAlive => Health > 0;
protected float _health = 100f;
public double StunTime { get; set; }
[Export]
public AnimatedSprite2D Sprite { get; set; }
[Export]
public Inventory Inventory { get; set; }
[Export]
public CharacterStateMachine StateMachine { get; set; }
[Export]
public ushort Faction { get; set; }
public override void _Process(double delta)
{
if (StateMachine != null)
{
StateMachine.Process(delta);
}
Sprite.FlipH = Target.X < 0;
DrawTarget();
}
public override void _Input(InputEvent @event)
{
if (StateMachine != null)
{
StateMachine.Input(@event);
}
}
public override void _PhysicsProcess(double delta)
{
if (StateMachine != null)
{
StateMachine.PhysicsProcess(delta);
}
}
/// <summary>
/// Modify the <c>Character</c>'s velocity
/// </summary>
public virtual void ModifyVelocity()
{
if (StunTime > 0)
{
Velocity *= 0.25f;
}
}
public virtual void Die()
{
GD.Print("lol died");
QueueFree();
}
public void ApplyImpulse(Vector2 impulse, bool resetVelocity = false)
{
// delta p = F delta t
if (resetVelocity)
Velocity = Vector2.Zero;
NetImpulse += impulse / Mass;
}
public virtual void Stun(float time)
{
StunTime += time;
}
protected void DrawTarget()
{
Vector2 target = Target;
float angle = Mathf.Atan2(target.Y, Mathf.Abs(target.X));
Vector2 scale = Inventory.Scale;
if (target.X < 0)
{
scale.Y = -1;
angle = Mathf.Pi - angle;
}
else
{
scale.Y = 1;
}
Inventory.Scale = scale;
Inventory.Rotation = angle;
}
public void UseCurrentItem()
{
if (StunTime > 0)
{
GD.Print("tried to use weapon but stunned");
return;
}
if (Inventory.SelectedItem is Weapon weapon)
{
weapon.Use();
}
}
public void LookTowardsDirection()
{
if (!Direction.IsZeroApprox())
{
Target = Direction;
}
}
public virtual void _on_hurtbox_received_damage(
float damage,
Character inflictor,
float knockback,
Vector2 knockbackOrigin = default,
Vector2 knockbackVector = default)
{
Health -= damage;
// create damage text
var textScene = GD.Load<PackedScene>("res://UI/FloatingText.tscn");
var instance = textScene.Instantiate<UI.FloatingText>();
instance.Text = Mathf.Round(damage).ToString();
instance.GlobalPosition = GlobalPosition;
this.GetAncestor<TileMap>().AddChild(instance);
// apply knockback
Vector2 knockbackDir = knockbackVector;
if (knockbackDir == default)
{
if (knockbackOrigin == default)
{
knockbackOrigin = inflictor.GlobalPosition;
}
knockbackDir = knockbackOrigin.DirectionTo(GlobalPosition);
}
ApplyImpulse(knockbackDir.Normalized() * knockback);
GD.Print("lol");
// play damage animation
var anim = GetNode<AnimationPlayer>("FlashAnimation");
if (anim != null)
{
anim.Stop();
anim.Play("Hurt");
}
// if anyone involved is a player, shake their screen
Player plr = inflictor as Player ?? this as Player;
if (plr is not null)
{
plr.Camera.Shake(1, 0.4f);
}
if (this.GetNode("HurtSound") is AudioStreamPlayer2D sound)
{
// very small pitch deviation
sound.At(GlobalPosition).WithPitchDeviation(0.125f).Play();
}
}
} }

View File

@ -1,19 +1,18 @@
using Godot; using Godot;
using System; using System;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters;
{
public partial class Enemy : NPC
{
public override void _Ready()
{
Inventory.SelectedItem = Inventory.GetNode<Items.Item>("Sword");
base._Ready();
}
public override void Die() public partial class Enemy : NPC
{ {
base.Die(); public override void _Ready()
} {
Inventory.SelectedItem = Inventory.GetNode<Items.Item>("Sword");
base._Ready();
}
public override void Die()
{
base.Die();
} }
} }

View File

@ -3,225 +3,224 @@ using SupaLidlGame.Extensions;
using SupaLidlGame.Items; using SupaLidlGame.Items;
using System; using System;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters;
public partial class NPC : Character
{ {
public partial class NPC : Character /// <summary>
/// Time in seconds it takes for the NPC to think FeelsDankCube
/// </summary>
public const float ThinkTime = 0.125f;
public float[] Weights => _weights;
protected float _preferredWeightDistance = 64.0f;
protected float _maxWeightDistance = 8.0f;
protected float _preferredWeightDistanceSq = 4096.0f;
protected float _maxWeightDistanceSq = 64.0f;
[Export]
public float PreferredWeightDistance
{ {
/// <summary> get => _preferredWeightDistance;
/// Time in seconds it takes for the NPC to think FeelsDankCube protected set
/// </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; _preferredWeightDistance = value;
protected set _preferredWeightDistanceSq = value * value;
{
_preferredWeightDistance = value;
_preferredWeightDistanceSq = value * value;
}
} }
}
[Export] [Export]
public float MaxWeightDistance public float MaxWeightDistance
{
get => _maxWeightDistance;
protected set
{ {
get => _maxWeightDistance; _maxWeightDistance = value;
protected set _maxWeightDistanceSq = value * value;
{
_maxWeightDistance = value;
_maxWeightDistanceSq = value * value;
}
} }
}
protected float[] _weights = new float[16]; protected float[] _weights = new float[16];
protected int _bestWeightIdx; protected int _bestWeightIdx;
protected double _thinkTimeElapsed = 0; protected double _thinkTimeElapsed = 0;
protected Vector2 _blockingDir; protected Vector2 _blockingDir;
protected static readonly Vector2[] _weightDirs = new Vector2[16]; 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);
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16); _weightDirs[i] = new Vector2(x, y);
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
_weightDirs[i] = new Vector2(x, y);
}
} }
}
public override void _Ready() public override void _Ready()
{ {
base._Ready(); base._Ready();
Array.Fill(_weights, 0); Array.Fill(_weights, 0);
} }
public override void _Draw() public override void _Draw()
{ {
#if DEBUG #if DEBUG
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{
Vector2 vec = _weightDirs[i] * _weights[i] * 32;
Color c = Colors.Green;
if (_bestWeightIdx == i)
{ {
Vector2 vec = _weightDirs[i] * _weights[i] * 32; c = Colors.Blue;
Color c = Colors.Green;
if (_bestWeightIdx == i)
{
c = Colors.Blue;
}
else if (_weights[i] < 0)
{
c = Colors.Red;
vec = -vec;
}
DrawLine(Vector2.Zero, vec, c);
} }
else if (_weights[i] < 0)
{
c = Colors.Red;
vec = -vec;
}
DrawLine(Vector2.Zero, vec, c);
}
#endif #endif
base._Draw(); base._Draw();
} }
protected virtual Character FindBestTarget() protected virtual Character FindBestTarget()
{
float bestDist = float.MaxValue;
Character bestChar = null;
foreach (Node node in GetParent().GetChildren())
{ {
float bestDist = float.MaxValue; if (node is Character character && character.Faction != Faction)
Character bestChar = null;
foreach (Node node in GetParent().GetChildren())
{ {
if (node is Character character && character.Faction != Faction) float dist = Position.DistanceTo(character.Position);
if (dist < bestDist)
{ {
float dist = Position.DistanceTo(character.Position); bestDist = dist;
if (dist < bestDist) bestChar = character;
{
bestDist = dist;
bestChar = character;
}
} }
} }
return bestChar;
} }
return bestChar;
}
public void ThinkProcess(double delta) public void ThinkProcess(double delta)
{
if ((_thinkTimeElapsed += delta) > ThinkTime)
{ {
if ((_thinkTimeElapsed += delta) > ThinkTime) _thinkTimeElapsed = 0;
{ Think();
_thinkTimeElapsed = 0;
Think();
#if DEBUG #if DEBUG
QueueRedraw(); QueueRedraw();
#endif #endif
}
Direction = _weightDirs[_bestWeightIdx];
} }
public void UpdateWeights(Vector2 pos) Direction = _weightDirs[_bestWeightIdx];
}
public void UpdateWeights(Vector2 pos)
{
// FIXME: TODO: remove all the spaghetti
Vector2 dir = Target.Normalized();
float distSq = GlobalPosition.DistanceSquaredTo(pos);
var spaceState = GetWorld2D().DirectSpaceState;
var exclude = new Godot.Collections.Array<Godot.Rid>();
exclude.Add(this.GetRid());
// calculate weights based on distance
for (int i = 0; i < 16; i++)
{ {
// FIXME: TODO: remove all the spaghetti float directDot = _weightDirs[i].Dot(dir);
Vector2 dir = Target.Normalized(); // clamp dot from [-1, 1] to [0, 1]
float distSq = GlobalPosition.DistanceSquaredTo(pos); directDot = (directDot + 1) / 2;
var spaceState = GetWorld2D().DirectSpaceState; float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
var exclude = new Godot.Collections.Array<Godot.Rid>(); float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
exclude.Add(this.GetRid()); strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
// calculate weights based on distance // favor strafing when getting closer
for (int i = 0; i < 16; i++) if (distSq > _preferredWeightDistanceSq)
{ {
float directDot = _weightDirs[i].Dot(dir); _weights[i] = directDot;
// clamp dot from [-1, 1] to [0, 1]
directDot = (directDot + 1) / 2;
float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
// favor strafing when getting closer
if (distSq > _preferredWeightDistanceSq)
{
_weights[i] = directDot;
}
else if (distSq > _maxWeightDistanceSq)
{
float dDotWeight = Mathf.Sqrt(distSq / 4096);
float sDotWeight = 1 - dDotWeight;
_weights[i] = (dDotWeight * directDot) +
(sDotWeight * strafeDot);
}
else
{
_weights[i] = strafeDot;
}
} }
else if (distSq > _maxWeightDistanceSq)
// subtract weights that collide
for (int i = 0; i < 16; i++)
{ {
var rayParams = new PhysicsRayQueryParameters2D float dDotWeight = Mathf.Sqrt(distSq / 4096);
{ float sDotWeight = 1 - dDotWeight;
Exclude = exclude, _weights[i] = (dDotWeight * directDot) +
CollideWithBodies = true, (sDotWeight * strafeDot);
From = GlobalPosition, }
To = GlobalPosition + (_weightDirs[i] * 24), else
CollisionMask = 1 + 2 + 16 {
}; _weights[i] = strafeDot;
}
}
var result = spaceState.IntersectRay(rayParams); // subtract weights that collide
for (int i = 0; i < 16; i++)
{
var rayParams = new PhysicsRayQueryParameters2D
{
Exclude = exclude,
CollideWithBodies = true,
From = GlobalPosition,
To = GlobalPosition + (_weightDirs[i] * 24),
CollisionMask = 1 + 2 + 16
};
// if we hit something var result = spaceState.IntersectRay(rayParams);
if (result.Count > 0)
// if we hit something
if (result.Count > 0)
{
// then we subtract the value of this from the other weights
float oldWeight = _weights[i];
for (int j = 0; j < 16; j++)
{ {
// then we subtract the value of this from the other weights if (i == j)
float oldWeight = _weights[i];
for (int j = 0; j < 16; j++)
{ {
if (i == j) _weights[i] = 0;
{ }
_weights[i] = 0; else
} {
else float dot = _weightDirs[i].Dot(_weightDirs[j]);
{ _weights[j] -= _weights[j] * dot;
float dot = _weightDirs[i].Dot(_weightDirs[j]);
_weights[j] -= _weights[j] * dot;
}
} }
}
}
float bestWeight = 0;
for (int i = 0; i < 16; i++)
{
if (_weights[i] > bestWeight)
{
_bestWeightIdx = i;
bestWeight = _weights[i];
} }
} }
} }
protected virtual void Think()
{
// TODO: the entity should wander if it doesn't find a best target
Character bestTarget = FindBestTarget();
if (bestTarget is not null)
{
Vector2 pos = FindBestTarget().GlobalPosition;
Target = pos - GlobalPosition;
Vector2 dir = Target;
float dist = GlobalPosition.DistanceSquaredTo(pos);
UpdateWeights(pos);
if (dist < 1024) float bestWeight = 0;
for (int i = 0; i < 16; i++)
{
if (_weights[i] > bestWeight)
{
_bestWeightIdx = i;
bestWeight = _weights[i];
}
}
}
protected virtual void Think()
{
// TODO: the entity should wander if it doesn't find a best target
Character bestTarget = FindBestTarget();
if (bestTarget is not null)
{
Vector2 pos = FindBestTarget().GlobalPosition;
Target = pos - GlobalPosition;
Vector2 dir = Target;
float dist = GlobalPosition.DistanceSquaredTo(pos);
UpdateWeights(pos);
if (dist < 1024)
{
if (Inventory.SelectedItem is Weapon weapon)
{ {
if (Inventory.SelectedItem is Weapon weapon) UseCurrentItem();
{
UseCurrentItem();
}
} }
} }
} }

View File

@ -1,65 +1,64 @@
using Godot; using Godot;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters;
public partial class Player : Character
{ {
public partial class Player : Character private AnimatedSprite2D _sprite;
private string _spriteAnim;
[Export]
public PlayerCamera Camera { get; set; }
public string Animation
{ {
private AnimatedSprite2D _sprite; get => _sprite.Animation;
private string _spriteAnim; set
[Export]
public PlayerCamera Camera { get; set; }
public string Animation
{ {
get => _sprite.Animation; // the Player.Animation property might be set before this node is
set // even ready, so if _sprite is null, then we just hold the new
// animation in a temp value, which will be assigned once this node
// is ready
if (_sprite is null)
{ {
// the Player.Animation property might be set before this node is _spriteAnim = value;
// even ready, so if _sprite is null, then we just hold the new return;
// animation in a temp value, which will be assigned once this
// node is ready
if (_sprite is null)
{
_spriteAnim = value;
return;
}
_sprite.Play(value);
}
}
public override void _Ready()
{
_sprite = GetNode<AnimatedSprite2D>("Sprite");
if (_spriteAnim != default)
{
_sprite.Animation = _spriteAnim;
}
}
public override void ModifyVelocity()
{
if (StateMachine.CurrentState is SupaLidlGame.State.Character.PlayerRollState)
{
Velocity *= 2;
} }
base.ModifyVelocity(); _sprite.Play(value);
}
public override void Stun(float time)
{
base.Stun(time);
Camera.Shake(2, 0.8f);
// TODO: implement visual effects for stun
}
public override void Die()
{
GD.Print("died");
//base.Die();
} }
} }
public override void _Ready()
{
_sprite = GetNode<AnimatedSprite2D>("Sprite");
if (_spriteAnim != default)
{
_sprite.Animation = _spriteAnim;
}
}
public override void ModifyVelocity()
{
if (StateMachine.CurrentState is SupaLidlGame.State.Character.PlayerRollState)
{
Velocity *= 2;
}
base.ModifyVelocity();
}
public override void Stun(float time)
{
base.Stun(time);
Camera.Shake(2, 0.8f);
// TODO: implement visual effects for stun
}
public override void Die()
{
GD.Print("died");
//base.Die();
}
} }

View File

@ -1,10 +1,9 @@
namespace SupaLidlGame.Characters.Thinkers namespace SupaLidlGame.Characters.Thinkers;
{
public class Thinker public class Thinker
{ {
public virtual void Think() public virtual void Think()
{ {
}
} }
} }

View File

@ -1,24 +1,23 @@
using Godot; using Godot;
using System; using System;
namespace SupaLidlGame.Entities namespace SupaLidlGame.Entities;
public partial class Campfire : StaticBody2D
{ {
public partial class Campfire : StaticBody2D private PointLight2D _light;
[Signal]
public delegate void OnCampfireUseEventHandler();
public override void _Ready()
{ {
private PointLight2D _light; _light = GetNode<PointLight2D>("PointLight2D");
}
[Signal] public override void _Process(double delta)
public delegate void OnCampfireUseEventHandler(); {
_light.Energy += (GD.Randf() - 0.5f) * 8 * (float)delta;
public override void _Ready() _light.Energy = Math.Clamp(_light.Energy, 1.2f, 2.0f);
{
_light = GetNode<PointLight2D>("PointLight2D");
}
public override void _Process(double delta)
{
_light.Energy += (GD.Randf() - 0.5f) * 8 * (float)delta;
_light.Energy = Math.Clamp(_light.Energy, 1.2f, 2.0f);
}
} }
} }

View File

@ -2,51 +2,50 @@ using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
using SupaLidlGame.BoundingBoxes; using SupaLidlGame.BoundingBoxes;
namespace SupaLidlGame.Entities namespace SupaLidlGame.Entities;
public partial class Projectile : RigidBody2D
{ {
public partial class Projectile : RigidBody2D public Vector2 Velocity => Direction * Speed;
[Export]
public float Speed { get; set; }
[Export]
public Vector2 Direction { get; set; }
[Export]
public Hitbox Hitbox { get; set; }
[Export]
public double Lifetime { get; set; } = 10;
public Character Character { get; set; }
public override void _Process(double delta)
{ {
public Vector2 Velocity => Direction * Speed; if ((Lifetime -= delta) <= 0)
[Export]
public float Speed { get; set; }
[Export]
public Vector2 Direction { get; set; }
[Export]
public Hitbox Hitbox { get; set; }
[Export]
public double Lifetime { get; set; } = 10;
public Character Character { get; set; }
public override void _Process(double delta)
{ {
if ((Lifetime -= delta) <= 0) QueueFree();
{
QueueFree();
}
} }
}
public override void _PhysicsProcess(double delta) public override void _PhysicsProcess(double delta)
{ {
Vector2 velocity = Velocity; Vector2 velocity = Velocity;
MoveAndCollide(velocity * (float)delta); MoveAndCollide(velocity * (float)delta);
} }
public void _on_hitbox_hit(BoundingBox box) public void _on_hitbox_hit(BoundingBox box)
{
if (box is Hurtbox hurtbox)
{ {
if (box is Hurtbox hurtbox) hurtbox.InflictDamage(
{ Hitbox.Damage,
hurtbox.InflictDamage( Character,
Hitbox.Damage, Hitbox.Knockback,
Character, knockbackVector: Direction
Hitbox.Knockback, );
knockbackVector: Direction
);
}
} }
} }
} }

View File

@ -2,72 +2,71 @@ using Godot;
using System; using System;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
namespace SupaLidlGame.Extensions namespace SupaLidlGame.Extensions;
public static class AudioStreamPlayer2DExtensions
{ {
public static class AudioStreamPlayer2DExtensions public static AudioStreamPlayer2D Clone(
this AudioStreamPlayer2D audio)
{ {
public static AudioStreamPlayer2D Clone( var clone = audio.Duplicate() as AudioStreamPlayer2D;
this AudioStreamPlayer2D audio) clone.Finished += () =>
{ {
var clone = audio.Duplicate() as AudioStreamPlayer2D; clone.QueueFree();
clone.Finished += () => };
{ return clone;
clone.QueueFree(); }
};
return clone; public static AudioStreamPlayer2D On(
this AudioStreamPlayer2D audio,
Node parent)
{
var clone = audio.Clone();
parent.AddChild(clone);
clone.GlobalPosition = audio.GlobalPosition;
return clone;
}
public static AudioStreamPlayer2D OnWorld(
this AudioStreamPlayer2D audio)
{
var world = audio.GetTree().Root.GetNode("World/TileMap");
if (world is null)
{
throw new NullReferenceException("World does not exist");
}
var clone = audio.On(world);
clone.GlobalPosition = audio.GlobalPosition;
return clone;
}
public static AudioStreamPlayer2D At(
this AudioStreamPlayer2D audio,
Vector2 globalPosition)
{
var world = audio.GetTree().Root.GetNode("World/TileMap");
if (world is null)
{
throw new NullReferenceException("World does not exist");
} }
public static AudioStreamPlayer2D On( var parent = new Node2D();
this AudioStreamPlayer2D audio, world.AddChild(parent);
Node parent) parent.GlobalPosition = globalPosition;
var clone = audio.On(world);
clone.Finished += () =>
{ {
var clone = audio.Clone(); parent.QueueFree();
parent.AddChild(clone); };
clone.GlobalPosition = audio.GlobalPosition; return clone;
return clone; }
}
public static AudioStreamPlayer2D OnWorld( public static AudioStreamPlayer2D WithPitchDeviation(
this AudioStreamPlayer2D audio) this AudioStreamPlayer2D audio,
{ float deviation)
var world = audio.GetTree().Root.GetNode("World/TileMap"); {
if (world is null) audio.PitchScale = (float)GD.Randfn(audio.PitchScale, deviation);
{ return audio;
throw new NullReferenceException("World does not exist");
}
var clone = audio.On(world);
clone.GlobalPosition = audio.GlobalPosition;
return clone;
}
public static AudioStreamPlayer2D At(
this AudioStreamPlayer2D audio,
Vector2 globalPosition)
{
var world = audio.GetTree().Root.GetNode("World/TileMap");
if (world is null)
{
throw new NullReferenceException("World does not exist");
}
var parent = new Node2D();
world.AddChild(parent);
parent.GlobalPosition = globalPosition;
var clone = audio.On(world);
clone.Finished += () =>
{
parent.QueueFree();
};
return clone;
}
public static AudioStreamPlayer2D WithPitchDeviation(
this AudioStreamPlayer2D audio,
float deviation)
{
audio.PitchScale = (float)GD.Randfn(audio.PitchScale, deviation);
return audio;
}
} }
} }

View File

@ -1,41 +1,40 @@
using Godot; using Godot;
namespace SupaLidlGame.Extensions namespace SupaLidlGame.Extensions;
public static class NodeExtensions
{ {
public static class NodeExtensions /// <summary>
/// Iterates through each ancestor until it finds an ancestor of type
/// <c>T</c>
/// </summary>
public static T GetAncestor<T>(this Node node) where T : Node
{ {
/// <summary> Node parent;
/// Iterates through each ancestor until it finds an ancestor of type
/// <c>T</c> while ((parent = node.GetParent()) != null)
/// </summary>
public static T GetAncestor<T>(this Node node) where T : Node
{ {
Node parent; if (parent is T t)
while ((parent = node.GetParent()) != null)
{ {
if (parent is T t) return t;
{
return t;
}
node = parent;
} }
return null; node = parent;
} }
/// <summary> return null;
/// A version <c>GetNode</c> that returns null rather than cause an }
/// exception if the node is not found or is not the same type.
/// </summary> /// <summary>
/// <returns> /// A version <c>GetNode</c> that returns null rather than cause an
/// <see langword="null">null</see> if <param>name</param> does not match /// exception if the node is not found or is not the same type.
/// a valid Node /// </summary>
/// </returns> /// <returns>
public static T GetN<T>(this Node node, string name) where T : Node /// <see langword="null">null</see> if <param>name</param> does not match
{ /// a valid Node
return node.GetNode(name) as T; /// </returns>
} public static T GetN<T>(this Node node, string name) where T : Node
{
return node.GetNode(name) as T;
} }
} }

View File

@ -1,13 +1,12 @@
using Godot; using Godot;
namespace SupaLidlGame.Extensions namespace SupaLidlGame.Extensions;
public static class Node2DExtensions
{ {
public static class Node2DExtensions public static void RayCast(this Node2D node, Vector2 ray)
{ {
public static void RayCast(this Node2D node, Vector2 ray) //var spaceState = node.GetWorld2d().DirectSpaceState;
{ //var result = spaceState.IntersectRay();
//var spaceState = node.GetWorld2d().DirectSpaceState;
//var result = spaceState.IntersectRay();
}
} }
} }

View File

@ -1,41 +1,40 @@
using Godot; using Godot;
namespace SupaLidlGame.Extensions namespace SupaLidlGame.Extensions;
public static class Vector2Extensions
{ {
public static class Vector2Extensions public static Vector2 Midpoint(this Vector2 vector, Vector2 other)
{ {
public static Vector2 Midpoint(this Vector2 vector, Vector2 other) return new Vector2((vector.X + other.X) / 2,
(vector.Y + other.Y) / 2);
}
public static Vector2 Midpoints(params Vector2[] vectors)
{
int length = vectors.Length;
float x = 0;
float y = 0;
for (int i = 0; i < length; i++)
{ {
return new Vector2((vector.X + other.X) / 2, x += vectors[i].X;
(vector.Y + other.Y) / 2); y += vectors[i].Y;
} }
public static Vector2 Midpoints(params Vector2[] vectors) return new Vector2(x / length, y / length);
{ }
int length = vectors.Length;
float x = 0;
float y = 0;
for (int i = 0; i < length; i++) /// <summary>
{ /// Returns this vector 90 degrees counter clockwise (x, y) -> (-y, x)
x += vectors[i].X; /// </summary>
y += vectors[i].Y; public static Vector2 Counterclockwise90(this Vector2 vector)
} {
return new Vector2(-vector.Y, vector.X);
}
return new Vector2(x / length, y / length); public static Vector2 Clockwise90(this Vector2 vector)
} {
return new Vector2(vector.Y, -vector.X);
/// <summary>
/// Returns this vector 90 degrees counter clockwise (x, y) -> (-y, x)
/// </summary>
public static Vector2 Counterclockwise90(this Vector2 vector)
{
return new Vector2(-vector.Y, vector.X);
}
public static Vector2 Clockwise90(this Vector2 vector)
{
return new Vector2(vector.Y, -vector.X);
}
} }
} }

View File

@ -2,145 +2,144 @@ using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
using Godot.Collections; using Godot.Collections;
namespace SupaLidlGame.Items namespace SupaLidlGame.Items;
public partial class Inventory : Node2D
{ {
public partial class Inventory : Node2D public Character Character { get; private set; }
[Export]
public Array<Item> Items { get; private set; }
[Export]
public Dictionary<string, int> InventoryMap { get; set; }
public const int MaxCapacity = 32;
private Item _selectedItem;
private Item _offhandItem;
public Item SelectedItem
{ {
public Character Character { get; private set; } get => _selectedItem;
set => EquipItem(value, ref _selectedItem);
}
[Export] public Item OffhandItem
public Array<Item> Items { get; private set; } {
get => _selectedItem;
set => EquipItem(value, ref _offhandItem);
}
[Export] public bool IsUsingItem => (SelectedItem?.IsUsing ?? false) ||
public Dictionary<string, int> InventoryMap { get; set; } (OffhandItem?.IsUsing ?? false);
public const int MaxCapacity = 32; public Inventory()
{
InventoryMap = new Dictionary<string, int>();
InventoryMap.Add("equip_1", 0);
InventoryMap.Add("equip_2", 1);
InventoryMap.Add("equip_3", 2);
}
private Item _selectedItem; public override void _Ready()
{
private Item _offhandItem; if (Items is null)
public Item SelectedItem
{ {
get => _selectedItem; // instantiating a new array will prevent characters from
set => EquipItem(value, ref _selectedItem); // sharing inventories
Items = new Array<Item>();
} }
Character = GetParent<Character>();
public Item OffhandItem foreach (Node child in GetChildren())
{ {
get => _selectedItem; if (child is Item item)
set => EquipItem(value, ref _offhandItem);
}
public bool IsUsingItem => (SelectedItem?.IsUsing ?? false) ||
(OffhandItem?.IsUsing ?? false);
public Inventory()
{
InventoryMap = new Dictionary<string, int>();
InventoryMap.Add("equip_1", 0);
InventoryMap.Add("equip_2", 1);
InventoryMap.Add("equip_3", 2);
}
public override void _Ready()
{
if (Items is null)
{ {
// instantiating a new array will prevent characters from GD.Print("Adding item " + item.Name);
// sharing inventories AddItem(item);
Items = new Array<Item>();
} }
Character = GetParent<Character>();
foreach (Node child in GetChildren())
{
if (child is Item item)
{
GD.Print("Adding item " + item.Name);
AddItem(item);
}
}
base._Ready();
} }
base._Ready();
}
private bool EquipItem(Item item, ref Item slot) private bool EquipItem(Item item, ref Item slot)
{
if (item is not null)
{ {
if (item is not null) if (item.IsOneHanded)
{ {
if (item.IsOneHanded) // we can not equip this if either hand is occupied by
// two-handed item
if (_selectedItem is not null && !_selectedItem.IsOneHanded)
{ {
// we can not equip this if either hand is occupied by return false;
// two-handed item
if (_selectedItem is not null && !_selectedItem.IsOneHanded)
{
return false;
}
if (_offhandItem is not null && !_offhandItem.IsOneHanded)
{
return false;
}
} }
if (!Items.Contains(item)) if (_offhandItem is not null && !_offhandItem.IsOneHanded)
{ {
GD.PrintErr("Tried to equip an item not in the inventory.");
return false; return false;
} }
} }
if (slot is not null) if (!Items.Contains(item))
{ {
slot.Unequip(Character); GD.PrintErr("Tried to equip an item not in the inventory.");
return false;
} }
slot = item;
if (item is not null)
{
item.Equip(Character);
}
return true;
} }
public Item GetItemByMap(string keymap) if (slot is not null)
{ {
if (InventoryMap.ContainsKey(keymap)) slot.Unequip(Character);
}
slot = item;
if (item is not null)
{
item.Equip(Character);
}
return true;
}
public Item GetItemByMap(string keymap)
{
if (InventoryMap.ContainsKey(keymap))
{
int idx = InventoryMap[keymap];
if (idx < Items.Count)
{ {
int idx = InventoryMap[keymap]; return Items[InventoryMap[keymap]];
if (idx < Items.Count)
{
return Items[InventoryMap[keymap]];
}
} }
else GD.Print(keymap + " does not exist"); }
else GD.Print(keymap + " does not exist");
return null;
}
public Item AddItem(Item item)
{
if (Items.Count >= MaxCapacity)
{
return null; return null;
} }
public Item AddItem(Item item) item.CharacterOwner = Character;
item.Visible = false;
if (!Items.Contains(item))
{ {
if (Items.Count >= MaxCapacity) Items.Add(item);
{
return null;
}
item.CharacterOwner = Character;
item.Visible = false;
if (!Items.Contains(item))
{
Items.Add(item);
}
return item;
} }
return item;
}
public Item DropItem(Item item) public Item DropItem(Item item)
{ {
item.CharacterOwner = null; item.CharacterOwner = null;
item.Visible = true; item.Visible = true;
var e = SelectedItem = item; var e = SelectedItem = item;
throw new System.NotImplementedException(); throw new System.NotImplementedException();
}
} }
} }

View File

@ -1,53 +1,52 @@
using Godot; using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
namespace SupaLidlGame.Items namespace SupaLidlGame.Items;
public abstract partial class Item : Node2D
{ {
public abstract partial class Item : Node2D [Export]
public string ItemName { get; set; }
[Export]
public string Description { get; set; }
[Export]
public bool CanStack { get; set; } = false;
public int Count { get; set; } = 1;
public bool IsOneHanded { get; set; } = false;
public Character CharacterOwner { get; set; }
public virtual bool IsUsing => false;
/// <summary>
/// Determines if this item can directly stack with other items
/// </summary>
public virtual bool StacksWith(Item item)
{ {
[Export] if (!CanStack)
public string ItemName { get; set; }
[Export]
public string Description { get; set; }
[Export]
public bool CanStack { get; set; } = false;
public int Count { get; set; } = 1;
public bool IsOneHanded { get; set; } = false;
public Character CharacterOwner { get; set; }
public virtual bool IsUsing => false;
/// <summary>
/// Determines if this item can directly stack with other items
/// </summary>
public virtual bool StacksWith(Item item)
{ {
if (!CanStack) return false;
{
return false;
}
if (ItemName != item.ItemName)
{
return false;
}
// several more conditions may be added soon
return true;
} }
public abstract void Equip(Character character); if (ItemName != item.ItemName)
{
return false;
}
public abstract void Unequip(Character character); // several more conditions may be added soon
public abstract void Use(); return true;
public abstract void Deuse();
} }
public abstract void Equip(Character character);
public abstract void Unequip(Character character);
public abstract void Use();
public abstract void Deuse();
} }

View File

@ -2,109 +2,108 @@ using Godot;
using SupaLidlGame.BoundingBoxes; using SupaLidlGame.BoundingBoxes;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
namespace SupaLidlGame.Items namespace SupaLidlGame.Items;
public abstract partial class Weapon : Item
{ {
public abstract partial class Weapon : Item public double RemainingUseTime { get; protected set; } = 0;
public override bool IsUsing => RemainingUseTime > 0;
/// <summary>
/// How much damage in HP that this weapon deals.
/// </summary>
[Export]
public float Damage { get; set; } = 0;
/// <summary>
/// The time in seconds it takes for this weapon to become available
/// again after using.
/// </summary>
[Export]
public double UseTime { get; set; } = 0;
/// <summary>
/// The magnitude of the knockback force of the weapon.
/// </summary>
[Export]
public float Knockback { get; set; } = 0;
/// <summary>
/// The initial velocity of any projectile the weapon may spawn.
/// </summary>
[Export]
public float InitialVelocity { get; set; } = 0;
/// <summary>
/// Hides the weapon if it is idle (i.e. not being used).
/// </summary>
[Export]
public bool ShouldHideIdle { get; set; } = false;
/// <summary>
/// Freezes the player's target angle if this weapon is being used.
/// </summary>
[Export]
public bool ShouldFreezeAngleOnUse { get; set; } = true;
[Export]
public float MinDistanceHint { get; set; }
[Export]
public float MaxDistanceHint { get; set; }
public virtual bool IsParryable { get; protected set; } = false;
public bool IsParried { get; set; }
public Character Character { get; set; }
public Vector2 UseDirection { get; set; }
public override bool StacksWith(Item item) => false;
public override void Equip(Character character)
{ {
public double RemainingUseTime { get; protected set; } = 0; if (!ShouldHideIdle || IsUsing)
public override bool IsUsing => RemainingUseTime > 0;
/// <summary>
/// How much damage in HP that this weapon deals.
/// </summary>
[Export]
public float Damage { get; set; } = 0;
/// <summary>
/// The time in seconds it takes for this weapon to become available
/// again after using.
/// </summary>
[Export]
public double UseTime { get; set; } = 0;
/// <summary>
/// The magnitude of the knockback force of the weapon.
/// </summary>
[Export]
public float Knockback { get; set; } = 0;
/// <summary>
/// The initial velocity of any projectile the weapon may spawn.
/// </summary>
[Export]
public float InitialVelocity { get; set; } = 0;
/// <summary>
/// Hides the weapon if it is idle (i.e. not being used).
/// </summary>
[Export]
public bool ShouldHideIdle { get; set; } = false;
/// <summary>
/// Freezes the player's target angle if this weapon is being used.
/// </summary>
[Export]
public bool ShouldFreezeAngleOnUse { get; set; } = true;
[Export]
public float MinDistanceHint { get; set; }
[Export]
public float MaxDistanceHint { get; set; }
public virtual bool IsParryable { get; protected set; } = false;
public bool IsParried { get; set; }
public Character Character { get; set; }
public Vector2 UseDirection { get; set; }
public override bool StacksWith(Item item) => false;
public override void Equip(Character character)
{ {
if (!ShouldHideIdle || IsUsing) Visible = true;
}
Character = character;
}
public override void Unequip(Character character)
{
Visible = false;
Character = null;
}
public override void Use()
{
RemainingUseTime = UseTime;
}
public override void Deuse()
{
}
public override void _Process(double delta)
{
if (RemainingUseTime > 0)
{
if ((RemainingUseTime -= delta) <= 0)
{ {
Visible = true; //Deuse();
}
Character = character;
}
public override void Unequip(Character character)
{
Visible = false;
Character = null;
}
public override void Use()
{
RemainingUseTime = UseTime;
}
public override void Deuse()
{
}
public override void _Process(double delta)
{
if (RemainingUseTime > 0)
{
if ((RemainingUseTime -= delta) <= 0)
{
//Deuse();
}
}
}
public virtual void _on_hitbox_hit(BoundingBox box)
{
if (box is Hurtbox hurtbox)
{
hurtbox.InflictDamage(Damage, Character, Knockback);
} }
} }
} }
public virtual void _on_hitbox_hit(BoundingBox box)
{
if (box is Hurtbox hurtbox)
{
hurtbox.InflictDamage(Damage, Character, Knockback);
}
}
} }

View File

@ -1,10 +1,9 @@
namespace SupaLidlGame.Items.Weapons namespace SupaLidlGame.Items.Weapons;
public interface IParryable
{ {
public interface IParryable public bool IsParryable { get; }
{ public bool IsParried { get; }
public bool IsParryable { get; } public ulong ParryTimeOrigin { get; }
public bool IsParried { get; } public void Stun();
public ulong ParryTimeOrigin { get; }
public void Stun();
}
} }

View File

@ -1,22 +1,21 @@
using Godot; using Godot;
using SupaLidlGame.Extensions; using SupaLidlGame.Extensions;
namespace SupaLidlGame.Items.Weapons namespace SupaLidlGame.Items.Weapons;
public partial class Railgun : Ranged
{ {
public partial class Railgun : Ranged public override void Attack()
{ {
public override void Attack() // create projectile
{ PackedScene scene = GD.Load<PackedScene>("res://Entities/RailBeam.tscn");
// create projectile GD.Print("lol");
PackedScene scene = GD.Load<PackedScene>("res://Entities/RailBeam.tscn"); var projectile = scene.Instantiate<Entities.Projectile>();
GD.Print("lol"); projectile.Hitbox.Faction = Character.Faction;
var projectile = scene.Instantiate<Entities.Projectile>(); projectile.Direction = Character.Target;
projectile.Hitbox.Faction = Character.Faction; projectile.GlobalPosition = GlobalPosition;
projectile.Direction = Character.Target; projectile.GlobalRotation = projectile.Direction.Angle();
projectile.GlobalPosition = GlobalPosition; this.GetAncestor<SupaLidlGame.Scenes.Map>()
projectile.GlobalRotation = projectile.Direction.Angle(); .Entities.AddChild(projectile);
this.GetAncestor<SupaLidlGame.Scenes.Map>()
.Entities.AddChild(projectile);
}
} }
} }

View File

@ -1,43 +1,42 @@
using Godot; using Godot;
namespace SupaLidlGame.Items.Weapons namespace SupaLidlGame.Items.Weapons;
public abstract partial class Ranged : Weapon
{ {
public abstract partial class Ranged : Weapon [Export]
public float AngleDeviation { get; set; }
[Export]
public float ChargeTime { get; set; }
[Export]
public State.Weapon.WeaponStateMachine StateMachine { get; set; }
public override bool IsUsing => StateMachine.CurrentState
is State.Weapon.RangedFireState;
public bool IsChargeable => ChargeTime > 0;
public bool IsCharging { get; protected set; }
public override void Use()
{ {
[Export] StateMachine.Use();
public float AngleDeviation { get; set; } base.Use();
[Export]
public float ChargeTime { get; set; }
[Export]
public State.Weapon.WeaponStateMachine StateMachine { get; set; }
public override bool IsUsing => StateMachine.CurrentState
is State.Weapon.RangedFireState;
public bool IsChargeable => ChargeTime > 0;
public bool IsCharging { get; protected set; }
public override void Use()
{
StateMachine.Use();
base.Use();
}
public override void Deuse()
{
StateMachine.Deuse();
base.Deuse();
}
public override void _Process(double delta)
{
StateMachine.Process(delta);
base._Process(delta);
}
public abstract void Attack();
} }
public override void Deuse()
{
StateMachine.Deuse();
base.Deuse();
}
public override void _Process(double delta)
{
StateMachine.Process(delta);
base._Process(delta);
}
public abstract void Attack();
} }

View File

@ -4,234 +4,233 @@ using SupaLidlGame.Characters;
using SupaLidlGame.Extensions; using SupaLidlGame.Extensions;
using SupaLidlGame.State.Weapon; using SupaLidlGame.State.Weapon;
namespace SupaLidlGame.Items.Weapons namespace SupaLidlGame.Items.Weapons;
public partial class Sword : Weapon, IParryable
{ {
public partial class Sword : Weapon, IParryable public bool IsAttacking { get; protected set; }
public override bool IsUsing => StateMachine.CurrentState
is SwordAttackState;
[Export]
public Hitbox Hitbox { get; set; }
[Export]
public AnimationPlayer AnimationPlayer { get; set; }
[Export]
public AnimationTree AnimationTree { get; set; }
/// <summary>
/// The time frame in seconds for which the weapon will deal damage.
/// </summary>
/// <remarks>
/// The value of <c>AttackTime</c> should be less than the
/// value of <c>UseTime</c>
/// </remarks>
[Export]
public double AttackTime { get; set; } = 0;
[Export]
public double AttackAnimationDuration { get; set; }
[Export]
public CpuParticles2D ParryParticles { get; set; }
[Export]
public double NPCAnticipateTime { get; set; }
[Export]
public WeaponStateMachine StateMachine { get; set; }
[Export]
public Node2D Anchor { get; set; }
public override bool IsParryable { get; protected set; }
public ulong ParryTimeOrigin { get; protected set; }
private Tween _currentTween;
private AnimationNodeStateMachinePlayback _playback;
public override void Equip(Character character)
{ {
public bool IsAttacking { get; protected set; } base.Equip(character);
Hitbox.Faction = character.Faction; // character is null before base
}
public override bool IsUsing => StateMachine.CurrentState public override void Unequip(Character character)
is SwordAttackState; {
base.Unequip(character);
}
[Export] public override void Use()
public Hitbox Hitbox { get; set; } {
// we can't use if we're still using the weapon
[Export] if (RemainingUseTime > 0)
public AnimationPlayer AnimationPlayer { get; set; }
[Export]
public AnimationTree AnimationTree { get; set; }
/// <summary>
/// The time frame in seconds for which the weapon will deal damage.
/// </summary>
/// <remarks>
/// The value of <c>AttackTime</c> should be less than the
/// value of <c>UseTime</c>
/// </remarks>
[Export]
public double AttackTime { get; set; } = 0;
[Export]
public double AttackAnimationDuration { get; set; }
[Export]
public CpuParticles2D ParryParticles { get; set; }
[Export]
public double NPCAnticipateTime { get; set; }
[Export]
public WeaponStateMachine StateMachine { get; set; }
[Export]
public Node2D Anchor { get; set; }
public override bool IsParryable { get; protected set; }
public ulong ParryTimeOrigin { get; protected set; }
private Tween _currentTween;
private AnimationNodeStateMachinePlayback _playback;
public override void Equip(Character character)
{ {
base.Equip(character); //return;
Hitbox.Faction = character.Faction; // character is null before base
} }
public override void Unequip(Character character) StateMachine.Use();
/*
// reset state of the weapon
IsParried = false;
IsParryable = true;
ParryTimeOrigin = Time.GetTicksMsec();
_playback.Travel("use");
*/
// play animation depending on rotation of weapon
/*
string anim = "use";
if (GetNode<Node2D>("Anchor").Rotation > Mathf.DegToRad(50))
{ {
base.Unequip(character); anim = "use2";
} }
public override void Use() if (Character is NPC)
{ {
// we can't use if we're still using the weapon // NPCs have a slower attack
if (RemainingUseTime > 0) anim += "-npc";
}
AnimationPlayer.Play(anim);
*/
base.Use();
}
public void EnableParry()
{
IsParried = false;
IsParryable = true;
ParryTimeOrigin = Time.GetTicksMsec();
GD.Print(Character.Name);
}
public void DisableParry()
{
IsParryable = false;
}
public override void Deuse()
{
//AnimationPlayer.Stop();
Deattack();
base.Deuse();
}
public void Attack()
{
//RemainingAttackTime = AttackTime;
IsAttacking = true;
Hitbox.IsDisabled = false;
}
public void Deattack()
{
IsAttacking = false;
DisableParry();
Hitbox.IsDisabled = true;
ProcessHits();
Hitbox.ResetIgnoreList();
AnimationPlayer.SpeedScale = 1;
}
public override void _Ready()
{
Hitbox.Damage = Damage;
_playback = (AnimationNodeStateMachinePlayback)AnimationTree
.Get("parameters/playback");
}
public override void _Process(double delta)
{
StateMachine.Process(delta);
base._Process(delta);
}
public void ProcessHits()
{
if (IsParried)
{
return;
}
foreach (BoundingBox box in Hitbox.Hits)
{
GD.Print("processing hit");
if (box is Hurtbox hurtbox)
{ {
//return; hurtbox.InflictDamage(Damage, Character, Knockback);
} }
}
}
StateMachine.Use(); public void AttemptParry(Weapon otherWeapon)
{
/* //if (IsParryable && otherWeapon.IsParryable)
// reset state of the weapon if (otherWeapon.IsParryable &&
IsParried = false; otherWeapon is IParryable otherParryable)
IsParryable = true; {
ParryTimeOrigin = Time.GetTicksMsec(); ParryParticles.Emitting = true;
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin)
_playback.Travel("use");
*/
// play animation depending on rotation of weapon
/*
string anim = "use";
if (GetNode<Node2D>("Anchor").Rotation > Mathf.DegToRad(50))
{ {
anim = "use2"; // our character was parried
} }
else
if (Character is NPC)
{ {
// NPCs have a slower attack otherParryable.Stun();
anim += "-npc";
} }
}
//this.GetAncestor<TileMap>().AddChild(instance);
}
AnimationPlayer.Play(anim); public void Stun()
*/ {
IsParried = true;
AnimationPlayer.SpeedScale = 0.25f;
Character.Stun(1.5f);
GetNode<AudioStreamPlayer2D>("ParrySound").OnWorld().Play();
}
base.Use(); public override void _on_hitbox_hit(BoundingBox box)
{
if (IsParried)
{
return;
} }
public void EnableParry() if (box is Hitbox hb)
{ {
IsParried = false; Weapon w = hb.GetAncestor<Weapon>();
IsParryable = true; if (w is not null)
ParryTimeOrigin = Time.GetTicksMsec();
GD.Print(Character.Name);
}
public void DisableParry()
{
IsParryable = false;
}
public override void Deuse()
{
//AnimationPlayer.Stop();
Deattack();
base.Deuse();
}
public void Attack()
{
//RemainingAttackTime = AttackTime;
IsAttacking = true;
Hitbox.IsDisabled = false;
}
public void Deattack()
{
IsAttacking = false;
DisableParry();
Hitbox.IsDisabled = true;
ProcessHits();
Hitbox.ResetIgnoreList();
AnimationPlayer.SpeedScale = 1;
}
public override void _Ready()
{
Hitbox.Damage = Damage;
_playback = (AnimationNodeStateMachinePlayback)AnimationTree
.Get("parameters/playback");
}
public override void _Process(double delta)
{
StateMachine.Process(delta);
base._Process(delta);
}
public void ProcessHits()
{
if (IsParried)
{ {
return; AttemptParry(w);
}
foreach (BoundingBox box in Hitbox.Hits)
{
GD.Print("processing hit");
if (box is Hurtbox hurtbox)
{
hurtbox.InflictDamage(Damage, Character, Knockback);
}
} }
} }
public void AttemptParry(Weapon otherWeapon) if (box is Hurtbox hurt)
{ {
//if (IsParryable && otherWeapon.IsParryable) if (hurt.GetParent() is Character c)
if (otherWeapon.IsParryable &&
otherWeapon is IParryable otherParryable)
{ {
ParryParticles.Emitting = true; var item = c.Inventory.SelectedItem;
if (ParryTimeOrigin < otherParryable.ParryTimeOrigin) if (item is Weapon w)
{
// our character was parried
}
else
{
otherParryable.Stun();
}
}
//this.GetAncestor<TileMap>().AddChild(instance);
}
public void Stun()
{
IsParried = true;
AnimationPlayer.SpeedScale = 0.25f;
Character.Stun(1.5f);
GetNode<AudioStreamPlayer2D>("ParrySound").OnWorld().Play();
}
public override void _on_hitbox_hit(BoundingBox box)
{
if (IsParried)
{
return;
}
if (box is Hitbox hb)
{
Weapon w = hb.GetAncestor<Weapon>();
if (w is not null)
{ {
AttemptParry(w); AttemptParry(w);
} }
} }
if (box is Hurtbox hurt)
{
if (hurt.GetParent() is Character c)
{
var item = c.Inventory.SelectedItem;
if (item is Weapon w)
{
AttemptParry(w);
}
}
}
}
protected void SetAnimationCondition(string condition, bool value)
{
AnimationTree.Set("parameters/conditions/" + condition, value);
} }
} }
protected void SetAnimationCondition(string condition, bool value)
{
AnimationTree.Set("parameters/conditions/" + condition, value);
}
} }

View File

@ -3,7 +3,6 @@
[ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"] [ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"]
[ext_resource type="PackedScene" uid="uid://bxtpv6jqodj4v" path="res://Scenes/Maps/Hills.tscn" id="2_juio7"] [ext_resource type="PackedScene" uid="uid://bxtpv6jqodj4v" path="res://Scenes/Maps/Hills.tscn" id="2_juio7"]
[node name="World" type="Node2D" node_paths=PackedStringArray("CurrentPlayer")] [node name="World" type="Node2D"]
script = ExtResource("1_1k6ew") script = ExtResource("1_1k6ew")
StartingArea = ExtResource("2_juio7") StartingArea = ExtResource("2_juio7")
CurrentPlayer = NodePath("")

View File

@ -1,48 +1,47 @@
using Godot; using Godot;
using System; using System;
namespace SupaLidlGame.Scenes namespace SupaLidlGame.Scenes;
public partial class Map : TileMap
{ {
public partial class Map : TileMap [Export]
public Node2D Entities { get; set; }
[Export]
public Node2D Areas { get; set; }
[Export]
public Node2D Spawners { get; set; }
[Export]
public Vector2 CameraLowerBound { get; set; }
[Export]
public Vector2 CameraUpperBound { get; set; }
private bool _active;
public bool Active
{ {
[Export] get
public Node2D Entities { get; set; }
[Export]
public Node2D Areas { get; set; }
[Export]
public Node2D Spawners { get; set; }
[Export]
public Vector2 CameraLowerBound { get; set; }
[Export]
public Vector2 CameraUpperBound { get; set; }
private bool _active;
public bool Active
{ {
get return _active;
{
return _active;
}
set
{
_active = Visible = value;
SetProcess(value);
}
} }
set
public override void _Ready()
{ {
Active = true; _active = Visible = value;
} SetProcess(value);
public override void _Process(double delta)
{
base._Process(delta);
} }
} }
public override void _Ready()
{
Active = true;
}
public override void _Process(double delta)
{
base._Process(delta);
}
} }

View File

@ -1,70 +1,69 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public abstract partial class CharacterState : Node, IState<CharacterState>
{ {
public abstract partial class CharacterState : Node, IState<CharacterState> [Export]
public Characters.Character Character { get; set; }
public virtual IState<CharacterState> Enter(IState<CharacterState> prev) => null;
public virtual void Exit(IState<CharacterState> next)
{ {
[Export]
public Characters.Character Character { get; set; }
public virtual IState<CharacterState> Enter(IState<CharacterState> prev) => null;
public virtual void Exit(IState<CharacterState> next)
{
}
public virtual CharacterState Process(double delta)
{
if (Character.StunTime > 0)
{
Character.StunTime -= delta;
}
var item = Character.Inventory.SelectedItem;
var offhand = Character.Inventory.OffhandItem;
// angle towards item use angle or offhand use angle if not used
bool targetTowards(Items.Item item)
{
if (item is Items.Weapon weapon)
{
if (weapon.IsUsing)
{
Character.Target = weapon.UseDirection;
return true;
}
}
return false;
}
var _ = targetTowards(item) || targetTowards(offhand);
return null;
}
public virtual CharacterState PhysicsProcess(double delta)
{
Character.Velocity = Character.NetImpulse;
if (Character.NetImpulse.LengthSquared() < Mathf.Pow(Character.Speed, 2))
{
Character.Velocity += Character.Direction.Normalized()
* Character.Speed;
// we should only modify velocity that is in the player's control
Character.ModifyVelocity();
}
Character.NetImpulse = Character.NetImpulse.MoveToward(
Vector2.Zero,
(float)delta * Character.Speed * Character.Friction);
Character.MoveAndSlide();
return null;
}
public virtual CharacterState Input(InputEvent @event) => null;
} }
public virtual CharacterState Process(double delta)
{
if (Character.StunTime > 0)
{
Character.StunTime -= delta;
}
var item = Character.Inventory.SelectedItem;
var offhand = Character.Inventory.OffhandItem;
// angle towards item use angle or offhand use angle if not used
bool targetTowards(Items.Item item)
{
if (item is Items.Weapon weapon)
{
if (weapon.IsUsing)
{
Character.Target = weapon.UseDirection;
return true;
}
}
return false;
}
var _ = targetTowards(item) || targetTowards(offhand);
return null;
}
public virtual CharacterState PhysicsProcess(double delta)
{
Character.Velocity = Character.NetImpulse;
if (Character.NetImpulse.LengthSquared() < Mathf.Pow(Character.Speed, 2))
{
Character.Velocity += Character.Direction.Normalized()
* Character.Speed;
// we should only modify velocity that is in the player's control
Character.ModifyVelocity();
}
Character.NetImpulse = Character.NetImpulse.MoveToward(
Vector2.Zero,
(float)delta * Character.Speed * Character.Friction);
Character.MoveAndSlide();
return null;
}
public virtual CharacterState Input(InputEvent @event) => null;
} }

View File

@ -1,40 +1,39 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class CharacterStateMachine : StateMachine<CharacterState>
{ {
public partial class CharacterStateMachine : StateMachine<CharacterState> [Export]
public override CharacterState InitialState { get; set; }
[Export]
public Characters.Character Character { get; set; }
public void Process(double delta)
{ {
[Export] var state = CurrentState.Process(delta);
public override CharacterState InitialState { get; set; } if (state is CharacterState)
[Export]
public Characters.Character Character { get; set; }
public void Process(double delta)
{ {
var state = CurrentState.Process(delta); ChangeState(state);
if (state is CharacterState)
{
ChangeState(state);
}
} }
}
public void PhysicsProcess(double delta) public void PhysicsProcess(double delta)
{
var state = CurrentState.PhysicsProcess(delta);
if (state is CharacterState)
{ {
var state = CurrentState.PhysicsProcess(delta); ChangeState(state);
if (state is CharacterState)
{
ChangeState(state);
}
} }
}
public void Input(InputEvent @event) public void Input(InputEvent @event)
{
var state = CurrentState.Input(@event);
if (state is CharacterState)
{ {
var state = CurrentState.Input(@event); ChangeState(state);
if (state is CharacterState)
{
ChangeState(state);
}
} }
} }
} }

View File

@ -1,26 +1,25 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class NPCIdleState : NPCState
{ {
public partial class NPCIdleState : NPCState [Export]
public CharacterState MoveState { get; set; }
public override CharacterState Process(double delta)
{ {
[Export] base.Process(delta);
public CharacterState MoveState { get; set; } if (Character.Direction.LengthSquared() > 0)
public override CharacterState Process(double delta)
{ {
base.Process(delta); return MoveState;
if (Character.Direction.LengthSquared() > 0)
{
return MoveState;
}
return null;
} }
return null;
}
public override IState<CharacterState> Enter(IState<CharacterState> previousState) public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
Character.Sprite.Play("idle"); Character.Sprite.Play("idle");
return base.Enter(previousState); return base.Enter(previousState);
}
} }
} }

View File

@ -1,26 +1,25 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class NPCMoveState : NPCState
{ {
public partial class NPCMoveState : NPCState [Export]
public CharacterState IdleState { get; set; }
public override CharacterState Process(double delta)
{ {
[Export] base.Process(delta);
public CharacterState IdleState { get; set; } if (Character.Direction.LengthSquared() == 0)
public override CharacterState Process(double delta)
{ {
base.Process(delta); return IdleState;
if (Character.Direction.LengthSquared() == 0)
{
return IdleState;
}
return null;
} }
return null;
}
public override IState<CharacterState> Enter(IState<CharacterState> prev) public override IState<CharacterState> Enter(IState<CharacterState> prev)
{ {
Character.Sprite.Play("move"); Character.Sprite.Play("move");
return base.Enter(prev); return base.Enter(prev);
}
} }
} }

View File

@ -1,13 +1,12 @@
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
{
public abstract partial class NPCState : CharacterState
{
protected Characters.NPC _npc => Character as Characters.NPC;
public override CharacterState Process(double delta) public abstract partial class NPCState : CharacterState
{ {
_npc.ThinkProcess(delta); protected Characters.NPC _npc => Character as Characters.NPC;
return base.Process(delta);
} public override CharacterState Process(double delta)
{
_npc.ThinkProcess(delta);
return base.Process(delta);
} }
} }

View File

@ -1,38 +1,37 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class PlayerIdleState : PlayerState
{ {
public partial class PlayerIdleState : PlayerState [Export]
public CharacterState MoveState { get; set; }
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
[Export] GD.Print("Entered idle state");
public CharacterState MoveState { get; set; } if (previousState is not PlayerMoveState)
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
GD.Print("Entered idle state"); if (Character.Direction.LengthSquared() > 0.01f)
if (previousState is not PlayerMoveState)
{
if (Character.Direction.LengthSquared() > 0.01f)
{
// other states like attacking or rolling can just delegate
// the work of checking if the player is moving to this idle
// state, so they do not have to manually check if the player
// wants to move to switch to the move state.
return MoveState;
}
}
_player.Animation = "idle";
return base.Enter(previousState);
}
public override CharacterState Process(double delta)
{
base.Process(delta);
if (Character.Direction.LengthSquared() > 0)
{ {
// other states like attacking or rolling can just delegate
// the work of checking if the player is moving to this idle
// state, so they do not have to manually check if the player
// wants to move to switch to the move state.
return MoveState; return MoveState;
} }
return null;
} }
_player.Animation = "idle";
return base.Enter(previousState);
}
public override CharacterState Process(double delta)
{
base.Process(delta);
if (Character.Direction.LengthSquared() > 0)
{
return MoveState;
}
return null;
} }
} }

View File

@ -1,46 +1,45 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class PlayerMoveState : PlayerState
{ {
public partial class PlayerMoveState : PlayerState [Export]
public PlayerRollState RollState { get; set; }
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
[Export] Godot.GD.Print("Started moving");
public PlayerRollState RollState { get; set; } _player.Animation = "move";
return base.Enter(previousState);
}
public override IState<CharacterState> Enter(IState<CharacterState> previousState) public override CharacterState Process(double delta)
{
base.Process(delta);
if (Character.Direction.LengthSquared() == 0)
{ {
Godot.GD.Print("Started moving"); return IdleState;
_player.Animation = "move";
return base.Enter(previousState);
} }
return null;
}
public override CharacterState Process(double delta) public override CharacterState Input(InputEvent @event)
{
if (@event.IsActionPressed("roll"))
{ {
base.Process(delta); if (Character.Inventory.SelectedItem is Items.Weapon weapon)
if (Character.Direction.LengthSquared() == 0)
{ {
return IdleState; if (!weapon.IsUsing)
}
return null;
}
public override CharacterState Input(InputEvent @event)
{
if (@event.IsActionPressed("roll"))
{
if (Character.Inventory.SelectedItem is Items.Weapon weapon)
{
if (!weapon.IsUsing)
{
return RollState;
}
}
else
{ {
return RollState; return RollState;
} }
} }
return base.Input(@event); else
{
return RollState;
}
} }
return base.Input(@event);
} }
} }

View File

@ -1,39 +1,38 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public partial class PlayerRollState : PlayerState
{ {
public partial class PlayerRollState : PlayerState private double _timeLeftToRoll = 0;
private Vector2 _rollDirection = Vector2.Zero;
public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
private double _timeLeftToRoll = 0; _timeLeftToRoll = 0.5;
// roll the direction we were previously moving in
_rollDirection = Character.Direction;
Character.Target = Character.Direction;
return base.Enter(previousState);
}
private Vector2 _rollDirection = Vector2.Zero; public override void Exit(IState<CharacterState> nextState)
{
// we want to reset our state variables in case we are forced out of
// this state (e.g. from death)
_timeLeftToRoll = 0;
_rollDirection = Character.Direction;
base.Exit(nextState);
}
public override IState<CharacterState> Enter(IState<CharacterState> previousState) public override CharacterState Process(double delta)
{
Character.Direction = _rollDirection;
if ((_timeLeftToRoll -= delta) <= 0)
{ {
_timeLeftToRoll = 0.5; return IdleState;
// roll the direction we were previously moving in
_rollDirection = Character.Direction;
Character.Target = Character.Direction;
return base.Enter(previousState);
}
public override void Exit(IState<CharacterState> nextState)
{
// we want to reset our state variables in case we are forced out of
// this state (e.g. from death)
_timeLeftToRoll = 0;
_rollDirection = Character.Direction;
base.Exit(nextState);
}
public override CharacterState Process(double delta)
{
Character.Direction = _rollDirection;
if ((_timeLeftToRoll -= delta) <= 0)
{
return IdleState;
}
return null;
} }
return null;
} }
} }

View File

@ -1,72 +1,71 @@
using Godot; using Godot;
using SupaLidlGame.Characters; using SupaLidlGame.Characters;
namespace SupaLidlGame.State.Character namespace SupaLidlGame.State.Character;
public abstract partial class PlayerState : CharacterState
{ {
public abstract partial class PlayerState : CharacterState protected Player _player => Character as Player;
[Export]
public PlayerIdleState IdleState { get; set; }
public override CharacterState Input(InputEvent @event)
{ {
protected Player _player => Character as Player; var inventory = Character.Inventory;
[Export] if (this is PlayerIdleState or PlayerMoveState &&
public PlayerIdleState IdleState { get; set; } !_player.Inventory.IsUsingItem)
public override CharacterState Input(InputEvent @event)
{ {
var inventory = Character.Inventory; if (@event.IsActionPressed("equip_1"))
if (this is PlayerIdleState or PlayerMoveState &&
!_player.Inventory.IsUsingItem)
{ {
if (@event.IsActionPressed("equip_1")) inventory.SelectedItem = inventory.GetItemByMap("equip_1");
{
inventory.SelectedItem = inventory.GetItemByMap("equip_1");
}
} }
return base.Input(@event);
} }
public override CharacterState Process(double delta) return base.Input(@event);
}
public override CharacterState Process(double delta)
{
Character.Direction = Godot.Input.GetVector("ui_left", "ui_right",
"ui_up", "ui_down");
Character.LookTowardsDirection();
Vector2 mousePos = Character.GetGlobalMousePosition();
Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos);
bool targetTowards(Items.Item item)
{ {
Character.Direction = Godot.Input.GetVector("ui_left", "ui_right", if (Character.Inventory.SelectedItem is Items.Weapon weapon)
"ui_up", "ui_down");
Character.LookTowardsDirection();
Vector2 mousePos = Character.GetGlobalMousePosition();
Vector2 dirToMouse = Character.GlobalPosition.DirectionTo(mousePos);
bool targetTowards(Items.Item item)
{ {
if (Character.Inventory.SelectedItem is Items.Weapon weapon) if (!weapon.IsUsing)
{ {
if (!weapon.IsUsing) var isPressed = Godot.Input.IsActionPressed("attack1");
var ret = false;
if (!weapon.ShouldHideIdle || isPressed)
{ {
var isPressed = Godot.Input.IsActionPressed("attack1"); Character.Target = dirToMouse;
var ret = false; ret = true;
if (!weapon.ShouldHideIdle || isPressed)
{
Character.Target = dirToMouse;
ret = true;
}
if (isPressed)
{
Character.UseCurrentItem();
}
return ret;
} }
if (isPressed)
{
Character.UseCurrentItem();
}
return ret;
} }
return false;
} }
return false;
var item = Character.Inventory.SelectedItem;
var offhand = Character.Inventory.OffhandItem;
var _ = targetTowards(item) || targetTowards(offhand);
return base.Process(delta);
} }
var item = Character.Inventory.SelectedItem;
var offhand = Character.Inventory.OffhandItem;
var _ = targetTowards(item) || targetTowards(offhand);
return base.Process(delta);
} }
} }

View File

@ -1,23 +1,22 @@
namespace SupaLidlGame.State namespace SupaLidlGame.State;
public interface IState<T> where T : IState<T>
{ {
public interface IState<T> where T : IState<T> /// <summary>
{ /// Called when this state is entered
/// <summary> /// </summary>
/// Called when this state is entered /// <remarks>
/// </summary> /// This returns a <c>IState</c> in case a state is being
/// <remarks> /// transitioned to but wants to transition to another state. For
/// This returns a <c>IState</c> in case a state is being /// example, an attack state can return to an idle state, but that idle
/// transitioned to but wants to transition to another state. For /// state can override it to the move state immediately when necessary.
/// example, an attack state can return to an idle state, but that idle /// </remarks>
/// state can override it to the move state immediately when necessary. public IState<T> Enter(IState<T> previousState);
/// </remarks>
public IState<T> Enter(IState<T> previousState);
/// <summary> /// <summary>
/// Called when the <c>Character</c> exits this <c>CharacterState</c>. /// Called when the <c>Character</c> exits this <c>CharacterState</c>.
/// </summary> /// </summary>
public void Exit(IState<T> nextState); public void Exit(IState<T> nextState);
public IState<T> Process(double delta) => null; public IState<T> Process(double delta) => null;
}
} }

View File

@ -1,41 +1,40 @@
using Godot; using Godot;
namespace SupaLidlGame.State namespace SupaLidlGame.State;
public abstract partial class StateMachine<T> : Node where T : IState<T>
{ {
public abstract partial class StateMachine<T> : Node where T : IState<T> public T CurrentState { get; protected set; }
public abstract T InitialState { get; set; }
public override void _Ready()
{ {
public T CurrentState { get; protected set; } ChangeState(InitialState);
}
public abstract T InitialState { get; set; } public virtual bool ChangeState(T nextState, bool isProxied = false)
{
public override void _Ready() if (nextState is null)
{ {
ChangeState(InitialState); return false;
} }
public virtual bool ChangeState(T nextState, bool isProxied = false) if (CurrentState is not null)
{ {
if (nextState is null) CurrentState.Exit(nextState);
{
return false;
}
if (CurrentState is not null)
{
CurrentState.Exit(nextState);
}
CurrentState = nextState;
// if the next state decides it should enter a different state,
// then we enter that different state instead
var nextNextState = nextState.Enter(CurrentState);
if (nextNextState is T t)
{
return ChangeState(t, true);
}
return true;
} }
CurrentState = nextState;
// if the next state decides it should enter a different state,
// then we enter that different state instead
var nextNextState = nextState.Enter(CurrentState);
if (nextNextState is T t)
{
return ChangeState(t, true);
}
return true;
} }
} }

View File

@ -1,38 +1,37 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class RangedFireState : WeaponState
{ {
public partial class RangedFireState : WeaponState [Export]
public Items.Weapons.Ranged Weapon { get; set; }
[Export]
public RangedIdleState IdleState { get; set; }
private double _timeLeft = 0;
public override IState<WeaponState> Enter(IState<WeaponState> prev)
{ {
[Export] //_timeLeft
public Items.Weapons.Ranged Weapon { get; set; } _timeLeft = Weapon.UseTime;
Weapon.Attack();
Weapon.UseDirection = Weapon.Character.Target;
return null;
}
[Export] public override WeaponState Process(double delta)
public RangedIdleState IdleState { get; set; } {
if ((_timeLeft -= delta) <= 0)
private double _timeLeft = 0;
public override IState<WeaponState> Enter(IState<WeaponState> prev)
{ {
//_timeLeft return IdleState;
_timeLeft = Weapon.UseTime;
Weapon.Attack();
Weapon.UseDirection = Weapon.Character.Target;
return null;
} }
return null;
}
public override WeaponState Process(double delta) public override void Exit(IState<WeaponState> nextState)
{ {
if ((_timeLeft -= delta) <= 0)
{
return IdleState;
}
return null;
}
public override void Exit(IState<WeaponState> nextState)
{
}
} }
} }

View File

@ -1,29 +1,28 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class RangedIdleState : WeaponState
{ {
public partial class RangedIdleState : WeaponState [Export]
public RangedFireState FireState { get; set; }
[Export]
public Items.Weapons.Ranged Weapon { get; set; }
public override IState<WeaponState> Enter(IState<WeaponState> prev)
{ {
[Export] Weapon.Visible = !Weapon.ShouldHideIdle;
public RangedFireState FireState { get; set; } return null;
}
[Export] public override WeaponState Use()
public Items.Weapons.Ranged Weapon { get; set; } {
return FireState;
}
public override IState<WeaponState> Enter(IState<WeaponState> prev) public override void Exit(IState<WeaponState> nextState)
{ {
Weapon.Visible = !Weapon.ShouldHideIdle; Weapon.Visible = true;
return null;
}
public override WeaponState Use()
{
return FireState;
}
public override void Exit(IState<WeaponState> nextState)
{
Weapon.Visible = true;
}
} }
} }

View File

@ -1,46 +1,45 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class SwordAnticipateState : WeaponState
{ {
public partial class SwordAnticipateState : WeaponState [Export]
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
[Export]
public SwordAttackState AttackState { get; set; }
private double _anticipateTime;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
[Export] Sword.EnableParry();
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
[Export] if (Sword.Character is SupaLidlGame.Characters.Player)
public SwordAttackState AttackState { get; set; }
private double _anticipateTime;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
Sword.EnableParry(); return AttackState;
if (Sword.Character is SupaLidlGame.Characters.Player)
{
return AttackState;
}
if (Sword.Anchor.Rotation > Mathf.DegToRad(50))
{
Sword.AnimationPlayer.Play("anticipate_alternate");
}
else
{
Sword.AnimationPlayer.Play("anticipate");
}
_anticipateTime = Sword.NPCAnticipateTime;
return null;
} }
public override WeaponState Process(double delta) if (Sword.Anchor.Rotation > Mathf.DegToRad(50))
{ {
// go into attack state if anticipation time is delta Sword.AnimationPlayer.Play("anticipate_alternate");
if ((_anticipateTime -= delta) <= 0)
{
return AttackState;
}
return null;
} }
else
{
Sword.AnimationPlayer.Play("anticipate");
}
_anticipateTime = Sword.NPCAnticipateTime;
return null;
}
public override WeaponState Process(double delta)
{
// go into attack state if anticipation time is delta
if ((_anticipateTime -= delta) <= 0)
{
return AttackState;
}
return null;
} }
} }

View File

@ -1,89 +1,88 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class SwordAttackState : WeaponState
{ {
public partial class SwordAttackState : WeaponState [Export]
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
[Export]
public SwordAnticipateState AnticipateState { get; set; }
[Export]
public SwordIdleState IdleState { get; set; }
private double _attackDuration = 0;
private double _useDuration = 0;
private double _attackAnimDuration = 0;
private bool _isAlternate = false;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
[Export] //Sword.AnimationPlayer.Stop();
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; } Sword.Attack();
[Export] if (_isAlternate)
public SwordAnticipateState AnticipateState { get; set; }
[Export]
public SwordIdleState IdleState { get; set; }
private double _attackDuration = 0;
private double _useDuration = 0;
private double _attackAnimDuration = 0;
private bool _isAlternate = false;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
//Sword.AnimationPlayer.Stop(); Sword.AnimationPlayer.Play("attack_alternate");
Sword.Attack(); }
else
if (_isAlternate) {
{ Sword.AnimationPlayer.Play("attack");
Sword.AnimationPlayer.Play("attack_alternate");
}
else
{
Sword.AnimationPlayer.Play("attack");
}
_attackDuration = Sword.AttackTime;
_useDuration = Sword.UseTime;
_attackAnimDuration = Sword.AttackAnimationDuration;
Sword.Visible = true;
Sword.UseDirection = Sword.Character.Target;
return null;
} }
public override void Exit(IState<WeaponState> nextState) _attackDuration = Sword.AttackTime;
_useDuration = Sword.UseTime;
_attackAnimDuration = Sword.AttackAnimationDuration;
Sword.Visible = true;
Sword.UseDirection = Sword.Character.Target;
return null;
}
public override void Exit(IState<WeaponState> nextState)
{
Sword.Deattack();
}
public override WeaponState Use()
{
if (_useDuration <= 0)
{ {
Sword.Deattack(); // if we are still playing the current attack animation, we should alternate
if (_attackAnimDuration > 0)
{
_isAlternate = !_isAlternate;
}
return IdleState;
} }
public override WeaponState Use() return null;
{ }
if (_useDuration <= 0)
{
// if we are still playing the current attack animation, we should alternate
if (_attackAnimDuration > 0)
{
_isAlternate = !_isAlternate;
}
return IdleState;
}
return null; public override WeaponState Process(double delta)
{
if (_attackDuration > 0)
{
if ((_attackDuration -= delta) <= 0)
{
Sword.Deattack();
}
} }
public override WeaponState Process(double delta) if ((_attackAnimDuration -= delta) <= 0)
{ {
if (_attackDuration > 0) Sword.AnimationPlayer.Play("RESET");
{ _isAlternate = false;
if ((_attackDuration -= delta) <= 0) return IdleState;
{
Sword.Deattack();
}
}
if ((_attackAnimDuration -= delta) <= 0)
{
Sword.AnimationPlayer.Play("RESET");
_isAlternate = false;
return IdleState;
}
_useDuration -= delta;
return null;
} }
_useDuration -= delta;
return null;
} }
} }

View File

@ -1,52 +1,51 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class SwordIdleState : WeaponState
{ {
public partial class SwordIdleState : WeaponState [Export]
public SwordAnticipateState AnticipateState { get; set; }
[Export]
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
private double _attackCooldown;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
[Export] if (prevState is SwordAttackState)
public SwordAnticipateState AnticipateState { get; set; }
[Export]
public SupaLidlGame.Items.Weapons.Sword Sword { get; set; }
private double _attackCooldown;
public override WeaponState Enter(IState<WeaponState> prevState)
{ {
if (prevState is SwordAttackState) _attackCooldown = Sword.UseTime - Sword.AttackTime;
{
_attackCooldown = Sword.UseTime - Sword.AttackTime;
}
Sword.Visible = !Sword.ShouldHideIdle;
return null;
} }
public override void Exit(IState<WeaponState> nextState) Sword.Visible = !Sword.ShouldHideIdle;
{
Sword.Visible = true;
}
public override WeaponState Use() return null;
{ }
if (_attackCooldown <= 0)
{
return AnticipateState;
}
public override void Exit(IState<WeaponState> nextState)
{
Sword.Visible = true;
}
public override WeaponState Use()
{
if (_attackCooldown <= 0)
{
return AnticipateState; return AnticipateState;
} }
public override WeaponState Process(double delta) return AnticipateState;
{ }
if (_attackCooldown > 0)
{
_attackCooldown -= delta;
}
return null; public override WeaponState Process(double delta)
{
if (_attackCooldown > 0)
{
_attackCooldown -= delta;
} }
return null;
} }
} }

View File

@ -1,20 +1,19 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public abstract partial class WeaponState : Node, IState<WeaponState>
{ {
public abstract partial class WeaponState : Node, IState<WeaponState> public virtual WeaponState Use() => null;
public virtual WeaponState Deuse() => null;
public abstract IState<WeaponState> Enter(IState<WeaponState> previousState);
public virtual void Exit(IState<WeaponState> nextState)
{ {
public virtual WeaponState Use() => null;
public virtual WeaponState Deuse() => null;
public abstract IState<WeaponState> Enter(IState<WeaponState> previousState);
public virtual void Exit(IState<WeaponState> nextState)
{
}
public virtual IState<WeaponState> Process(double delta) => null;
} }
public virtual IState<WeaponState> Process(double delta) => null;
} }

View File

@ -1,37 +1,36 @@
using Godot; using Godot;
namespace SupaLidlGame.State.Weapon namespace SupaLidlGame.State.Weapon;
public partial class WeaponStateMachine : StateMachine<WeaponState>
{ {
public partial class WeaponStateMachine : StateMachine<WeaponState> [Export]
public override WeaponState InitialState { get; set; }
public void Use()
{ {
[Export] var state = CurrentState.Use();
public override WeaponState InitialState { get; set; } if (state is WeaponState)
public void Use()
{ {
var state = CurrentState.Use(); ChangeState(state);
if (state is WeaponState)
{
ChangeState(state);
}
} }
}
public void Deuse() public void Deuse()
{
var state = CurrentState.Deuse();
if (state is WeaponState)
{ {
var state = CurrentState.Deuse(); ChangeState(state);
if (state is WeaponState)
{
ChangeState(state);
}
} }
}
public void Process(double delta) public void Process(double delta)
{
var state = CurrentState.Process(delta);
if (state is WeaponState s)
{ {
var state = CurrentState.Process(delta); ChangeState(s);
if (state is WeaponState s)
{
ChangeState(s);
}
} }
} }
} }

View File

@ -2,57 +2,56 @@ using Godot;
using SupaLidlGame.Extensions; using SupaLidlGame.Extensions;
using System; using System;
namespace SupaLidlGame.Prototyping namespace SupaLidlGame.Prototyping;
public partial class ContextBasedSteering : Node2D
{ {
public partial class ContextBasedSteering : Node2D public float PreferredDistance { get; set; } = 256.0f;
Vector2 _direction;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{ {
public float PreferredDistance { get; set; } = 256.0f; GD.Print("Started ContextBasedSteering test");
Vector2 _direction; }
// Called when the node enters the scene tree for the first time. // Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Ready() public override void _Process(double delta)
{ {
GD.Print("Started ContextBasedSteering test"); _direction = GetDirection();
} QueueRedraw();
}
// Called every frame. 'delta' is the elapsed time since the previous frame. public Vector2 GetDirection()
public override void _Process(double delta) {
{ float directWeight;
_direction = GetDirection(); float strafeWeight;
QueueRedraw();
}
public Vector2 GetDirection() Vector2 towards = GetGlobalMousePosition() - GlobalPosition;
{ float dist = towards.Length();
float directWeight;
float strafeWeight;
Vector2 towards = GetGlobalMousePosition() - GlobalPosition; Vector2 directDir = towards.Normalized();
float dist = towards.Length(); Vector2 strafeDir = directDir.Clockwise90();
Vector2 directDir = towards.Normalized(); // weights approach 1
Vector2 strafeDir = directDir.Clockwise90(); // dy/dx = 1 - y
// y = 1 - e^(-x)
// weights approach 1 directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance));
// dy/dx = 1 - y strafeWeight = 1 - directWeight;
// y = 1 - e^(-x)
directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance)); /*
strafeWeight = 1 - directWeight; Vector2 midpoint = (strafeDir * strafeWeight)
.Midpoint(directDir * directWeight);
*/
Vector2 midpoint = (directDir * directWeight)
.Midpoint(strafeDir * strafeWeight);
/* return midpoint.Normalized();
Vector2 midpoint = (strafeDir * strafeWeight) }
.Midpoint(directDir * directWeight);
*/
Vector2 midpoint = (directDir * directWeight)
.Midpoint(strafeDir * strafeWeight);
return midpoint.Normalized(); public override void _Draw()
} {
DrawLine(Vector2.Zero, _direction * 256, Colors.Green);
public override void _Draw()
{
DrawLine(Vector2.Zero, _direction * 256, Colors.Green);
}
} }
} }

View File

@ -1,51 +1,50 @@
using Godot; using Godot;
using System; using System;
namespace SupaLidlGame.UI namespace SupaLidlGame.UI;
public partial class FloatingText : Node2D
{ {
public partial class FloatingText : Node2D private Label _label;
[Export]
public string Text { get; set; }
public override void _Ready()
{ {
private Label _label; _label = GetNode<Label>("Label");
_label.Text = Text;
[Export] Tween tween = GetTree().CreateTween()
public string Text { get; set; } .SetEase(Tween.EaseType.Out)
.SetTrans(Tween.TransitionType.Quint)
.SetParallel();
public override void _Ready() Random rng = new Random();
float randomFloat(float min, float max)
{ {
_label = GetNode<Label>("Label"); return (float)rng.NextDouble() * (max - min) + min;
_label.Text = Text;
Tween tween = GetTree().CreateTween()
.SetEase(Tween.EaseType.Out)
.SetTrans(Tween.TransitionType.Quint)
.SetParallel();
Random rng = new Random();
float randomFloat(float min, float max)
{
return (float)rng.NextDouble() * (max - min) + min;
}
GD.Print(GlobalPosition);
Position += new Vector2(randomFloat(-8, 8), 0);
var endPos = Position + new Vector2(0, randomFloat(-16, -8));
var endMod = new Color(1, 1, 1, 0);
var endScale = Scale * 0.5f;
tween.TweenProperty(this, "position", endPos, 0.5f);
tween.SetTrans(Tween.TransitionType.Linear);
tween.TweenProperty(this, "modulate", endMod, 0.5f).SetDelay(1.0f);
tween.TweenProperty(this, "scale", endScale, 0.5f).SetDelay(1.0f);
tween.TweenCallback(new Callable(this, nameof(OnTweenFinished)))
.SetDelay(2.5f);
base._Ready();
} }
public void OnTweenFinished() GD.Print(GlobalPosition);
{ Position += new Vector2(randomFloat(-8, 8), 0);
QueueFree(); var endPos = Position + new Vector2(0, randomFloat(-16, -8));
} var endMod = new Color(1, 1, 1, 0);
var endScale = Scale * 0.5f;
tween.TweenProperty(this, "position", endPos, 0.5f);
tween.SetTrans(Tween.TransitionType.Linear);
tween.TweenProperty(this, "modulate", endMod, 0.5f).SetDelay(1.0f);
tween.TweenProperty(this, "scale", endScale, 0.5f).SetDelay(1.0f);
tween.TweenCallback(new Callable(this, nameof(OnTweenFinished)))
.SetDelay(2.5f);
base._Ready();
}
public void OnTweenFinished()
{
QueueFree();
} }
} }

View File

@ -1,13 +1,12 @@
using Godot; using Godot;
namespace SupaLidlGame.Utils namespace SupaLidlGame.Utils;
public interface IFaction
{ {
public interface IFaction /// <summary>
{ /// The faction index that this entity belongs to.
/// <summary> /// </summary>
/// The faction index that this entity belongs to. [Export]
/// </summary> public ushort Faction { get; set; }
[Export]
public ushort Faction { get; set; }
}
} }

View File

@ -1,50 +1,49 @@
using Godot; using Godot;
using System; using System;
namespace SupaLidlGame.Utils namespace SupaLidlGame.Utils;
public partial class PlayerCamera : Camera2D
{ {
public partial class PlayerCamera : Camera2D protected float _intensity;
protected double _timeLeft;
public override void _Ready()
{ {
protected float _intensity;
protected double _timeLeft; }
public override void _Ready() public override void _Process(double delta)
{
if (_timeLeft > 0)
{ {
_timeLeft -= delta;
Offset = RandomOffset(_intensity);
}
else
{
Offset = Vector2.Zero;
} }
public override void _Process(double delta) if (_intensity > 0)
{ {
if (_timeLeft > 0) _intensity = Mathf.MoveToward(_intensity, 0.0f, (float)delta);
{
_timeLeft -= delta;
Offset = RandomOffset(_intensity);
}
else
{
Offset = Vector2.Zero;
}
if (_intensity > 0)
{
_intensity = Mathf.MoveToward(_intensity, 0.0f, (float)delta);
}
}
public void Shake(float intensity, double time)
{
_intensity = Mathf.Max(_intensity, intensity);
_timeLeft = Math.Max(_timeLeft, time);
}
private Vector2 RandomOffset(float intensity)
{
Vector2 ret = Vector2.Zero;
var rng = new RandomNumberGenerator();
ret.X = (rng.Randf() - 0.5f) * intensity;
ret.Y = (rng.Randf() - 0.5f) * intensity;
return ret;
} }
} }
public void Shake(float intensity, double time)
{
_intensity = Mathf.Max(_intensity, intensity);
_timeLeft = Math.Max(_timeLeft, time);
}
private Vector2 RandomOffset(float intensity)
{
Vector2 ret = Vector2.Zero;
var rng = new RandomNumberGenerator();
ret.X = (rng.Randf() - 0.5f) * intensity;
ret.Y = (rng.Randf() - 0.5f) * intensity;
return ret;
}
} }

View File

@ -2,58 +2,57 @@ using Godot;
using Godot.Collections; using Godot.Collections;
using SupaLidlGame.Extensions; using SupaLidlGame.Extensions;
namespace SupaLidlGame.Utils namespace SupaLidlGame.Utils;
public partial class Spawner : Node2D
{ {
public partial class Spawner : Node2D [Export]
public Area2D SpawnArea { get; set; }
[Export]
public PackedScene Character { get; set; }
[Export]
public double SpawnTime { get; set; }
protected double _timeLeftToSpawn = 0;
public override void _Ready()
{ {
[Export] }
public Area2D SpawnArea { get; set; }
[Export] public override void _Process(double delta)
public PackedScene Character { get; set; } {
if ((_timeLeftToSpawn -= delta) <= 0)
[Export]
public double SpawnTime { get; set; }
protected double _timeLeftToSpawn = 0;
public override void _Ready()
{ {
_timeLeftToSpawn = SpawnTime;
Spawn();
} }
}
public override void _Process(double delta) public void Spawn()
{
var coll = GetNode<CollisionShape2D>("Area2D/CollisionShape2D");
var rect = coll.Shape as RectangleShape2D;
if (rect is not null)
{ {
if ((_timeLeftToSpawn -= delta) <= 0) Vector2 size = rect.Size / 2;
{ Vector2 pos = GlobalPosition;
_timeLeftToSpawn = SpawnTime;
Spawn();
}
}
public void Spawn() double x1, x2, y1, y2;
{ x1 = pos.X - size.X;
var coll = GetNode<CollisionShape2D>("Area2D/CollisionShape2D"); x2 = pos.X + size.X;
var rect = coll.Shape as RectangleShape2D; y1 = pos.Y - size.Y;
if (rect is not null) y2 = pos.Y + size.Y;
{
Vector2 size = rect.Size / 2;
Vector2 pos = GlobalPosition;
double x1, x2, y1, y2; float randX = (float)GD.RandRange(x1, x2);
x1 = pos.X - size.X; float randY = (float)GD.RandRange(y1, y2);
x2 = pos.X + size.X;
y1 = pos.Y - size.Y;
y2 = pos.Y + size.Y;
float randX = (float)GD.RandRange(x1, x2); Vector2 randPos = new Vector2(randX, randY);
float randY = (float)GD.RandRange(y1, y2);
Vector2 randPos = new Vector2(randX, randY); var chr = Character.Instantiate<Characters.Character>();
chr.GlobalPosition = randPos;
var chr = Character.Instantiate<Characters.Character>(); this.GetAncestor<Scenes.Map>().Entities.AddChild(chr);
chr.GlobalPosition = randPos;
this.GetAncestor<Scenes.Map>().Entities.AddChild(chr);
}
} }
} }
} }

View File

@ -5,146 +5,145 @@ using SupaLidlGame.Extensions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace SupaLidlGame.Utils namespace SupaLidlGame.Utils;
public partial class World : Node2D
{ {
public partial class World : Node2D [Export]
public PackedScene StartingArea { get; set; }
[Export]
public Map CurrentMap { get; protected set; }
[Export]
public Player CurrentPlayer { get; set; }
private Dictionary<string, Map> _maps;
private string _currentConnector;
private string _currentMapResourcePath;
private const string PLAYER_PATH = "res://Characters/Player.tscn";
private PackedScene _playerScene;
public World()
{ {
[Export] _maps = new Dictionary<string, Map>();
public PackedScene StartingArea { get; set; } _playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
}
[Export] public override void _Ready()
public Map CurrentMap { get; protected set; } {
if (StartingArea is not null)
[Export]
public Player CurrentPlayer { get; set; }
private Dictionary<string, Map> _maps;
private string _currentConnector;
private string _currentMapResourcePath;
private const string PLAYER_PATH = "res://Characters/Player.tscn";
private PackedScene _playerScene;
public World()
{ {
_maps = new Dictionary<string, Map>(); LoadScene(StartingArea);
_playerScene = ResourceLoader.Load<PackedScene>(PLAYER_PATH);
} }
public override void _Ready() // spawn the player in
CreatePlayer();
base._Ready();
}
public void LoadScene(PackedScene scene)
{
GD.Print("Loading map " + scene.ResourcePath);
Map map;
if (_maps.ContainsKey(scene.ResourcePath))
{ {
if (StartingArea is not null) map = _maps[scene.ResourcePath];
{ }
LoadScene(StartingArea); else
} {
map = scene.Instantiate<Map>();
// spawn the player in _maps.Add(scene.ResourcePath, map);
CreatePlayer();
base._Ready();
} }
public void LoadScene(PackedScene scene) if (CurrentMap is not null)
{ {
GD.Print("Loading map " + scene.ResourcePath); CurrentMap.Entities.RemoveChild(CurrentPlayer);
RemoveChild(CurrentMap);
Map map; CurrentMap.Active = false;
if (_maps.ContainsKey(scene.ResourcePath))
{
map = _maps[scene.ResourcePath];
}
else
{
map = scene.Instantiate<Map>();
_maps.Add(scene.ResourcePath, map);
}
if (CurrentMap is not null)
{
CurrentMap.Entities.RemoveChild(CurrentPlayer);
RemoveChild(CurrentMap);
CurrentMap.Active = false;
}
AddChild(map);
InitTilemap(map);
CurrentMap = map;
CurrentMap.Active = true;
if (CurrentPlayer is not null)
{
CurrentMap.Entities.AddChild(CurrentPlayer);
}
} }
public void CreatePlayer() AddChild(map);
InitTilemap(map);
CurrentMap = map;
CurrentMap.Active = true;
if (CurrentPlayer is not null)
{ {
CurrentPlayer = _playerScene.Instantiate<Player>();
CurrentMap.Entities.AddChild(CurrentPlayer); CurrentMap.Entities.AddChild(CurrentPlayer);
} }
}
private void InitTilemap(Map map) public void CreatePlayer()
{
CurrentPlayer = _playerScene.Instantiate<Player>();
CurrentMap.Entities.AddChild(CurrentPlayer);
}
private void InitTilemap(Map map)
{
var children = map.Areas.GetChildren();
foreach (Node node in children)
{ {
var children = map.Areas.GetChildren(); if (node is BoundingBoxes.ConnectorBox connector)
foreach (Node node in children)
{ {
if (node is BoundingBoxes.ConnectorBox connector) // this reconnects the EventHandler if it is connected
{ connector.RequestedEnter -= _on_area_2d_requested_enter;
// this reconnects the EventHandler if it is connected connector.RequestedEnter += _on_area_2d_requested_enter;
connector.RequestedEnter -= _on_area_2d_requested_enter;
connector.RequestedEnter += _on_area_2d_requested_enter;
}
} }
} }
}
private void MovePlayerToConnector(string name)
{ private void MovePlayerToConnector(string name)
// find the first connector with the specified name {
var connector = CurrentMap.Areas.GetChildren().First((child) => // find the first connector with the specified name
{ var connector = CurrentMap.Areas.GetChildren().First((child) =>
if (child is BoundingBoxes.ConnectorBox connector) {
{ if (child is BoundingBoxes.ConnectorBox connector)
return connector.Identifier == name; {
} return connector.Identifier == name;
return false; }
}) as BoundingBoxes.ConnectorBox; return false;
}) as BoundingBoxes.ConnectorBox;
CurrentPlayer.GlobalPosition = connector.GlobalPosition;
} CurrentPlayer.GlobalPosition = connector.GlobalPosition;
}
public void MoveToArea(string path, string connector)
{ public void MoveToArea(string path, string connector)
_currentConnector = connector; {
if (path != _currentMapResourcePath) _currentConnector = connector;
{ if (path != _currentMapResourcePath)
var scene = ResourceLoader.Load<PackedScene>(path); {
LoadScene(scene); var scene = ResourceLoader.Load<PackedScene>(path);
_currentMapResourcePath = path; LoadScene(scene);
} _currentMapResourcePath = path;
}
// after finished loading, move our player to the connector
MovePlayerToConnector(connector); // after finished loading, move our player to the connector
} MovePlayerToConnector(connector);
}
public void _on_area_2d_requested_enter(
BoundingBoxes.ConnectorBox box, public void _on_area_2d_requested_enter(
Player player) BoundingBoxes.ConnectorBox box,
{ Player player)
GD.Print("Requesting to enter " + box.ToConnector); {
MoveToArea(box.ToArea, box.ToConnector); GD.Print("Requesting to enter " + box.ToConnector);
} MoveToArea(box.ToArea, box.ToConnector);
}
public void SaveGame()
{ public void SaveGame()
throw new System.NotImplementedException(); {
} throw new System.NotImplementedException();
}
public void LoadGame()
{ public void LoadGame()
throw new System.NotImplementedException(); {
} throw new System.NotImplementedException();
} }
} }