Skip to content

Commit 489869f

Browse files
authored
Status effects (#158)
* Status Effects Implemented Refactored damage taking into DamageInstances Added StatusEffect class Added Block Status as an example * Refactored targetting to include Player targetting * Add Mulligan status effect Allows the player to ignore the combo penalty of a missed note
1 parent 40ca647 commit 489869f

21 files changed

Lines changed: 526 additions & 82 deletions

Classes/Notes/Note.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,7 @@ public Note(
3535
Id = id;
3636
Name = name;
3737
Owner = owner;
38-
NoteEffect =
39-
noteEffect
40-
?? (
41-
(BD, source, timing) =>
42-
{
43-
Array.ForEach(
44-
BD.GetTargets(source), //Ok, sure
45-
enemy => enemy.TakeDamage((int)timing * source._baseVal)
46-
);
47-
}
48-
);
38+
NoteEffect = noteEffect;
4939
_baseVal = baseVal;
5040
Texture = texture;
5141
Tooltip = tooltip;
181 Bytes
Loading
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://cdlepg5fxfwn0"
6+
path="res://.godot/imported/Status_Block.png-e8facdf93b546460a44dfea789c90919.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://Classes/StatusEffects/Assets/Status_Block.png"
14+
dest_files=["res://.godot/imported/Status_Block.png-e8facdf93b546460a44dfea789c90919.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/hdr_compression=1
22+
compress/normal_map=0
23+
compress/channel_pack=0
24+
mipmaps/generate=false
25+
mipmaps/limit=-1
26+
roughness/mode=0
27+
roughness/src_normal=""
28+
process/fix_alpha_border=true
29+
process/premult_alpha=false
30+
process/normal_map_invert_y=false
31+
process/hdr_as_srgb=false
32+
process/hdr_clamp_exposure=false
33+
process/size_limit=0
34+
detect_3d/compress_to=1
201 Bytes
Loading
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Linq;
3+
using FunkEngine;
4+
using Godot;
5+
6+
/// <summary>
7+
/// Status Effect class.
8+
/// Preferably set up as a static default status, then apply status using GetInstance, but custom statuses can be defined elsewhere.
9+
/// Invoke StatusEnd to remove status.
10+
/// </summary>
11+
public partial class StatusEffect : TextureRect, IBattleEvent
12+
{ //TODO: Status effects that are permanent, and status effects that don't take up a slot/are invisible
13+
public static readonly string LoadPath = "res://Classes/StatusEffects/StatusIcon.tscn";
14+
public PuppetTemplate Sufferer { get; private set; }
15+
public int Count { get; private set; }
16+
17+
public string StatusName { get; private set; }
18+
19+
[Export]
20+
private Label CountLabel { get; set; }
21+
22+
internal delegate void StatusEndHandler(StatusEffect status);
23+
internal event StatusEndHandler StatusEnd;
24+
25+
#region DefaultStatuses
26+
private static readonly Action<BattleEventArgs, StatusEffect> BlockEffect = (e, self) =>
27+
{
28+
if (e is BattleDirector.Harbinger.OnDamageInstanceArgs dmgArgs)
29+
{
30+
if (dmgArgs.Dmg.Target != self.Sufferer || dmgArgs.Dmg.Damage <= 0)
31+
return;
32+
dmgArgs.Dmg.ModifyDamage(0, 0);
33+
self.DecCount();
34+
}
35+
};
36+
37+
/// <summary>
38+
/// On the owner receiving a damage instance, if valid (correct target and dmg > 0) sets damage to 0 and reduces count.
39+
/// </summary>
40+
public static readonly StatusEffect Block = new StatusEffect()
41+
.InitStatus(
42+
"Block",
43+
BlockEffect,
44+
BattleEffectTrigger.OnDamageInstance,
45+
GD.Load<Texture2D>("res://Classes/StatusEffects/Assets/Status_Block.png")
46+
)
47+
.SetTags(true);
48+
49+
private static readonly Action<BattleEventArgs, StatusEffect> MulliganEffect = (e, self) =>
50+
{
51+
if (e is not BattleDirector.Harbinger.NoteHitArgs { Timing: Timing.Miss })
52+
return;
53+
e.BD.NPB.SetIgnoreMiss(true); //Intercept the miss
54+
self.DecCount();
55+
};
56+
57+
/// <summary>
58+
/// If the player missed, take damage, but don't receive combo penalty.
59+
/// </summary>
60+
public static readonly StatusEffect Mulligan = new StatusEffect()
61+
.InitStatus(
62+
"Mulligan",
63+
MulliganEffect,
64+
BattleEffectTrigger.NoteHit,
65+
GD.Load<Texture2D>("res://Classes/StatusEffects/Assets/Status_Mulligan.png")
66+
)
67+
.SetTags(true);
68+
#endregion
69+
70+
private BattleEffectTrigger _trigger;
71+
private Action<BattleEventArgs, StatusEffect> _effect;
72+
73+
public BattleEffectTrigger GetTrigger()
74+
{
75+
return _trigger;
76+
}
77+
78+
public void OnTrigger(BattleEventArgs e)
79+
{
80+
_effect(e, this);
81+
}
82+
83+
public StatusEffect InitStatus(
84+
string name,
85+
Action<BattleEventArgs, StatusEffect> effect,
86+
BattleEffectTrigger trigger,
87+
Texture2D texture = null
88+
)
89+
{
90+
_effect = effect;
91+
_trigger = trigger;
92+
StatusName = name;
93+
Texture = texture;
94+
return this;
95+
}
96+
97+
public StatusEffect GetInstance(int count = 1)
98+
{
99+
StatusEffect result = GD.Load<PackedScene>(LoadPath).Instantiate<StatusEffect>();
100+
result.SetCount(count);
101+
result.InitStatus(Name, _effect, _trigger, Texture);
102+
result.SetTags(_stackable, _refreshes);
103+
return result;
104+
}
105+
106+
public void SetOwner(PuppetTemplate owner)
107+
{
108+
Sufferer = owner;
109+
}
110+
111+
public void IncCount(int count = 1)
112+
{
113+
SetCount(Count + count);
114+
}
115+
116+
public void DecCount(int count = 1)
117+
{
118+
SetCount(Count - count);
119+
}
120+
121+
public void SetCount(int count)
122+
{
123+
Count = count;
124+
CountLabel.Text = Count.ToString();
125+
if (Count <= 0)
126+
{
127+
StatusEnd?.Invoke(this);
128+
}
129+
}
130+
131+
/// <summary>
132+
/// Re-applying a status increases the count.
133+
/// </summary>
134+
private bool _stackable;
135+
136+
/// <summary>
137+
/// Re-applying a status sets the count to the higher counte
138+
/// </summary>
139+
private bool _refreshes;
140+
141+
public StatusEffect SetTags(bool stackable = false, bool refreshes = false)
142+
{
143+
_stackable = stackable;
144+
_refreshes = refreshes;
145+
return this;
146+
}
147+
148+
//Called if a puppet is receiving a duplicate effect.
149+
public void StackEffect(StatusEffect incomingEffect)
150+
{
151+
if (incomingEffect.StatusName != StatusName)
152+
return;
153+
if (_stackable)
154+
{
155+
IncCount(incomingEffect.Count);
156+
}
157+
158+
if (_refreshes && incomingEffect.Count >= Count)
159+
{
160+
SetCount(incomingEffect.Count);
161+
}
162+
}
163+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://wawjisy70w1v
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[gd_scene load_steps=4 format=3 uid="uid://opqtl7khulko"]
2+
3+
[ext_resource type="Script" uid="uid://wawjisy70w1v" path="res://Classes/StatusEffects/StatusEffect.cs" id="1_2adc3"]
4+
5+
[sub_resource type="Gradient" id="Gradient_y1lef"]
6+
offsets = PackedFloat32Array(1)
7+
colors = PackedColorArray(1, 1, 1, 1)
8+
9+
[sub_resource type="GradientTexture2D" id="GradientTexture2D_2adc3"]
10+
gradient = SubResource("Gradient_y1lef")
11+
width = 16
12+
height = 16
13+
14+
[node name="StatusIcon" type="TextureRect" node_paths=PackedStringArray("CountLabel")]
15+
custom_minimum_size = Vector2(8, 8)
16+
offset_right = 8.0
17+
offset_bottom = 8.0
18+
texture = SubResource("GradientTexture2D_2adc3")
19+
script = ExtResource("1_2adc3")
20+
CountLabel = NodePath("Count")
21+
22+
[node name="Count" type="Label" parent="."]
23+
layout_mode = 1
24+
anchors_preset = 15
25+
anchor_right = 1.0
26+
anchor_bottom = 1.0
27+
offset_left = 2.0
28+
offset_top = 4.0
29+
offset_bottom = 2.0
30+
grow_horizontal = 2
31+
grow_vertical = 2
32+
theme_override_colors/font_color = Color(0, 0, 0, 1)
33+
theme_override_font_sizes/font_size = 12
34+
text = "0"
35+
horizontal_alignment = 2
36+
vertical_alignment = 2

Globals/FunkEngineNameSpace.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ public enum Timing
224224

225225
public enum Targetting
226226
{
227+
Player,
227228
First,
228229
All,
229230
}
@@ -237,6 +238,8 @@ public enum BattleEffectTrigger
237238
OnLoop,
238239
OnBattleStart,
239240
OnBattleEnd,
241+
OnDamageInstance,
242+
OnDamageTaken,
240243
}
241244

242245
public enum Stages

Globals/Scribe.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public partial class Scribe : Node
2222
1,
2323
(director, note, timing) =>
2424
{
25-
director.Player.TakeDamage((3 - (int)timing) * note.GetBaseVal());
25+
int dmg = (3 - (int)timing) * note.GetBaseVal();
26+
director.Player.TakeDamage(new DamageInstance(dmg, null, director.Player));
2627
}
2728
),
2829
new Note(
@@ -36,7 +37,7 @@ public partial class Scribe : Node
3637
{
3738
if (timing == Timing.Miss)
3839
return;
39-
director.GetFirstEnemy()?.TakeDamage((int)timing * note.GetBaseVal());
40+
director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
4041
}
4142
),
4243
new Note(
@@ -50,7 +51,7 @@ public partial class Scribe : Node
5051
{
5152
if (timing == Timing.Miss)
5253
return;
53-
director.GetFirstEnemy()?.TakeDamage(note.GetBaseVal() * (int)timing);
54+
director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
5455
}
5556
),
5657
new Note(
@@ -78,8 +79,9 @@ public partial class Scribe : Node
7879
{
7980
if (timing == Timing.Miss)
8081
return;
81-
director.Player.Heal((int)timing * note.GetBaseVal());
82-
director.GetFirstEnemy()?.TakeDamage((int)timing * note.GetBaseVal());
82+
int dmg = (int)timing * note.GetBaseVal();
83+
director.Player.Heal(dmg);
84+
director.DealDamage(note, dmg, director.Player);
8385
}
8486
),
8587
new Note(
@@ -93,13 +95,13 @@ public partial class Scribe : Node
9395
{
9496
if (timing == Timing.Miss)
9597
return;
96-
director.GetFirstEnemy()?.TakeDamage((int)timing + note.GetBaseVal());
98+
director.DealDamage(note, (int)timing * note.GetBaseVal(), director.Player);
9799
},
98100
0.25f
99101
),
100102
new Note(
101103
6,
102-
"Play-c-HoldBlock",
104+
"PlayerBlock",
103105
"Gives player one charge of block.",
104106
GD.Load<Texture2D>("res://Classes/Notes/Assets/Note_PlayerBlock.png"),
105107
null,
@@ -108,7 +110,7 @@ public partial class Scribe : Node
108110
{
109111
if (timing == Timing.Miss)
110112
return;
111-
//director.Player.GainShield(note.GetBaseVal()); //todo: should scale with timing????
113+
director.AddStatus(Targetting.Player, StatusEffect.Block.GetInstance()); //todo: should scale with timing????
112114
}
113115
),
114116
};
@@ -203,7 +205,10 @@ public partial class Scribe : Node
203205
1,
204206
(e, self, val) =>
205207
{
206-
e.BD.GetFirstEnemy()?.TakeDamage(val);
208+
if (e is not BattleDirector.Harbinger.NoteHitArgs noteHitArgs)
209+
return;
210+
if (noteHitArgs.Timing != Timing.Miss)
211+
e.BD.DealDamage(Targetting.First, val, null);
207212
}
208213
),
209214
}
@@ -221,7 +226,7 @@ public partial class Scribe : Node
221226
5,
222227
(e, self, val) =>
223228
{
224-
e.BD.GetFirstEnemy()?.TakeDamage(val);
229+
e.BD.DealDamage(Targetting.First, val, null);
225230
}
226231
),
227232
}

0 commit comments

Comments
 (0)