From 47dfb4078787f48528ec6c17326ac7bd7e61a6db Mon Sep 17 00:00:00 2001 From: Russell L Bates Date: Wed, 2 Jul 2025 16:13:30 -0500 Subject: [PATCH] Add visual effect for shield break --- core/Constants.lua | 1 + docs/Visual_Language.md | 1 + docs/wizard.md | 1 + systems/TokenManager.lua | 17 +++++++++++++++++ systems/VisualResolver.lua | 31 +++++++++++++++++++++++++++++++ vfx.lua | 14 ++++++++++++++ vfx/initializeParticles.lua | 1 + 7 files changed, 66 insertions(+) diff --git a/core/Constants.lua b/core/Constants.lua index 5f228ce..52fa208 100644 --- a/core/Constants.lua +++ b/core/Constants.lua @@ -430,6 +430,7 @@ Constants.VFXType = { -- Defense effects SHIELD = "shield", REFLECT = "reflect", + SHIELD_BREAK = "shield_break", -- Spell timing effects SPELL_ACCELERATE = "spell_accelerate", diff --git a/docs/Visual_Language.md b/docs/Visual_Language.md index f940c7d..8a67d33 100644 --- a/docs/Visual_Language.md +++ b/docs/Visual_Language.md @@ -45,6 +45,7 @@ The visual language is organized by: | Generic | Acceleration | Slot | `SPELL_ACCELERATE` | sparkle | LIME | Speed-up animation | | Generic | Cancel | Slot | `SPELL_CANCEL` | impactRing | CRIMSON | Spell interruption effect | | Generic | Echo | Slot | `SPELL_ECHO` | sparkle | BONE | Spell replication effect | +| Generic | Shield Break | Self | `SHIELD_BREAK` | impactRing | SMOKE | Flash when a shield collapses | ## Shield Visual Language diff --git a/docs/wizard.md b/docs/wizard.md index 75b99e3..260c98f 100644 --- a/docs/wizard.md +++ b/docs/wizard.md @@ -63,6 +63,7 @@ Each `Wizard` instance maintains a comprehensive set of state variables: * Shield Spells: Calls local `createShield` helper, potentially applies elevation, leaves slot active with tokens (`isShield=true`). * Normal Spells: Applies damage (`target.health`), status effects (burn, stun), position changes (range, elevation), mana manipulation (lock, delay) based on `effect` table. Returns caster's tokens (`requestReturnAnimation`/`manaPool:returnToken`), resets caster's slot (`resetSpellSlot`). * **`Wizard:handleShieldBlock(slotIndex, blockedSpell)`:** (Called on the target wizard). Consumes tokens from the specified shield slot based on `blockedSpell.shieldBreaker`, returns consumed tokens, and calls `resetSpellSlot` if the shield breaks (runs out of tokens). +* When a shield collapses from losing its last token, `EventRunner` plays the `SHIELD_BREAK` visual effect at the defender's position for clarity. * **`Wizard:resetSpellSlot(slotIndex)`:** Utility function to reset all properties of a spell slot to default/inactive state and clear its token list. Used after normal casts, cancellations, or shield breaks. * **`Wizard:canPayManaCost(costOrFn[, target])`:** Checks if a mana cost can be paid without consuming tokens. `costOrFn` may be a static table or the `getCost` function from a spell definition. The function returns a reservation detail table or `nil` if the cost can't currently be met. * **`Wizard:freeAllSpells()`:** Cancels all active spells/shields, returns their tokens, and resets the corresponding slots. diff --git a/systems/TokenManager.lua b/systems/TokenManager.lua index f16d4c5..a31bc69 100644 --- a/systems/TokenManager.lua +++ b/systems/TokenManager.lua @@ -442,6 +442,23 @@ function TokenManager.checkFizzleCondition(wizard, slotIndex, removedToken) -- If slot has no tokens left, reset it if not slot.tokens or #slot.tokens == 0 then print("[TOKEN MANAGER] Spell in slot " .. slotIndex .. " fizzled - no tokens left") + + if slot.isShield and wizard.gameState and wizard.gameState.eventRunner then + local EventRunner = require("systems.EventRunner") + local Constants = require("core.Constants") + local breakEvent = { + type = "EFFECT", + source = Constants.TargetType.TARGET, + target = Constants.TargetType.TARGET, + effectType = "shield_break", + shieldType = slot.defenseType, + vfxParams = { x = wizard.x, y = wizard.y }, + rangeBand = wizard.rangeBand, + elevation = wizard.elevation, + } + wizard.gameState.eventRunner.processEvents({breakEvent}, wizard, nil) + end + wizard:resetSpellSlot(slotIndex) return true end diff --git a/systems/VisualResolver.lua b/systems/VisualResolver.lua index 8411bca..8519705 100644 --- a/systems/VisualResolver.lua +++ b/systems/VisualResolver.lua @@ -202,6 +202,37 @@ function VisualResolver.pick(event) return baseTemplate, opts end + + -- PRIORITY 3b: Handle shield break event + if event.effectType == "shield_break" then + baseTemplate = Constants.VFXType.SHIELD_BREAK + selectionPath = "SHIELD_BREAK" + + VisualResolver.debug("Handling shield break effect") + + local color = DEFAULT_COLOR + if event.shieldType == Constants.ShieldType.BARRIER then + color = {1.0, 1.0, 0.3, 1.0} + elseif event.shieldType == Constants.ShieldType.WARD then + color = {0.3, 0.3, 1.0, 1.0} + elseif event.shieldType == Constants.ShieldType.FIELD then + color = {0.3, 1.0, 0.3, 1.0} + end + + local opts = { + color = color, + scale = 1.5, + motion = Constants.MotionStyle.PULSE, + addons = {}, + rangeBand = event.rangeBand, + elevation = event.elevation, + } + + VisualResolver.debug(string.format( + "Resolved shield break event to base=%s via %s", baseTemplate, selectionPath)) + + return baseTemplate, opts + end -- PRIORITY 4: Legacy manual VFX: If the event has manualVfx flag and effectType if event.manualVfx and event.effectType then diff --git a/vfx.lua b/vfx.lua index b4c0a13..897db0e 100644 --- a/vfx.lua +++ b/vfx.lua @@ -501,6 +501,18 @@ function VFX.init() sound = "shield", -- Use shield sound criticalAssets = {"impactRing", "sparkle"} -- Assets needed for shield hit }, + + shield_break = { + type = "shield_break", + duration = 1.0, + particleCount = 40, + startScale = 0.6, + endScale = 1.6, + color = Constants.Color.GRAY, + radius = 80, + sound = "shield_break", + criticalAssets = {"impactRing", "sparkle"} + }, -- Existing effects -- General impact effect (used for many spell interactions) @@ -1748,6 +1760,7 @@ VFX.updaters["conjure_base"] = ConjureEffect.update -- Conjuration template VFX.updaters["remote_base"] = RemoteEffect.update -- Remote effect template VFX.updaters["warp_base"] = RemoteEffect.update -- Warp uses remote logic VFX.updaters["shield_hit_base"] = ImpactEffect.update -- Shield hit template +VFX.updaters["shield_break"] = ImpactEffect.update -- Shield break uses impact logic -- Specific effect templates VFX.updaters["meteor"] = MeteorEffect.update -- Meteor effect @@ -1778,6 +1791,7 @@ VFX.drawers["conjure_base"] = ConjureEffect.draw -- Conjuration template VFX.drawers["remote_base"] = RemoteEffect.draw -- Remote effect template VFX.drawers["warp_base"] = RemoteEffect.draw -- Warp uses remote logic VFX.drawers["shield_hit_base"] = ImpactEffect.draw -- Shield hit template +VFX.drawers["shield_break"] = ImpactEffect.draw -- Shield break template -- Specific effect templates VFX.drawers["meteor"] = MeteorEffect.draw -- Meteor effect diff --git a/vfx/initializeParticles.lua b/vfx/initializeParticles.lua index 1905f4e..fe44bc3 100644 --- a/vfx/initializeParticles.lua +++ b/vfx/initializeParticles.lua @@ -30,6 +30,7 @@ local function initializeParticles(effect) ["remote_base"] = "remote", ["warp_base"] = "remote", ["shield_hit_base"] = "impact", + ["shield_break"] = "impact", -- Specific effect templates ["meteor"] = "meteor",