diff --git a/manifest.json b/manifest.json
index 7558b52..1f80e4e 100644
--- a/manifest.json
+++ b/manifest.json
@@ -8,5 +8,6 @@
"sources": [
"src/**/*.cs"
],
+ "test_sources": ["tests/*.cs"],
"depends": [ "characters_base>=1", "ui_base>=1", "items_base>=1", "quests_base>=1", "time_base>=1" ]
}
diff --git a/src/StatusEffect/StatusEffectItem.cs b/src/StatusEffect/StatusEffectItem.cs
index 8008bef..94bd7e0 100644
--- a/src/StatusEffect/StatusEffectItem.cs
+++ b/src/StatusEffect/StatusEffectItem.cs
@@ -45,6 +45,7 @@ namespace Cthangover.Core.Cards.StatusEffect
Name = info.Name;
Description = info.Description;
Duration = info.Duration;
+ RemainingTurns = info.Duration;
EffectType = info.Type;
Icon = TextureUtils.LoadFromModGroup("characters", info.Icon);
Actions = StatusEffectActionFactory.Instance.Get(info.Actions);
diff --git a/src/StatusEffect/StatusEffectQueue.cs b/src/StatusEffect/StatusEffectQueue.cs
index d673469..b378423 100644
--- a/src/StatusEffect/StatusEffectQueue.cs
+++ b/src/StatusEffect/StatusEffectQueue.cs
@@ -134,21 +134,13 @@ namespace Cthangover.Core.Cards.StatusEffect
/// Fires end-of-turn hooks on all active effects. Operates
/// on a defensive copy so that OnTurnEnd callbacks that
/// add or remove effects do not invalidate the enumerator.
- /// Decrements remaining turns and removes expired effects via
- /// (which also fires
- /// OnRemove).
+ /// Durations count down in only.
public void OnTurnEnd()
{
+ // Only the hook: the countdown belongs to OnTurnStart, a second one
+ // here halved every duration and expired effects without one.
foreach (var statusEffect in ActiveStatusEffects)
- {
statusEffect.Actions?.OnTurnEnd(owner);
- statusEffect.RemainingTurns--;
- if (statusEffect.RemainingTurns <= 0)
- {
- statusEffect.Actions?.OnFinalAction(owner);
- Remove(statusEffect.ID);
- }
- }
}
/// Pipes outgoing damage through every active effect's
diff --git a/src/WingroundBehaviour.cs b/src/WingroundBehaviour.cs
index f07ef96..3a152a2 100644
--- a/src/WingroundBehaviour.cs
+++ b/src/WingroundBehaviour.cs
@@ -75,17 +75,17 @@ namespace Cthangover.TurnBattle
audioService?.PlaySound("battle/winground", SoundType.UI);
var bg = GetNodeOrNull("TransitionBg");
- GameLogger.Log("BATTLE", $"Winground.Show: TransitionBg={(bg != null ? "found" : "NULL")}, texture={(bg?.Texture != null ? "has" : "null")}", LogLevel.Error);
+ GameLogger.Log("BATTLE", $"Winground.Show: TransitionBg={(bg != null ? "found" : "NULL")}, texture={(bg?.Texture != null ? "has" : "null")}", bg != null ? LogLevel.Debug : LogLevel.Error);
if (bg != null && bg.Texture == null)
{
var tex = Textures.Resolve("winground");
- GameLogger.Log("BATTLE", $"Winground.Show: ResolveTexture('winground') = {(tex != null ? "loaded" : "NULL")}", LogLevel.Error);
+ GameLogger.Log("BATTLE", $"Winground.Show: ResolveTexture('winground') = {(tex != null ? "loaded" : "NULL")}", tex != null ? LogLevel.Debug : LogLevel.Error);
bg.Texture = tex;
}
data = BattleRequest.Current;
- GameLogger.Log("BATTLE_UI", $"Winground.Show: BattleData={(data != null ? "found" : "NULL")}", LogLevel.Error);
+ GameLogger.Log("BATTLE_UI", $"Winground.Show: BattleData={(data != null ? "found" : "NULL")}", data != null ? LogLevel.Debug : LogLevel.Error);
var expTracking = BuildExpTracking();
var defeatedEnemies = BuildDefeatedEnemies();
@@ -167,7 +167,7 @@ namespace Cthangover.TurnBattle
private void BuildCharExpDisplay(Dictionary expTracking)
{
var container = charExpContainer ?? GetNodeOrNull("Content/CharExpContainer");
- GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: charExpContainer export={(charExpContainer != null ? "found" : "NULL")}, GetNodeOrNull fallback={(container != null ? "found" : "NULL")}", LogLevel.Error);
+ GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: charExpContainer export={(charExpContainer != null ? "found" : "NULL")}, GetNodeOrNull fallback={(container != null ? "found" : "NULL")}", LogLevel.Debug);
if (container == null)
{
@@ -180,7 +180,7 @@ namespace Cthangover.TurnBattle
var charData = GameData.Instance.Runtime.CharacterData;
- GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: expTracking={(expTracking != null ? $"found ({expTracking.Count} entries)" : "NULL")}, BattleSet count={charData.BattleSet.Count}", LogLevel.Error);
+ GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: expTracking={(expTracking != null ? $"found ({expTracking.Count} entries)" : "NULL")}, BattleSet count={charData.BattleSet.Count}", LogLevel.Debug);
var battleSetList = charData.BattleSet.ToList();
@@ -208,7 +208,7 @@ namespace Cthangover.TurnBattle
reportCard.Setup(cardTemplate?.Image, name, earnedExp);
container.AddChild(reportCard);
- GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: added reportCard for '{characterType}', id='{info.ID}', name='{name}', earnedExp={earnedExp}, cardTemplate={(cardTemplate != null ? "found" : "NULL")}, icon={(cardTemplate?.Image != null ? "has" : "null")}", LogLevel.Error);
+ GameLogger.Log("BATTLE_UI", $"BuildCharExpDisplay: added reportCard for '{characterType}', id='{info.ID}', name='{name}', earnedExp={earnedExp}, cardTemplate={(cardTemplate != null ? "found" : "NULL")}, icon={(cardTemplate?.Image != null ? "has" : "null")}", LogLevel.Debug);
}
else
{
@@ -247,16 +247,7 @@ namespace Cthangover.TurnBattle
if (string.IsNullOrWhiteSpace(entry.ItemId))
continue;
- var probability = Math.Clamp(entry.Probability, 0, 100);
- if (probability <= 0)
- continue;
- if (probability < 100 && random.Next(1, 101) > probability)
- continue;
-
- var count = entry.MinCount == entry.MaxCount
- ? entry.MinCount
- : random.Next(entry.MinCount, entry.MaxCount + 1);
-
+ var count = entry.Roll(random);
if (count > 0)
TryAddLootItem(loot, entry.ItemId, count);
}
diff --git a/tests/StatusEffectQueueTests.cs b/tests/StatusEffectQueueTests.cs
new file mode 100644
index 0000000..dd8ade7
--- /dev/null
+++ b/tests/StatusEffectQueueTests.cs
@@ -0,0 +1,43 @@
+namespace Cthangover.Mods.TurnBattle.Tests
+{
+ using Cthangover.Core.Cards.StatusEffect;
+ using Cthangover.Core.Characters;
+ using Cthangover.Core.Mods.Testing;
+
+ ///
+ /// Lifetime of status effects: the countdown runs once per turn start, a
+ /// duration from the effect's data counts like an explicit one, and the
+ /// turn-end hook does not shorten an effect a second time.
+ ///
+ public class StatusEffectQueueTests
+ {
+ private const string Stun = "effect/physics/stun";
+
+ [ModTest("status/data_duration_expires")]
+ static void DataDurationExpires(ModTestContext ctx)
+ {
+ var queue = StatusEffectService.Of(new Character { ID = "status_test" });
+
+ ModAssert.True(queue.Add(Stun), "stun applied with its data duration");
+ queue.OnTurnStart();
+ queue.OnTurnStart();
+ ModAssert.True(queue.HasStun(), "stun still active after 2 of 3 turns");
+ queue.OnTurnStart();
+ ModAssert.False(queue.HasStun(), "stun expired after its 3 turns");
+ }
+
+ [ModTest("status/turn_end_does_not_shorten")]
+ static void TurnEndDoesNotShorten(ModTestContext ctx)
+ {
+ var queue = StatusEffectService.Of(new Character { ID = "status_test" });
+
+ queue.Add(Stun, 2);
+ queue.OnTurnEnd();
+ queue.OnTurnStart();
+ ModAssert.True(queue.HasStun(), "one turn of two left after a full turn");
+ queue.OnTurnEnd();
+ queue.OnTurnStart();
+ ModAssert.False(queue.HasStun(), "expired after two turns");
+ }
+ }
+}