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