diff --git a/BoundingBoxes/Hurtbox.cs b/BoundingBoxes/Hurtbox.cs index 449b3a3..fd67c0b 100644 --- a/BoundingBoxes/Hurtbox.cs +++ b/BoundingBoxes/Hurtbox.cs @@ -11,6 +11,7 @@ public partial class Hurtbox : BoundingBox, IFaction float damage, Character inflictor, float knockback, + Items.Weapon weapon = null, Vector2 knockbackDir = default); /// @@ -39,6 +40,7 @@ public partial class Hurtbox : BoundingBox, IFaction float damage, Character inflictor, float knockback, + Items.Weapon weapon = default, Vector2 knockbackOrigin = default, Vector2 knockbackVector = default) { @@ -83,6 +85,7 @@ public partial class Hurtbox : BoundingBox, IFaction damage, inflictor, knockback, + weapon, knockbackDir); } } diff --git a/Characters/Character.cs b/Characters/Character.cs index 13a5fc7..2fec6b9 100644 --- a/Characters/Character.cs +++ b/Characters/Character.cs @@ -308,6 +308,7 @@ public partial class Character : CharacterBody2D, IFaction float damage, Character inflictor, float knockback, + Weapon weapon = null, Vector2 knockbackDir = default) { if (Health <= 0) @@ -352,11 +353,17 @@ public partial class Character : CharacterBody2D, IFaction Attacker = inflictor, OldHealth = oldHealth, NewHealth = Health, + Weapon = weapon, Damage = damage, }; EmitSignal(SignalName.Hurt, args); + if (inflictor is Player) + { + EmitPlayerHitSignal(args); + } + if (Health <= 0) { EmitSignal(SignalName.Death, args); @@ -366,6 +373,25 @@ public partial class Character : CharacterBody2D, IFaction } } + /// + /// Converts a HurtArgs to HitArgs if the attacker was a player and emits + /// the EventBus.PlayerHit signal. + /// + private void EmitPlayerHitSignal(Events.HurtArgs args) + { + var newArgs = new Events.HitArgs + { + OldHealth = args.OldHealth, + NewHealth = args.NewHealth, + Damage = args.Damage, + Weapon = args.Weapon, + Victim = this, + }; + + var bus = Events.EventBus.Instance; + bus.EmitSignal(Events.EventBus.SignalName.PlayerHit, newArgs); + } + #if DEBUG /// /// For debugging purposes diff --git a/Characters/Player.cs b/Characters/Player.cs index 16c4a8e..4b8bed9 100644 --- a/Characters/Player.cs +++ b/Characters/Player.cs @@ -81,6 +81,7 @@ public sealed partial class Player : Character float damage, Character inflictor, float knockback, + Items.Weapon weapon = null, Vector2 knockbackDir = default) { if (damage >= 10 && IsAlive) @@ -91,7 +92,11 @@ public sealed partial class Player : Character GetNode("Effects/HurtParticles") .SetDirection(knockbackDir); - base.OnReceivedDamage(damage, inflictor, knockback, knockbackDir); + base.OnReceivedDamage(damage, + inflictor, + knockback, + weapon, + knockbackDir); } public override void Die() diff --git a/Characters/Player.tscn b/Characters/Player.tscn index a4a7122..855c060 100644 --- a/Characters/Player.tscn +++ b/Characters/Player.tscn @@ -1,12 +1,15 @@ -[gd_scene load_steps=63 format=3 uid="uid://b2254pup8k161"] +[gd_scene load_steps=66 format=3 uid="uid://b2254pup8k161"] [ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"] [ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"] [ext_resource type="Texture2D" uid="uid://dpepm54hjuyga" path="res://Assets/Sprites/Characters/forsen-hand.png" id="3_3dqh7"] [ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="4_5vird"] +[ext_resource type="Script" path="res://Utils/PlayerStats.cs" id="4_06oya"] [ext_resource type="PackedScene" uid="uid://cl56eadpklnbo" path="res://Utils/PlayerCamera.tscn" id="4_ym125"] [ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="5_rgckv"] +[ext_resource type="Script" path="res://Utils/Values/DoubleValue.cs" id="5_txl0r"] [ext_resource type="Script" path="res://State/Character/CharacterDashState.cs" id="6_rft7p"] +[ext_resource type="Script" path="res://Utils/Values/IntValue.cs" id="6_sunc5"] [ext_resource type="Script" path="res://State/Character/PlayerIdleState.cs" id="6_wkfdm"] [ext_resource type="PackedScene" uid="uid://dvqap2uhcah63" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"] [ext_resource type="Script" path="res://State/Character/PlayerMoveState.cs" id="7_dfqd8"] @@ -331,6 +334,15 @@ StateMachine = NodePath("StateMachine") Hurtbox = NodePath("Hurtbox") Faction = 1 +[node name="Stats" type="Node" parent="."] +script = ExtResource("4_06oya") + +[node name="XP" type="Node" parent="Stats"] +script = ExtResource("5_txl0r") + +[node name="Level" type="Node" parent="Stats"] +script = ExtResource("6_sunc5") + [node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")] script = ExtResource("5_rgckv") InitialState = NodePath("Idle") diff --git a/Entities/Projectile.cs b/Entities/Projectile.cs index 0eaf7bf..5f09fa5 100644 --- a/Entities/Projectile.cs +++ b/Entities/Projectile.cs @@ -43,8 +43,11 @@ public partial class Projectile : RigidBody2D [Export] public double Delay { get; set; } = 0; + [System.Obsolete] public Character Character { get; set; } + public Items.Weapon Weapon { get; set; } + public bool IsDead { get; set; } public override void _Ready() @@ -87,8 +90,9 @@ public partial class Projectile : RigidBody2D { hurtbox.InflictDamage( Hitbox.Damage, - Character, + Hitbox.Inflictor, Hitbox.Knockback, + weapon: Weapon, knockbackVector: Direction ); EmitSignal(SignalName.Hit, box); diff --git a/Entities/ShungiteSpike.cs b/Entities/ShungiteSpike.cs index 26fc2a2..983241a 100644 --- a/Entities/ShungiteSpike.cs +++ b/Entities/ShungiteSpike.cs @@ -63,6 +63,7 @@ public partial class ShungiteSpike : Projectile float damage, Characters.Character inflictor, float knockback, + Items.Weapon weapon, Vector2 knockbackDir) { // if we were hit by the player before the spike freezes, diff --git a/Events/EventBus.cs b/Events/EventBus.cs index 9f719b1..24835a2 100644 --- a/Events/EventBus.cs +++ b/Events/EventBus.cs @@ -4,6 +4,8 @@ namespace SupaLidlGame.Events; public partial class EventBus : Node { + public static EventBus Instance { get; private set; } + [Signal] public delegate void RequestMoveToAreaEventHandler(RequestAreaArgs args); @@ -16,6 +18,15 @@ public partial class EventBus : Node [Signal] public delegate void PlayerHurtEventHandler(HurtArgs args); + [Signal] + public delegate void PlayerHitEventHandler(HitArgs args); + + [Signal] + public delegate void PlayerXPChangedEventHandler(double xp); + + [Signal] + public delegate void PlayerLevelChangedEventHandler(int level); + [Signal] public delegate void PlayerHealthChangedEventHandler(HealthChangedArgs args); @@ -37,5 +48,10 @@ public partial class EventBus : Node public override void _Ready() { ProcessMode = ProcessModeEnum.Always; + if (Instance is not null) + { + throw new MultipleSingletonsException(); + } + Instance = this; } } diff --git a/Events/HitArgs.cs b/Events/HitArgs.cs new file mode 100644 index 0000000..e00ee82 --- /dev/null +++ b/Events/HitArgs.cs @@ -0,0 +1,6 @@ +namespace SupaLidlGame.Events; + +public partial class HitArgs : HurtArgs +{ + public Characters.Character Victim { get; set; } +} diff --git a/Exceptions/MultipleSingletonsException.cs b/Exceptions/MultipleSingletonsException.cs new file mode 100644 index 0000000..0ab3b1f --- /dev/null +++ b/Exceptions/MultipleSingletonsException.cs @@ -0,0 +1,6 @@ +namespace SupaLidlGame; + +public class MultipleSingletonsException : System.Exception +{ + +} diff --git a/Items/Weapon.cs b/Items/Weapon.cs index 29146b5..ea4475c 100644 --- a/Items/Weapon.cs +++ b/Items/Weapon.cs @@ -60,6 +60,9 @@ public abstract partial class Weapon : Item [Export] public float MaxDistanceHint { get; set; } + [Export] + public float PlayerLevelGain { get; set; } + [Export] public Sprite2D HandAnchor { get; set; } diff --git a/Items/Weapons/ProjectileSpawner.cs b/Items/Weapons/ProjectileSpawner.cs index 89cafad..61e3b9d 100644 --- a/Items/Weapons/ProjectileSpawner.cs +++ b/Items/Weapons/ProjectileSpawner.cs @@ -49,6 +49,9 @@ public partial class ProjectileSpawner : Ranged } } + projectile.Hitbox.Inflictor = Character; + projectile.Weapon = this; + if (projectile is Utils.ITarget target) { if (Character is Characters.NPC npc) diff --git a/Items/Weapons/Pugio.tscn b/Items/Weapons/Pugio.tscn index a3f393a..f73fc2e 100644 --- a/Items/Weapons/Pugio.tscn +++ b/Items/Weapons/Pugio.tscn @@ -370,35 +370,53 @@ shader_parameter/color = Quaternion(1, 1, 1, 1) shader_parameter/intensity = 0.0 shader_parameter/alpha_modulate = 1.0 -[node name="Pugio" type="Node2D"] +[node name="Pugio" type="Node2D" node_paths=PackedStringArray("Hitbox", "AnimationPlayer", "ParryParticles", "StateMachine", "Anchor", "HandAnchor")] y_sort_enabled = true texture_filter = 3 script = ExtResource("1_mai31") +Hitbox = NodePath("Hitbox") +AnimationPlayer = NodePath("AnimationPlayer") AttackTime = 0.2 AttackAltTime = 0.75 AttackAnimationDuration = 0.5 +ParryParticles = NodePath("Anchor/Node2D/Sprite2D/ParryParticles") NPCAnticipateTime = 0.3 +StateMachine = NodePath("State") +Anchor = NodePath("Anchor") Damage = 20.0 UseTime = 0.55 UseAltTime = 1.5 Knockback = 64.0 +PlayerLevelGain = 1.0 +HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand") -[node name="State" type="Node" parent="."] +[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] script = ExtResource("2_5ramr") +InitialState = NodePath("Idle") UsedItemStates = Array[NodePath]([NodePath("Attack"), NodePath("Block")]) DeusedItemStates = Array[NodePath]([NodePath("Idle")]) -[node name="Idle" type="Node" parent="State"] +[node name="Idle" type="Node" parent="State" node_paths=PackedStringArray("UseState", "UseAltState", "Sword")] script = ExtResource("3_fwkit") +UseState = NodePath("../Anticipate") +UseAltState = NodePath("../Block") +Sword = NodePath("../..") -[node name="Anticipate" type="Node" parent="State"] +[node name="Anticipate" type="Node" parent="State" node_paths=PackedStringArray("Sword", "AttackState")] script = ExtResource("4_nsn1q") +Sword = NodePath("../..") +AttackState = NodePath("../Attack") -[node name="Attack" type="Node" parent="State"] +[node name="Attack" type="Node" parent="State" node_paths=PackedStringArray("Sword", "IdleState")] script = ExtResource("5_g1en5") +Sword = NodePath("../..") +IdleState = NodePath("../Idle") -[node name="Block" type="Node" parent="State"] +[node name="Block" type="Node" parent="State" node_paths=PackedStringArray("Sword", "IdleState", "UseState")] script = ExtResource("6_yvm8x") +Sword = NodePath("../..") +IdleState = NodePath("../Idle") +UseState = NodePath("../Anticipate") BlockAnimKey = "block" [node name="WorldEnvironment" type="WorldEnvironment" parent="."] diff --git a/Items/Weapons/Shotgun.tscn b/Items/Weapons/Shotgun.tscn index 9fcd5d5..1f84f6d 100644 --- a/Items/Weapons/Shotgun.tscn +++ b/Items/Weapons/Shotgun.tscn @@ -258,6 +258,7 @@ StateMachine = NodePath("State") Damage = 12.0 UseTime = 1.5 InitialVelocity = 220.0 +PlayerLevelGain = 0.5 [node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] script = ExtResource("2_ag6rd") diff --git a/Items/Weapons/Sword.cs b/Items/Weapons/Sword.cs index 76a1964..07a7351 100644 --- a/Items/Weapons/Sword.cs +++ b/Items/Weapons/Sword.cs @@ -191,7 +191,11 @@ public partial class Sword : Weapon, IParryable { if (box is Hurtbox hurtbox) { - hurtbox.InflictDamage(Damage, Character, Knockback); + hurtbox.InflictDamage( + Damage, + Character, + Knockback, + this); } } } diff --git a/Items/Weapons/Sword.tscn b/Items/Weapons/Sword.tscn index 2c7bb84..3da3286 100644 --- a/Items/Weapons/Sword.tscn +++ b/Items/Weapons/Sword.tscn @@ -354,31 +354,45 @@ graph_offset = Vector2(0, -104.073) [sub_resource type="AnimationNodeStateMachinePlayback" id="AnimationNodeStateMachinePlayback_37556"] -[node name="Sword" type="Node2D"] +[node name="Sword" type="Node2D" node_paths=PackedStringArray("Hitbox", "AnimationPlayer", "ParryParticles", "StateMachine", "Anchor", "HandAnchor")] y_sort_enabled = true texture_filter = 3 script = ExtResource("1_mlo73") +Hitbox = NodePath("Hitbox") +AnimationPlayer = NodePath("AnimationPlayer") AttackTime = 0.2 AttackAnimationDuration = 0.75 +ParryParticles = NodePath("Anchor/Node2D/Sprite2D/ParryParticles") NPCAnticipateTime = 0.3 +StateMachine = NodePath("State") +Anchor = NodePath("Anchor") Damage = 20.0 UseTime = 0.55 Knockback = 64.0 ShouldHideIdle = true +PlayerLevelGain = 1.0 +HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand") -[node name="State" type="Node" parent="."] +[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] script = ExtResource("2_vwirq") +InitialState = NodePath("Idle") UsedItemStates = Array[NodePath]([NodePath("Attack")]) DeusedItemStates = Array[NodePath]([NodePath("Idle")]) -[node name="Idle" type="Node" parent="State"] +[node name="Idle" type="Node" parent="State" node_paths=PackedStringArray("UseState", "Sword")] script = ExtResource("3_nw6r0") +UseState = NodePath("../Anticipate") +Sword = NodePath("../..") -[node name="Anticipate" type="Node" parent="State"] +[node name="Anticipate" type="Node" parent="State" node_paths=PackedStringArray("Sword", "AttackState")] script = ExtResource("4_j3cud") +Sword = NodePath("../..") +AttackState = NodePath("../Attack") -[node name="Attack" type="Node" parent="State"] +[node name="Attack" type="Node" parent="State" node_paths=PackedStringArray("Sword", "IdleState")] script = ExtResource("5_hmisb") +Sword = NodePath("../..") +IdleState = NodePath("../Idle") [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_72txp") diff --git a/UI/Base.tscn b/UI/Base.tscn index db8df23..ad628a7 100644 --- a/UI/Base.tscn +++ b/UI/Base.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=7 format=3 uid="uid://c271rdjhd1gfo"] +[gd_scene load_steps=8 format=3 uid="uid://c271rdjhd1gfo"] [ext_resource type="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"] [ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"] [ext_resource type="PackedScene" uid="uid://bxo553hblp6nf" path="res://UI/HealthBar.tscn" id="3_j1j6h"] [ext_resource type="PackedScene" uid="uid://01d24ij5av1y" path="res://UI/BossBar.tscn" id="4_igi28"] +[ext_resource type="PackedScene" uid="uid://cr7tkxctmyags" path="res://UI/LevelBar.tscn" id="4_rcekd"] [ext_resource type="PackedScene" uid="uid://c77754nvmckn" path="res://UI/LocationDisplay.tscn" id="5_cr6vo"] [ext_resource type="PackedScene" uid="uid://d3q1yu3n7cqfj" path="res://UI/SceneTransition.tscn" id="6_j0nhv"] @@ -47,7 +48,7 @@ BossBar = NodePath("Bottom/BossBar") layout_mode = 1 anchors_preset = 10 anchor_right = 1.0 -offset_bottom = 40.0 +offset_bottom = 64.0 grow_horizontal = 2 [node name="Margin" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top"] @@ -55,9 +56,21 @@ layout_mode = 2 theme_override_constants/margin_left = 16 theme_override_constants/margin_top = 16 -[node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin" instance=ExtResource("3_j1j6h")] +[node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin"] layout_mode = 2 -size_flags_horizontal = 3 +theme_override_constants/separation = 12 + +[node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("3_j1j6h")] +layout_mode = 2 + +[node name="LevelBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("4_rcekd")] +layout_mode = 2 + +[node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 16 [node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] layout_mode = 1 diff --git a/UI/LevelBar.cs b/UI/LevelBar.cs new file mode 100644 index 0000000..cf08429 --- /dev/null +++ b/UI/LevelBar.cs @@ -0,0 +1,36 @@ +using Godot; +using SupaLidlGame.Events; + +namespace SupaLidlGame.UI; + +public partial class LevelBar : Control +{ + public TextureProgressBar XPBar { get; set; } + + private TextureProgressBar[] _levelBars = new TextureProgressBar[4]; + + public override void _Ready() + { + XPBar = GetNode("%XPBar"); + + for (int i = 0; i < 4; i++) + { + _levelBars[i] = GetNode($"%Level{i + 1}Bar"); + } + + EventBus.Instance.PlayerXPChanged += (xp) => + { + XPBar.Value = xp; + }; + + EventBus.Instance.PlayerLevelChanged += (level) => + { + for (int i = 0; i < _levelBars.Length; i++) + { + // level 0: 0 is not less than 0 + // level 1: 0 is less than 1 + _levelBars[i].Value = i < level ? 1 : 0; + } + }; + } +} diff --git a/UI/LevelBar.tscn b/UI/LevelBar.tscn new file mode 100644 index 0000000..eaff566 --- /dev/null +++ b/UI/LevelBar.tscn @@ -0,0 +1,94 @@ +[gd_scene load_steps=4 format=3 uid="uid://cr7tkxctmyags"] + +[ext_resource type="Script" path="res://UI/LevelBar.cs" id="1_eqetx"] +[ext_resource type="Texture2D" uid="uid://b75oak1nd2q6x" path="res://Assets/Sprites/UI/over-under-bar.png" id="2_f332l"] +[ext_resource type="Texture2D" uid="uid://co7xm7i5f6n51" path="res://Assets/Sprites/UI/progress-bar.png" id="3_arvub"] + +[node name="LevelBar" type="Control"] +layout_mode = 3 +anchors_preset = 0 +offset_right = 128.0 +offset_bottom = 8.0 +script = ExtResource("1_eqetx") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +offset_right = 128.0 +offset_bottom = 8.0 + +[node name="XPBar" type="TextureProgressBar" parent="HBoxContainer"] +unique_name_in_owner = true +texture_filter = 1 +layout_mode = 2 +size_flags_horizontal = 3 +max_value = 4.0 +step = 0.01 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +texture_under = ExtResource("2_f332l") +texture_progress = ExtResource("3_arvub") + +[node name="Level1Bar" type="TextureProgressBar" parent="HBoxContainer"] +unique_name_in_owner = true +texture_filter = 1 +layout_mode = 2 +max_value = 1.0 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +texture_under = ExtResource("2_f332l") +texture_progress = ExtResource("3_arvub") + +[node name="Level2Bar" type="TextureProgressBar" parent="HBoxContainer"] +unique_name_in_owner = true +texture_filter = 1 +layout_mode = 2 +max_value = 1.0 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +texture_under = ExtResource("2_f332l") +texture_progress = ExtResource("3_arvub") + +[node name="Level3Bar" type="TextureProgressBar" parent="HBoxContainer"] +unique_name_in_owner = true +texture_filter = 1 +layout_mode = 2 +max_value = 1.0 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +texture_under = ExtResource("2_f332l") +texture_progress = ExtResource("3_arvub") + +[node name="Level4Bar" type="TextureProgressBar" parent="HBoxContainer"] +unique_name_in_owner = true +texture_filter = 1 +layout_mode = 2 +max_value = 1.0 +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +texture_under = ExtResource("2_f332l") +texture_progress = ExtResource("3_arvub") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 4 diff --git a/Utils/PlayerStats.cs b/Utils/PlayerStats.cs new file mode 100644 index 0000000..70f51bb --- /dev/null +++ b/Utils/PlayerStats.cs @@ -0,0 +1,75 @@ +using Godot; +using SupaLidlGame.Events; + +namespace SupaLidlGame.Utils; + +public partial class PlayerStats : Node +{ + public const int MAX_XP_PER_LEVEL = 4; + + public const int MAX_LEVELS = 4; + + public DoubleValue XP { get; set; } + + public IntValue Level { get; set; } + + public double XPDecayVelocity { get; set; } = 1; + + protected bool _shouldDecayXP = true; + + protected Timer _xpDecayTimer; + + public override void _Ready() + { + XP = GetNode("XP"); + Level = GetNode("Level"); + + _xpDecayTimer = new Timer(); + _xpDecayTimer.Timeout += () => _shouldDecayXP = true; + _xpDecayTimer.Stop(); + AddChild(_xpDecayTimer); + + var bus = EventBus.Instance; + XP.Changed += (oldValue, newValue) => + { + bus.EmitSignal(EventBus.SignalName.PlayerXPChanged, newValue); + }; + + Level.Changed += (oldValue, newValue) => + { + bus.EmitSignal(EventBus.SignalName.PlayerLevelChanged, newValue); + }; + + bus.PlayerHit += (args) => + { + double xp = XP.Value; + xp += args.Weapon?.PlayerLevelGain ?? 0; + + if (xp >= MAX_XP_PER_LEVEL) + { + int deltaLevel = (int)(xp / MAX_XP_PER_LEVEL); + xp %= MAX_XP_PER_LEVEL; + Level.Value = Mathf.Min(Level.Value + deltaLevel, MAX_LEVELS); + } + + if (Level.Value == MAX_LEVELS) + { + // if max level, can only go up to 1 xp + xp = Mathf.Min(xp, 1); + } + + XP.Value = xp; + + _shouldDecayXP = false; + _xpDecayTimer.Start(1); + }; + } + + public override void _Process(double delta) + { + if (_shouldDecayXP) + { + XP.Value = Mathf.MoveToward(XP.Value, 1, XPDecayVelocity * delta); + } + } +} diff --git a/Utils/Values/DoubleValue.cs b/Utils/Values/DoubleValue.cs new file mode 100644 index 0000000..bad84bb --- /dev/null +++ b/Utils/Values/DoubleValue.cs @@ -0,0 +1,22 @@ +using Godot; + +namespace SupaLidlGame; + +public partial class DoubleValue : Node, IValue +{ + [Signal] + public delegate void ChangedEventHandler(double oldValue, double newValue); + + protected double _value = default; + + [Export] + public double Value + { + get => _value; + set + { + EmitSignal(SignalName.Changed, _value, value); + _value = value; + } + } +} diff --git a/Utils/Values/FloatValue.cs b/Utils/Values/FloatValue.cs new file mode 100644 index 0000000..602beb6 --- /dev/null +++ b/Utils/Values/FloatValue.cs @@ -0,0 +1,22 @@ +using Godot; + +namespace SupaLidlGame; + +public partial class FloatValue : Node, IValue +{ + [Signal] + public delegate void ChangedEventHandler(float oldValue, float newValue); + + protected float _value = default; + + [Export] + public float Value + { + get => _value; + set + { + EmitSignal(SignalName.Changed, _value, value); + _value = value; + } + } +} diff --git a/Utils/Values/IValue.cs b/Utils/Values/IValue.cs new file mode 100644 index 0000000..d44a31e --- /dev/null +++ b/Utils/Values/IValue.cs @@ -0,0 +1,8 @@ +namespace SupaLidlGame; + +public interface IValue +{ + public delegate void ChangedEventHandler(T oldValue, T newValue); + + public T Value { get; set; } +} diff --git a/Utils/Values/IntValue.cs b/Utils/Values/IntValue.cs new file mode 100644 index 0000000..d3975b8 --- /dev/null +++ b/Utils/Values/IntValue.cs @@ -0,0 +1,22 @@ +using Godot; + +namespace SupaLidlGame; + +public partial class IntValue : Node, IValue +{ + [Signal] + public delegate void ChangedEventHandler(int oldValue, int newValue); + + protected int _value = default; + + [Export] + public int Value + { + get => _value; + set + { + EmitSignal(SignalName.Changed, _value, value); + _value = value; + } + } +}