diff --git a/Classes/Notes/Note.cs b/Classes/Notes/Note.cs
index 57c463d7..466a5827 100644
--- a/Classes/Notes/Note.cs
+++ b/Classes/Notes/Note.cs
@@ -35,17 +35,7 @@ public Note(
Id = id;
Name = name;
Owner = owner;
- NoteEffect =
- noteEffect
- ?? (
- (BD, source, timing) =>
- {
- Array.ForEach(
- BD.GetTargets(source), //Ok, sure
- enemy => enemy.TakeDamage((int)timing * source._baseVal)
- );
- }
- );
+ NoteEffect = noteEffect;
_baseVal = baseVal;
Texture = texture;
Tooltip = tooltip;
diff --git a/Classes/StatusEffects/Assets/Status_Block.png b/Classes/StatusEffects/Assets/Status_Block.png
new file mode 100644
index 00000000..fe2d2f57
Binary files /dev/null and b/Classes/StatusEffects/Assets/Status_Block.png differ
diff --git a/Classes/StatusEffects/Assets/Status_Block.png.import b/Classes/StatusEffects/Assets/Status_Block.png.import
new file mode 100644
index 00000000..ebdcbce5
--- /dev/null
+++ b/Classes/StatusEffects/Assets/Status_Block.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cdlepg5fxfwn0"
+path="res://.godot/imported/Status_Block.png-e8facdf93b546460a44dfea789c90919.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Classes/StatusEffects/Assets/Status_Block.png"
+dest_files=["res://.godot/imported/Status_Block.png-e8facdf93b546460a44dfea789c90919.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Classes/StatusEffects/Assets/Status_Mulligan.png b/Classes/StatusEffects/Assets/Status_Mulligan.png
new file mode 100644
index 00000000..5d84a8ac
Binary files /dev/null and b/Classes/StatusEffects/Assets/Status_Mulligan.png differ
diff --git a/Classes/StatusEffects/StatusEffect.cs b/Classes/StatusEffects/StatusEffect.cs
new file mode 100644
index 00000000..0a430811
--- /dev/null
+++ b/Classes/StatusEffects/StatusEffect.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Linq;
+using FunkEngine;
+using Godot;
+
+///
+/// Status Effect class.
+/// Preferably set up as a static default status, then apply status using GetInstance, but custom statuses can be defined elsewhere.
+/// Invoke StatusEnd to remove status.
+///
+public partial class StatusEffect : TextureRect, IBattleEvent
+{ //TODO: Status effects that are permanent, and status effects that don't take up a slot/are invisible
+ public static readonly string LoadPath = "res://Classes/StatusEffects/StatusIcon.tscn";
+ public PuppetTemplate Sufferer { get; private set; }
+ public int Count { get; private set; }
+
+ public string StatusName { get; private set; }
+
+ [Export]
+ private Label CountLabel { get; set; }
+
+ internal delegate void StatusEndHandler(StatusEffect status);
+ internal event StatusEndHandler StatusEnd;
+
+ #region DefaultStatuses
+ private static readonly Action BlockEffect = (e, self) =>
+ {
+ if (e is BattleDirector.Harbinger.OnDamageInstanceArgs dmgArgs)
+ {
+ if (dmgArgs.Dmg.Target != self.Sufferer || dmgArgs.Dmg.Damage <= 0)
+ return;
+ dmgArgs.Dmg.ModifyDamage(0, 0);
+ self.DecCount();
+ }
+ };
+
+ ///
+ /// On the owner receiving a damage instance, if valid (correct target and dmg > 0) sets damage to 0 and reduces count.
+ ///
+ public static readonly StatusEffect Block = new StatusEffect()
+ .InitStatus(
+ "Block",
+ BlockEffect,
+ BattleEffectTrigger.OnDamageInstance,
+ GD.Load("res://Classes/StatusEffects/Assets/Status_Block.png")
+ )
+ .SetTags(true);
+
+ private static readonly Action MulliganEffect = (e, self) =>
+ {
+ if (e is not BattleDirector.Harbinger.NoteHitArgs { Timing: Timing.Miss })
+ return;
+ e.BD.NPB.SetIgnoreMiss(true); //Intercept the miss
+ self.DecCount();
+ };
+
+ ///
+ /// If the player missed, take damage, but don't receive combo penalty.
+ ///
+ public static readonly StatusEffect Mulligan = new StatusEffect()
+ .InitStatus(
+ "Mulligan",
+ MulliganEffect,
+ BattleEffectTrigger.NoteHit,
+ GD.Load("res://Classes/StatusEffects/Assets/Status_Mulligan.png")
+ )
+ .SetTags(true);
+ #endregion
+
+ private BattleEffectTrigger _trigger;
+ private Action _effect;
+
+ public BattleEffectTrigger GetTrigger()
+ {
+ return _trigger;
+ }
+
+ public void OnTrigger(BattleEventArgs e)
+ {
+ _effect(e, this);
+ }
+
+ public StatusEffect InitStatus(
+ string name,
+ Action effect,
+ BattleEffectTrigger trigger,
+ Texture2D texture = null
+ )
+ {
+ _effect = effect;
+ _trigger = trigger;
+ StatusName = name;
+ Texture = texture;
+ return this;
+ }
+
+ public StatusEffect GetInstance(int count = 1)
+ {
+ StatusEffect result = GD.Load(LoadPath).Instantiate();
+ result.SetCount(count);
+ result.InitStatus(Name, _effect, _trigger, Texture);
+ result.SetTags(_stackable, _refreshes);
+ return result;
+ }
+
+ public void SetOwner(PuppetTemplate owner)
+ {
+ Sufferer = owner;
+ }
+
+ public void IncCount(int count = 1)
+ {
+ SetCount(Count + count);
+ }
+
+ public void DecCount(int count = 1)
+ {
+ SetCount(Count - count);
+ }
+
+ public void SetCount(int count)
+ {
+ Count = count;
+ CountLabel.Text = Count.ToString();
+ if (Count <= 0)
+ {
+ StatusEnd?.Invoke(this);
+ }
+ }
+
+ ///
+ /// Re-applying a status increases the count.
+ ///
+ private bool _stackable;
+
+ ///
+ /// Re-applying a status sets the count to the higher counte
+ ///
+ private bool _refreshes;
+
+ public StatusEffect SetTags(bool stackable = false, bool refreshes = false)
+ {
+ _stackable = stackable;
+ _refreshes = refreshes;
+ return this;
+ }
+
+ //Called if a puppet is receiving a duplicate effect.
+ public void StackEffect(StatusEffect incomingEffect)
+ {
+ if (incomingEffect.StatusName != StatusName)
+ return;
+ if (_stackable)
+ {
+ IncCount(incomingEffect.Count);
+ }
+
+ if (_refreshes && incomingEffect.Count >= Count)
+ {
+ SetCount(incomingEffect.Count);
+ }
+ }
+}
diff --git a/Classes/StatusEffects/StatusEffect.cs.uid b/Classes/StatusEffects/StatusEffect.cs.uid
new file mode 100644
index 00000000..ed242236
--- /dev/null
+++ b/Classes/StatusEffects/StatusEffect.cs.uid
@@ -0,0 +1 @@
+uid://wawjisy70w1v
diff --git a/Classes/StatusEffects/StatusIcon.tscn b/Classes/StatusEffects/StatusIcon.tscn
new file mode 100644
index 00000000..9ca0495f
--- /dev/null
+++ b/Classes/StatusEffects/StatusIcon.tscn
@@ -0,0 +1,36 @@
+[gd_scene load_steps=4 format=3 uid="uid://opqtl7khulko"]
+
+[ext_resource type="Script" uid="uid://wawjisy70w1v" path="res://Classes/StatusEffects/StatusEffect.cs" id="1_2adc3"]
+
+[sub_resource type="Gradient" id="Gradient_y1lef"]
+offsets = PackedFloat32Array(1)
+colors = PackedColorArray(1, 1, 1, 1)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_2adc3"]
+gradient = SubResource("Gradient_y1lef")
+width = 16
+height = 16
+
+[node name="StatusIcon" type="TextureRect" node_paths=PackedStringArray("CountLabel")]
+custom_minimum_size = Vector2(8, 8)
+offset_right = 8.0
+offset_bottom = 8.0
+texture = SubResource("GradientTexture2D_2adc3")
+script = ExtResource("1_2adc3")
+CountLabel = NodePath("Count")
+
+[node name="Count" type="Label" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 2.0
+offset_top = 4.0
+offset_bottom = 2.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_font_sizes/font_size = 12
+text = "0"
+horizontal_alignment = 2
+vertical_alignment = 2
diff --git a/Globals/FunkEngineNameSpace.cs b/Globals/FunkEngineNameSpace.cs
index 7cf98d80..a280d737 100644
--- a/Globals/FunkEngineNameSpace.cs
+++ b/Globals/FunkEngineNameSpace.cs
@@ -224,6 +224,7 @@ public enum Timing
public enum Targetting
{
+ Player,
First,
All,
}
@@ -237,6 +238,8 @@ public enum BattleEffectTrigger
OnLoop,
OnBattleStart,
OnBattleEnd,
+ OnDamageInstance,
+ OnDamageTaken,
}
public enum Stages
diff --git a/Globals/Scribe.cs b/Globals/Scribe.cs
index 1a5b8419..d0f49fdb 100644
--- a/Globals/Scribe.cs
+++ b/Globals/Scribe.cs
@@ -22,7 +22,8 @@ public partial class Scribe : Node
1,
(director, note, timing) =>
{
- director.Player.TakeDamage((3 - (int)timing) * note.GetBaseVal());
+ int dmg = (3 - (int)timing) * note.GetBaseVal();
+ director.Player.TakeDamage(new DamageInstance(dmg, null, director.Player));
}
),
new Note(
@@ -36,7 +37,7 @@ public partial class Scribe : Node
{
if (timing == Timing.Miss)
return;
- director.GetFirstEnemy()?.TakeDamage((int)timing * note.GetBaseVal());
+ director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
}
),
new Note(
@@ -50,7 +51,7 @@ public partial class Scribe : Node
{
if (timing == Timing.Miss)
return;
- director.GetFirstEnemy()?.TakeDamage(note.GetBaseVal() * (int)timing);
+ director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
}
),
new Note(
@@ -78,8 +79,9 @@ public partial class Scribe : Node
{
if (timing == Timing.Miss)
return;
- director.Player.Heal((int)timing * note.GetBaseVal());
- director.GetFirstEnemy()?.TakeDamage((int)timing * note.GetBaseVal());
+ int dmg = (int)timing * note.GetBaseVal();
+ director.Player.Heal(dmg);
+ director.DealDamage(note, dmg, director.Player);
}
),
new Note(
@@ -93,13 +95,13 @@ public partial class Scribe : Node
{
if (timing == Timing.Miss)
return;
- director.GetFirstEnemy()?.TakeDamage((int)timing + note.GetBaseVal());
+ director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
},
0.25f
),
new Note(
6,
- "Play-c-HoldBlock",
+ "PlayerBlock",
"Gives player one charge of block.",
GD.Load("res://Classes/Notes/Assets/Note_PlayerBlock.png"),
null,
@@ -108,7 +110,7 @@ public partial class Scribe : Node
{
if (timing == Timing.Miss)
return;
- //director.Player.GainShield(note.GetBaseVal()); //todo: should scale with timing????
+ director.AddStatus(Targetting.Player, StatusEffect.Block.GetInstance()); //todo: should scale with timing????
}
),
};
@@ -203,7 +205,10 @@ public partial class Scribe : Node
1,
(e, self, val) =>
{
- e.BD.GetFirstEnemy()?.TakeDamage(val);
+ if (e is not BattleDirector.Harbinger.NoteHitArgs noteHitArgs)
+ return;
+ if (noteHitArgs.Timing != Timing.Miss)
+ e.BD.DealDamage(Targetting.First, val, null);
}
),
}
@@ -221,7 +226,7 @@ public partial class Scribe : Node
5,
(e, self, val) =>
{
- e.BD.GetFirstEnemy()?.TakeDamage(val);
+ e.BD.DealDamage(Targetting.First, val, null);
}
),
}
diff --git a/Scenes/BattleDirector/Scripts/BattleDirector.cs b/Scenes/BattleDirector/Scripts/BattleDirector.cs
index be7ac2ee..8e105c1b 100644
--- a/Scenes/BattleDirector/Scripts/BattleDirector.cs
+++ b/Scenes/BattleDirector/Scripts/BattleDirector.cs
@@ -39,49 +39,6 @@ public partial class BattleDirector : Node2D
#endregion
- #region Note Handling
- private bool PlayerAddNote(ArrowType type, Beat beat)
- {
- if (!NPB.CanPlaceNote())
- return false;
-
- Note noteToPlace = NPB.NotePlaced();
- noteToPlace.OnHit(this, Timing.Okay);
-
- CD.AddPlayerNote(noteToPlace, type, beat);
- Harbinger.Instance.InvokeNotePlaced(new ArrowData(type, beat, noteToPlace));
- Harbinger.Instance.InvokeNoteHit(noteToPlace, Timing.Okay); //TODO: test how this feels? maybe take it out later
- return true;
- }
-
- public PuppetTemplate[] GetTargets(Note note)
- {
- if (!note.IsPlayerNote())
- return [Player];
- switch (note.TargetType)
- {
- case Targetting.First:
- if (GetFirstEnemy() != null)
- return [GetFirstEnemy()];
- return [];
- case Targetting.All:
- return _enemies.Where(x => x.GetCurrentHealth() > 0).ToArray();
- }
- return null;
- }
-
- public PuppetTemplate GetFirstEnemy()
- {
- foreach (var enemy in _enemies)
- {
- if (enemy.GetCurrentHealth() > 0)
- return enemy;
- }
-
- return null;
- }
- #endregion
-
#region Initialization
private void SyncStartWithMix()
{
@@ -179,6 +136,20 @@ public override void _UnhandledInput(InputEvent @event)
}
}
+ private bool PlayerAddNote(ArrowType type, Beat beat)
+ {
+ if (!NPB.CanPlaceNote())
+ return false;
+
+ Note noteToPlace = NPB.NotePlaced();
+ noteToPlace.OnHit(this, Timing.Okay);
+
+ CD.AddPlayerNote(noteToPlace, type, beat);
+ Harbinger.Instance.InvokeNotePlaced(new ArrowData(type, beat, noteToPlace));
+ Harbinger.Instance.InvokeNoteHit(noteToPlace, Timing.Okay); //TODO: test how this feels? maybe take it out later
+ return true;
+ }
+
//Only called from CD signal when a note is processed
private void OnTimedInput(ArrowData data, double beatDif)
{
@@ -206,7 +177,7 @@ private void ForceMiss(ArrowType type)
{
NPB.HandleTiming(Timing.Miss, type);
CM.ComboText(Timing.Miss, type, NPB.GetCurrentCombo());
- Player.TakeDamage(4);
+ Player.TakeDamage(new DamageInstance(4, null, Player));
}
private Timing CheckTiming(double beatDif)
@@ -280,10 +251,82 @@ private void TransitionOutOfBattle()
}
#endregion
+ #region Battles
+ public void DealDamage(Note note, int damage, PuppetTemplate source)
+ {
+ PuppetTemplate[] targets = GetTargets(note.TargetType);
+ foreach (PuppetTemplate target in targets)
+ {
+ target.TakeDamage(new DamageInstance(damage, source, target));
+ }
+ }
+
+ public void DealDamage(
+ Targetting targetting,
+ int damage,
+ PuppetTemplate source,
+ bool targetPlayer = false
+ )
+ {
+ PuppetTemplate[] targets = GetTargets(targetting);
+ foreach (PuppetTemplate target in targets)
+ {
+ target.TakeDamage(new DamageInstance(damage, source, target));
+ }
+ }
+
+ public void AddStatus(Targetting targetting, StatusEffect status)
+ {
+ PuppetTemplate[] targets = GetTargets(targetting);
+ foreach (PuppetTemplate target in targets)
+ {
+ target.AddStatusEffect(status);
+ }
+
+ status.StatusEnd += RemoveStatus;
+ AddEvent(status);
+ }
+
+ public void RemoveStatus(StatusEffect status)
+ {
+ status.Sufferer.RemoveStatusEffect(status);
+ status.StatusEnd -= RemoveStatus;
+ RemoveEvent(status);
+ }
+
+ private PuppetTemplate[] GetTargets(Targetting targetting)
+ {
+ switch (targetting)
+ {
+ case Targetting.Player:
+ return [Player];
+ case Targetting.First:
+ if (GetFirstEnemy() != null)
+ return [GetFirstEnemy()];
+ return [];
+ case Targetting.All:
+ return _enemies.Where(x => x.GetCurrentHealth() > 0).ToArray();
+ default:
+ return null;
+ }
+ }
+
+ private PuppetTemplate GetFirstEnemy()
+ {
+ foreach (var enemy in _enemies)
+ {
+ if (enemy.GetCurrentHealth() > 0)
+ return enemy;
+ }
+
+ return null;
+ }
+ #endregion
+
#region BattleEffect Handling
private void AddEvent(IBattleEvent bEvent)
{
- switch (bEvent.GetTrigger()) //TODO: Look into a way to get eventhandler from string
+ switch (bEvent.GetTrigger())
{
case BattleEffectTrigger.NotePlaced:
Harbinger.Instance.NotePlaced += bEvent.OnTrigger;
@@ -297,6 +340,31 @@ private void AddEvent(IBattleEvent bEvent)
case BattleEffectTrigger.OnBattleEnd:
Harbinger.Instance.BattleEnded += bEvent.OnTrigger;
break;
+ case BattleEffectTrigger.OnDamageInstance:
+ Harbinger.Instance.OnDamageInstance += bEvent.OnTrigger;
+ break;
+ }
+ }
+
+ private void RemoveEvent(IBattleEvent bEvent)
+ {
+ switch (bEvent.GetTrigger())
+ {
+ case BattleEffectTrigger.NotePlaced:
+ Harbinger.Instance.NotePlaced -= bEvent.OnTrigger;
+ break;
+ case BattleEffectTrigger.OnLoop:
+ Harbinger.Instance.ChartLooped -= bEvent.OnTrigger;
+ break;
+ case BattleEffectTrigger.NoteHit:
+ Harbinger.Instance.NoteHit -= bEvent.OnTrigger;
+ break;
+ case BattleEffectTrigger.OnBattleEnd:
+ Harbinger.Instance.BattleEnded -= bEvent.OnTrigger;
+ break;
+ case BattleEffectTrigger.OnDamageInstance:
+ Harbinger.Instance.OnDamageInstance -= bEvent.OnTrigger;
+ break;
}
}
@@ -414,13 +482,33 @@ public void InvokeBattleEnded()
{
BattleEnded?.Invoke(new BattleEventArgs(_curDirector));
}
+
+ ///
+ /// Event Args to handle a damage instance being dealt. Happens before taking damage.
+ /// This allows damage to be intercepted, to be reduced/increased, to counter, or heal based on incoming damage.
+ ///
+ /// The BattleDirector calling the event.
+ /// The damage instance being thrown.
+ public class OnDamageInstanceArgs(BattleDirector bd, DamageInstance dmg)
+ : BattleEventArgs(bd)
+ {
+ public DamageInstance Dmg = dmg;
+ }
+
+ internal delegate void OnDamageInstanceHandler(OnDamageInstanceArgs e);
+ internal event OnDamageInstanceHandler OnDamageInstance;
+
+ public void InvokeOnDamageInstance(DamageInstance dmg)
+ {
+ OnDamageInstance?.Invoke(new OnDamageInstanceArgs(_curDirector, dmg));
+ }
}
private void DebugKillEnemy()
{
foreach (EnemyPuppet enemy in _enemies)
{
- enemy.TakeDamage(1000);
+ enemy.TakeDamage(new DamageInstance(1000, null, enemy));
}
}
}
diff --git a/Scenes/BattleDirector/Scripts/NotePlacementBar.cs b/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
index c90ad98c..448c9633 100644
--- a/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
+++ b/Scenes/BattleDirector/Scripts/NotePlacementBar.cs
@@ -186,6 +186,12 @@ public int GetCurrentCombo()
return _currentCombo;
}
+ public void ResetCurrentCombo()
+ {
+ _currentCombo = 0;
+ _bonusMult = 0;
+ }
+
//For external events to directly change mult
public void IncreaseBonusMult(int amount = 1)
{
@@ -239,13 +245,24 @@ private void HitNote(ArrowType type)
UpdateComboMultText();
}
+ public bool IgnoreMiss; //a one time safe miss
+
// Missing a note resets combo
private void MissNote()
{
- _currentCombo = 0;
- _bonusMult = 0;
+ if (IgnoreMiss)
+ {
+ IgnoreMiss = false;
+ return;
+ }
+ ResetCurrentCombo();
UpdateComboMultText();
}
+
+ public void SetIgnoreMiss(bool ignore)
+ {
+ IgnoreMiss = ignore;
+ }
#endregion
#region Shake
diff --git a/Scenes/Puppets/Enemies/BossBlood/Boss1.tscn b/Scenes/Puppets/Enemies/BossBlood/Boss1.tscn
index d200454d..fd5648c8 100644
--- a/Scenes/Puppets/Enemies/BossBlood/Boss1.tscn
+++ b/Scenes/Puppets/Enemies/BossBlood/Boss1.tscn
@@ -1,6 +1,7 @@
-[gd_scene load_steps=8 format=3 uid="uid://bi5iqbwpsd381"]
+[gd_scene load_steps=9 format=3 uid="uid://bi5iqbwpsd381"]
[ext_resource type="Script" uid="uid://bpyrrnhvisxgv" path="res://Scenes/Puppets/Enemies/BossBlood/P_BossBlood.cs" id="1_qj2oj"]
+[ext_resource type="PackedScene" uid="uid://cdoguwlxehbpg" path="res://Scenes/Puppets/StatusContainer.tscn" id="2_b8x7h"]
[ext_resource type="Texture2D" uid="uid://veedngaorx3l" path="res://Scenes/Puppets/Enemies/BossBlood/Assets/Boss1.png" id="2_mul30"]
[ext_resource type="Texture2D" uid="uid://b2iptr3o8rg4t" path="res://Scenes/Puppets/Enemies/BossBlood/Assets/Boss1EmissionShape.tres" id="3_yxnso"]
[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://Scenes/Puppets/HealthBar.tscn" id="4_ffkxf"]
@@ -22,11 +23,16 @@ scale_min = 4.0
scale_max = 4.0
turbulence_noise_strength = 14.2
-[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite")]
+[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite", "_statusContainer")]
script = ExtResource("1_qj2oj")
HealthBar = NodePath("ProgressBar")
Sprite = NodePath("Sprite")
InitScale = Vector2(2, 2)
+_statusContainer = NodePath("StatusContainer")
+
+[node name="StatusContainer" parent="." instance=ExtResource("2_b8x7h")]
+offset_top = -105.0
+offset_bottom = -69.0
[node name="Sprite" type="Sprite2D" parent="."]
position = Vector2(0, -32)
diff --git a/Scenes/Puppets/Enemies/EnemyPuppet.tscn b/Scenes/Puppets/Enemies/EnemyPuppet.tscn
index c7c6b849..fe97e6ba 100644
--- a/Scenes/Puppets/Enemies/EnemyPuppet.tscn
+++ b/Scenes/Puppets/Enemies/EnemyPuppet.tscn
@@ -1,12 +1,14 @@
-[gd_scene load_steps=3 format=3 uid="uid://cwm0n2wu851nx"]
+[gd_scene load_steps=4 format=3 uid="uid://cwm0n2wu851nx"]
[ext_resource type="Script" uid="uid://bnfo57c070cll" path="res://Scenes/Puppets/Scripts/PuppetTemplate.cs" id="1_kgmh0"]
[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://Scenes/Puppets/HealthBar.tscn" id="1_sxlrs"]
+[ext_resource type="PackedScene" uid="uid://cdoguwlxehbpg" path="res://Scenes/Puppets/StatusContainer.tscn" id="3_hguc7"]
-[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite")]
+[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite", "_statusContainer")]
script = ExtResource("1_kgmh0")
HealthBar = NodePath("ProgressBar")
Sprite = NodePath("Sprite")
+_statusContainer = NodePath("StatusContainer")
[node name="Sprite" type="Sprite2D" parent="."]
@@ -15,3 +17,5 @@ offset_left = -50.0
offset_top = 32.0
offset_right = 50.0
offset_bottom = 52.0
+
+[node name="StatusContainer" parent="." instance=ExtResource("3_hguc7")]
diff --git a/Scenes/Puppets/Enemies/Parasifly/Parasifly.tscn b/Scenes/Puppets/Enemies/Parasifly/Parasifly.tscn
index d5ef4a24..7b2b873f 100644
--- a/Scenes/Puppets/Enemies/Parasifly/Parasifly.tscn
+++ b/Scenes/Puppets/Enemies/Parasifly/Parasifly.tscn
@@ -1,13 +1,17 @@
-[gd_scene load_steps=4 format=3 uid="uid://uvlux4t6h5de"]
+[gd_scene load_steps=5 format=3 uid="uid://uvlux4t6h5de"]
[ext_resource type="Script" uid="uid://btaqgieybx0ep" path="res://Scenes/Puppets/Enemies/Parasifly/P_Parasifly.cs" id="1_ci2ca"]
+[ext_resource type="PackedScene" uid="uid://cdoguwlxehbpg" path="res://Scenes/Puppets/StatusContainer.tscn" id="2_dq3sr"]
[ext_resource type="Texture2D" uid="uid://pngu3pw1pu4o" path="res://Scenes/Puppets/Enemies/Parasifly/Assets/Parasifly.png" id="2_g4o48"]
[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://Scenes/Puppets/HealthBar.tscn" id="3_f74ri"]
-[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite")]
+[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite", "_statusContainer")]
script = ExtResource("1_ci2ca")
HealthBar = NodePath("ProgressBar")
Sprite = NodePath("Sprite")
+_statusContainer = NodePath("StatusContainer")
+
+[node name="StatusContainer" parent="." instance=ExtResource("2_dq3sr")]
[node name="Sprite" type="Sprite2D" parent="."]
position = Vector2(0, -12)
diff --git a/Scenes/Puppets/Enemies/TheGWS/GWS.tscn b/Scenes/Puppets/Enemies/TheGWS/GWS.tscn
index 20f76e45..c77cac57 100644
--- a/Scenes/Puppets/Enemies/TheGWS/GWS.tscn
+++ b/Scenes/Puppets/Enemies/TheGWS/GWS.tscn
@@ -1,13 +1,19 @@
-[gd_scene load_steps=4 format=3 uid="uid://d1puw6fvmkrb5"]
+[gd_scene load_steps=5 format=3 uid="uid://d1puw6fvmkrb5"]
[ext_resource type="Script" uid="uid://3axgcdtdevtx" path="res://Scenes/Puppets/Enemies/TheGWS/P_TheGWS.cs" id="1_dlike"]
[ext_resource type="Texture2D" uid="uid://ci0a2h2eatjht" path="res://Scenes/Puppets/Enemies/TheGWS/Assets/GhostWolfSnake.png" id="2_77v4w"]
+[ext_resource type="PackedScene" uid="uid://cdoguwlxehbpg" path="res://Scenes/Puppets/StatusContainer.tscn" id="2_c7cx1"]
[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://Scenes/Puppets/HealthBar.tscn" id="3_mr0it"]
-[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite")]
+[node name="EnemPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite", "_statusContainer")]
script = ExtResource("1_dlike")
HealthBar = NodePath("ProgressBar")
Sprite = NodePath("Sprite")
+_statusContainer = NodePath("StatusContainer")
+
+[node name="StatusContainer" parent="." instance=ExtResource("2_c7cx1")]
+offset_top = -6.0
+offset_bottom = 30.0
[node name="Sprite" type="Sprite2D" parent="."]
position = Vector2(0, -54)
diff --git a/Scenes/Puppets/PlayerPuppet.tscn b/Scenes/Puppets/PlayerPuppet.tscn
index 2fd0a348..e0caec7c 100644
--- a/Scenes/Puppets/PlayerPuppet.tscn
+++ b/Scenes/Puppets/PlayerPuppet.tscn
@@ -1,13 +1,15 @@
-[gd_scene load_steps=4 format=3 uid="uid://eus17omen6yk"]
+[gd_scene load_steps=5 format=3 uid="uid://eus17omen6yk"]
[ext_resource type="Script" uid="uid://kkb4qp3s86n3" path="res://Scenes/Puppets/Scripts/PlayerPuppet.cs" id="1_f4rea"]
[ext_resource type="Texture2D" uid="uid://b6fkei0i83vte" path="res://SharedAssets/Character1.png" id="2_affso"]
[ext_resource type="PackedScene" uid="uid://bgomxovxs7sr8" path="res://Scenes/Puppets/HealthBar.tscn" id="3_rechw"]
+[ext_resource type="PackedScene" uid="uid://cdoguwlxehbpg" path="res://Scenes/Puppets/StatusContainer.tscn" id="4_o8yrn"]
-[node name="PlayerPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite")]
+[node name="PlayerPuppet" type="Node2D" node_paths=PackedStringArray("HealthBar", "Sprite", "_statusContainer")]
script = ExtResource("1_f4rea")
HealthBar = NodePath("ProgressBar")
Sprite = NodePath("Sprite")
+_statusContainer = NodePath("StatusContainer")
[node name="Sprite" type="Sprite2D" parent="."]
texture = ExtResource("2_affso")
@@ -17,3 +19,5 @@ offset_left = -50.0
offset_top = 32.0
offset_right = 50.0
offset_bottom = 52.0
+
+[node name="StatusContainer" parent="." instance=ExtResource("4_o8yrn")]
diff --git a/Scenes/Puppets/Scripts/DamageInstance.cs b/Scenes/Puppets/Scripts/DamageInstance.cs
new file mode 100644
index 00000000..f5406864
--- /dev/null
+++ b/Scenes/Puppets/Scripts/DamageInstance.cs
@@ -0,0 +1,26 @@
+using System;
+using Godot;
+
+///
+/// A class to transmit damage with the source of damage and intended target.
+/// Source should never be null, null target represents an indirect source of damage.
+///
+public class DamageInstance
+{
+ public int Damage { get; private set; }
+ public PuppetTemplate Target { get; private set; }
+ public PuppetTemplate Source { get; private set; }
+
+ public DamageInstance(int damage, PuppetTemplate source, PuppetTemplate target)
+ {
+ Damage = damage;
+ Source = source;
+ Target = target;
+ }
+
+ public DamageInstance ModifyDamage(int increase, int multiplier = 1)
+ {
+ Damage = (Damage + increase) * multiplier;
+ return this;
+ }
+}
diff --git a/Scenes/Puppets/Scripts/DamageInstance.cs.uid b/Scenes/Puppets/Scripts/DamageInstance.cs.uid
new file mode 100644
index 00000000..5f754b86
--- /dev/null
+++ b/Scenes/Puppets/Scripts/DamageInstance.cs.uid
@@ -0,0 +1 @@
+uid://cj3nureovetrp
diff --git a/Scenes/Puppets/Scripts/PlayerPuppet.cs b/Scenes/Puppets/Scripts/PlayerPuppet.cs
index 6c283c88..513c7965 100644
--- a/Scenes/Puppets/Scripts/PlayerPuppet.cs
+++ b/Scenes/Puppets/Scripts/PlayerPuppet.cs
@@ -19,9 +19,9 @@ public override void _Ready()
base._Ready();
}
- public override void TakeDamage(int amount)
+ public override void TakeDamage(DamageInstance dmg)
{
- base.TakeDamage(amount);
+ base.TakeDamage(dmg);
Stats.CurrentHealth = CurrentHealth;
}
diff --git a/Scenes/Puppets/Scripts/PuppetTemplate.cs b/Scenes/Puppets/Scripts/PuppetTemplate.cs
index 24651cc9..5257d67f 100644
--- a/Scenes/Puppets/Scripts/PuppetTemplate.cs
+++ b/Scenes/Puppets/Scripts/PuppetTemplate.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using Godot;
/**
@@ -113,8 +115,11 @@ protected virtual void Kill()
}
#endregion
- public virtual void TakeDamage(int amount)
+ public virtual void TakeDamage(DamageInstance dmg)
{
+ BattleDirector.Harbinger.Instance.InvokeOnDamageInstance(dmg);
+ int amount = dmg.Damage;
+
amount = Math.Max(0, amount); //Should not be able to heal from damage.
if (CurrentHealth <= 0 || amount == 0)
return; //Only check if hp would change
@@ -148,4 +153,45 @@ public int GetCurrentHealth()
{
return CurrentHealth;
}
+
+ #region Status Effects
+ ///
+ /// The visual indicators for status effects.
+ ///
+ [Export]
+ protected GridContainer _statusContainer;
+ const int MaxStatuses = 8;
+ protected List StatusEffects = new List();
+
+ ///
+ /// Returns true if it could be successfully added as a new status effect. False if it is stacking, or can't add.
+ /// ONLY call this from within BattleDirector's AddStatus
+ ///
+ /// The status effect to add.
+ ///
+ public bool AddStatusEffect(StatusEffect effect)
+ {
+ int index = StatusEffects.FindIndex(sEff => sEff.StatusName == effect.StatusName);
+ if (index != -1) //If status of same name -> stack -> return false
+ {
+ StatusEffects[index].StackEffect(effect);
+ return false;
+ }
+ if (StatusEffects.Count >= MaxStatuses)
+ return false; //Max status effects -> return false
+
+ //Add status effect -> true
+ _statusContainer.AddChild(effect);
+ effect.SetOwner(this);
+ StatusEffects.Add(effect);
+ return true;
+ }
+
+ public void RemoveStatusEffect(StatusEffect effect)
+ {
+ _statusContainer.RemoveChild(effect);
+ StatusEffects.Remove(effect);
+ effect.QueueFree();
+ }
+ #endregion
}
diff --git a/Scenes/Puppets/StatusContainer.tscn b/Scenes/Puppets/StatusContainer.tscn
new file mode 100644
index 00000000..4a462b46
--- /dev/null
+++ b/Scenes/Puppets/StatusContainer.tscn
@@ -0,0 +1,10 @@
+[gd_scene format=3 uid="uid://cdoguwlxehbpg"]
+
+[node name="StatusContainer" type="GridContainer"]
+offset_left = -38.0
+offset_top = -69.0
+offset_right = 38.0
+offset_bottom = -33.0
+theme_override_constants/h_separation = 4
+theme_override_constants/v_separation = 4
+columns = 4