From 94052573b3bd7eb6597bc3326ce54a99512b9ba5 Mon Sep 17 00:00:00 2001 From: Marvin Date: Fri, 23 Oct 2026 23:54:18 +0200 Subject: [PATCH] blackout duration now adds up --- .../src/ActionBlocks.Game/Items/Items.cs | 3 +- .../ItemAndMatchTests.cs | 30 +++++++++++++++++++ actionBlocks_website/src/theme/assets.ts | 2 +- doc/game-rules.md | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/actionBlocks_server/src/ActionBlocks.Game/Items/Items.cs b/actionBlocks_server/src/ActionBlocks.Game/Items/Items.cs index 3d569ff..e3bc148 100644 --- a/actionBlocks_server/src/ActionBlocks.Game/Items/Items.cs +++ b/actionBlocks_server/src/ActionBlocks.Game/Items/Items.cs @@ -46,6 +46,7 @@ public sealed class InvisibilityItem : IItem public string Id => "blind"; public string DisplayName => "Blackout"; + // Stacks: a second Blackout adds its full duration to whatever is left. public void Apply(ItemContext context) => - context.Target.InvisibleUntil = Math.Max(context.Target.InvisibleUntil, context.Now + DurationSeconds); + context.Target.InvisibleUntil = Math.Max(context.Target.InvisibleUntil, context.Now) + DurationSeconds; } diff --git a/actionBlocks_server/tests/ActionBlocks.Game.Tests/ItemAndMatchTests.cs b/actionBlocks_server/tests/ActionBlocks.Game.Tests/ItemAndMatchTests.cs index 7971f42..43b8ad2 100644 --- a/actionBlocks_server/tests/ActionBlocks.Game.Tests/ItemAndMatchTests.cs +++ b/actionBlocks_server/tests/ActionBlocks.Game.Tests/ItemAndMatchTests.cs @@ -90,6 +90,36 @@ public class ItemTests Assert.False(target.IsInvisible); } + [Fact] + public void Blackout_durations_stack() + { + var target = CreatePlayer("b"); + target.Start(); + + new InvisibilityItem().Apply(Context(target, target, now: 10)); + new InvisibilityItem().Apply(Context(target, target, now: 12)); + + target.Update(0.01, 17.9); + Assert.True(target.IsInvisible); + target.Update(0.01, 18.1); + Assert.False(target.IsInvisible); + } + + [Fact] + public void Blackout_after_expiry_starts_fresh() + { + var target = CreatePlayer("b"); + target.Start(); + + new InvisibilityItem().Apply(Context(target, target, now: 10)); + new InvisibilityItem().Apply(Context(target, target, now: 20)); + + target.Update(0.01, 23.9); + Assert.True(target.IsInvisible); + target.Update(0.01, 24.1); + Assert.False(target.IsInvisible); + } + [Fact] public void Registry_contains_five_unique_items() { diff --git a/actionBlocks_website/src/theme/assets.ts b/actionBlocks_website/src/theme/assets.ts index 8e9ee7c..2280728 100644 --- a/actionBlocks_website/src/theme/assets.ts +++ b/actionBlocks_website/src/theme/assets.ts @@ -47,7 +47,7 @@ export const itemVisuals: Record = { icon: asset('assets/items/blind.svg'), name: 'Blackout', color: '#c452ff', - description: 'Hides placed blocks and the drop shadow for 4 seconds.', + description: 'Hides placed blocks and the drop shadow for 4 seconds. Stacks.', }, }; diff --git a/doc/game-rules.md b/doc/game-rules.md index 48c6b72..c4e53d0 100644 --- a/doc/game-rules.md +++ b/doc/game-rules.md @@ -141,7 +141,7 @@ A player is eliminated when any of these happens: | `clear_line` | Clear Line | Removes the bottom row of the target. Items in it are lost. | `ClearBottomLineItem` | | `nuke` | Wipe Out | Removes **all** locked blocks of the target. The active piece is unaffected. | `ClearBoardItem` | | `swap` | Switch | The user and the target exchange all locked blocks. Active pieces, hold, queues, inventories and pending garbage stay with their owners. If an active piece now overlaps, it is pushed up. Does nothing when used on yourself. | `SwapBoardItem` | -| `blind` | Blackout | For 4 seconds, the target cannot see their locked blocks or their ghost piece. Only the falling piece stays visible. Re-applying extends the effect to *now + 4 s*. | `InvisibilityItem` | +| `blind` | Blackout | For 4 seconds, the target cannot see their locked blocks or their ghost piece. Only the falling piece stays visible. Durations stack: each further Blackout adds 4 s to the time that is left. | `InvisibilityItem` | Every item can also be used on yourself: target `1`. *Wipe Out* and *Clear Line* are useful defensively.