refactor file scoped namespace

pull/3/head
John Montagu, the 4th Earl of Sandvich 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 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 SupaLidlGame.Characters;
namespace SupaLidlGame.BoundingBoxes
namespace SupaLidlGame.BoundingBoxes;
public partial class ConnectorBox : Area2D
{
public partial class ConnectorBox : Area2D
[Signal]
public delegate void RequestedEnterEventHandler(
ConnectorBox box,
Player player);
[Export]
public string ToArea { get; set; }
[Export]
public string ToConnector { get; set; }
[Export]
public string Identifier { get; set; }
/// <summary>
/// Determines if the connector requires the user to interact to enter
/// the connector
/// </summary>
[Export]
public bool RequiresInteraction { get; set; } = false;
[Export]
public CollisionShape2D Collision { get; set; }
private Player _player = null;
public override void _Ready()
{
[Signal]
public delegate void RequestedEnterEventHandler(
ConnectorBox box,
Player player);
[Export]
public string ToArea { get; set; }
[Export]
public string ToConnector { get; set; }
[Export]
public string Identifier { get; set; }
/// <summary>
/// Determines if the connector requires the user to interact to enter
/// the connector
/// </summary>
[Export]
public bool RequiresInteraction { get; set; } = false;
[Export]
public CollisionShape2D Collision { get; set; }
private Player _player = null;
public override void _Ready()
if (Collision is null)
{
if (Collision is null)
{
throw new NullReferenceException("Collision not specified");
}
BodyEntered += (Node2D body) =>
{
if (body is Player player)
{
_player = player;
}
};
BodyExited += (Node2D body) =>
{
if (body is Player)
{
_player = null;
}
};
throw new NullReferenceException("Collision not specified");
}
public override void _Process(double delta)
BodyEntered += (Node2D body) =>
{
if (Input.IsActionJustReleased("interact"))
if (body is Player player)
{
if (_player is not null)
{
EmitSignal(SignalName.RequestedEnter, this, _player);
}
_player = player;
}
};
base._Process(delta);
BodyExited += (Node2D body) =>
{
if (body is Player)
{
_player = null;
}
};
}
public override void _Process(double delta)
{
if (Input.IsActionJustReleased("interact"))
{
if (_player is not null)
{
EmitSignal(SignalName.RequestedEnter, this, _player);
}
}
base._Process(delta);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 ulong ParryTimeOrigin { get; }
public void Stun();
}
public bool IsParryable { get; }
public bool IsParried { get; }
public ulong ParryTimeOrigin { get; }
public void Stun();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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>
/// <remarks>
/// This returns a <c>IState</c> in case a state is being
/// transitioned to but wants to transition to another state. For
/// example, an attack state can return to an idle state, but that idle
/// state can override it to the move state immediately when necessary.
/// </remarks>
public IState<T> Enter(IState<T> previousState);
/// <summary>
/// Called when this state is entered
/// </summary>
/// <remarks>
/// This returns a <c>IState</c> in case a state is being
/// transitioned to but wants to transition to another state. For
/// example, an attack state can return to an idle state, but that idle
/// state can override it to the move state immediately when necessary.
/// </remarks>
public IState<T> Enter(IState<T> previousState);
/// <summary>
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
/// </summary>
public void Exit(IState<T> nextState);
/// <summary>
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
/// </summary>
public void Exit(IState<T> nextState);
public IState<T> Process(double delta) => null;
}
public IState<T> Process(double delta) => null;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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