leveling up

pull/6/head
John Montagu, the 4th Earl of Sandvich 2023-09-03 17:42:32 -07:00
parent a07642c3af
commit 385ed4ffdb
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
23 changed files with 433 additions and 19 deletions

View File

@ -11,6 +11,7 @@ public partial class Hurtbox : BoundingBox, IFaction
float damage,
Character inflictor,
float knockback,
Items.Weapon weapon = null,
Vector2 knockbackDir = default);
/// <summary>
@ -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);
}
}

View File

@ -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
}
}
/// <summary>
/// Converts a HurtArgs to HitArgs if the attacker was a player and emits
/// the <c>EventBus.PlayerHit</c> signal.
/// </summary>
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
/// <summary>
/// For debugging purposes

View File

@ -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<GpuParticles2D>("Effects/HurtParticles")
.SetDirection(knockbackDir);
base.OnReceivedDamage(damage, inflictor, knockback, knockbackDir);
base.OnReceivedDamage(damage,
inflictor,
knockback,
weapon,
knockbackDir);
}
public override void Die()

View File

@ -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")

View File

@ -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);

View File

@ -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,

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
namespace SupaLidlGame.Events;
public partial class HitArgs : HurtArgs
{
public Characters.Character Victim { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace SupaLidlGame;
public class MultipleSingletonsException : System.Exception
{
}

View File

@ -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; }

View File

@ -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)

View File

@ -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="."]

View File

@ -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")

View File

@ -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);
}
}
}

View File

@ -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")

View File

@ -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

36
UI/LevelBar.cs 100644
View File

@ -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<TextureProgressBar>("%XPBar");
for (int i = 0; i < 4; i++)
{
_levelBars[i] = GetNode<TextureProgressBar>($"%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;
}
};
}
}

94
UI/LevelBar.tscn 100644
View File

@ -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

View File

@ -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<DoubleValue>("XP");
Level = GetNode<IntValue>("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);
}
}
}

View File

@ -0,0 +1,22 @@
using Godot;
namespace SupaLidlGame;
public partial class DoubleValue : Node, IValue<double>
{
[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;
}
}
}

View File

@ -0,0 +1,22 @@
using Godot;
namespace SupaLidlGame;
public partial class FloatValue : Node, IValue<float>
{
[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;
}
}
}

View File

@ -0,0 +1,8 @@
namespace SupaLidlGame;
public interface IValue<T>
{
public delegate void ChangedEventHandler(T oldValue, T newValue);
public T Value { get; set; }
}

View File

@ -0,0 +1,22 @@
using Godot;
namespace SupaLidlGame;
public partial class IntValue : Node, IValue<int>
{
[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;
}
}
}