diff --git a/lucksmith.lua b/lucksmith.lua index d0803bf..a85e98f 100644 --- a/lucksmith.lua +++ b/lucksmith.lua @@ -937,49 +937,11 @@ function TIC() dmg = math.max(0, dmg - weaken_reduction) if not damage_applied then - -- Sequence: flash screen, flash health, adjust hearts + -- Sequence: flash background then run the unified sequenced damage effect + queue_effect(flash_background, 40) queue_effect(function() - sfx(0, "E-4", 12, 0) -- play damage sound - for flash = 8, 1, -1 do - -- Tint screen red on alternating frames - if flash % 4 < 2 then -- Flash on/off every 2 frames - poke(0x3FC0, 255) -- Red tint - else - poke(0x3FC0, original_palette[0]) - end - flash = flash - 1 - coroutine.yield() - -- When effect ends, ensure palette is restored - if flash == 0 then - poke(0x3FC0, original_palette[0]) - end - end - - end, 40) - queue_effect(function() - local flash_damage = current_damage.enemy + dmg - sfx(0, "E-4", 12, 0) -- play damage sound - for flash = 8, 1, -1 do - -- Flash health bar red on alternating frames - if flash % 2 == 0 then - current_damage.enemy = flash_damage - else - current_damage.enemy = 0 - end - coroutine.yield() - end - current_damage.enemy = flash_damage - end, 60) - queue_effect(function() - local old_hp = enemy.hp - for step = 1, dmg do - enemy.hp = old_hp - step - sfx(0, "E-4", 12, 0) -- play damage sound - for wait = 1, 30 do - coroutine.yield() - end - end - end, 30) + sequenced_damage("enemy", dmg, { flash_cycles = 8, damage_display_frames = 20, step_wait = 10 }) + end, 0) end msgtxt = "You win!" elseif enemy.total > player.total then @@ -993,17 +955,29 @@ function TIC() dmg = math.max(0, dmg - weaken_reduction) if not damage_applied then - - -- Apply shield reduction + -- Apply shield reduction (calculate now, but do visual/HP changes via sequenced_damage) local shield_reduction = player.shield or 0 - dmg = math.max(0, dmg - shield_reduction) - player.hp = player.hp - dmg - current_damage.player = current_damage.player + dmg - -- Remove shield after taking damage - if shield_reduction > 0 and dmg < (2 * enemy.mult) then - player.shield = math.max(0, (player.shield or 0) - 1) + local final_dmg = math.max(0, dmg - shield_reduction) + + -- Queue flash + sequenced stepped HP change for player + queue_effect(flash_background, 40) + queue_effect(function() + sequenced_damage("player", final_dmg, { flash_cycles = 8, damage_display_frames = 20, step_wait = 10 }) + end, 0) + + -- After damage is applied, remove one shield if it was used and set shake + if shield_reduction > 0 then + queue_effect(function() + player.shield = math.max(0, (player.shield or 0) - 1) + shake = 10 + coroutine.yield() + end, 0) + else + queue_effect(function() + shake = 10 + coroutine.yield() + end, 0) end - shake = 10 end msgtxt = enemy.name .. " wins!" else @@ -1114,57 +1088,125 @@ function TIC() end run_effects() end --- === EFFECT QUEUE SYSTEM === --- Holds coroutines for queued effects +-- === EFFECTS HELPERS === + +-- flash_background(frames) +-- frames: optional number of frames to flash (defaults to 40) +function flash_background(frames) + local f = frames or 40 + -- play impact sound safely + pcall(function() sfx(0, "E-4", 12, 0) end) + for i = 1, f do + if (i % 4) < 2 then + poke(0x3FC0, 255) -- red tint + else + poke(0x3FC0, original_palette[0]) + end + coroutine.yield() + end + -- ensure palette restored + poke(0x3FC0, original_palette[0]) +end + +-- sequenced_damage(target_key, amount, opts) +-- target_key: "enemy" or "player" (used to pick table and current_damage key) +-- amount: integer damage to apply +-- opts: table optional fields: +-- flash_cycles (default 8) - number of flash toggles for damage readout +-- damage_display_frames (default 20) - frames to hold the damage readout steady +-- step_wait (default 10) - frames to wait between each HP decrement step +function sequenced_damage(target_key, amount, opts) + opts = opts or {} + local flash_cycles = opts.flash_cycles or 8 + local damage_display_frames = opts.damage_display_frames or 20 + local step_wait = opts.step_wait or 10 + + local target = (target_key == "enemy") and enemy or player + local dmg_key = (target_key == "enemy") and "enemy" or "player" + + -- play impact sound once + pcall(function() sfx(0, "E-4", 12, 0) end) + + -- Flash the damage readout on/off for visibility + for i = 1, flash_cycles do + if (i % 2) == 0 then + current_damage[dmg_key] = amount + else + current_damage[dmg_key] = 0 + end + coroutine.yield() + end + + -- Hold the damage readout steady for a short period + current_damage[dmg_key] = amount + for i = 1, damage_display_frames do + coroutine.yield() + end + + -- Apply HP reduction stepwise so the hearts animate + for step = 1, amount do + target.hp = math.max(0, target.hp - 1) + pcall(function() sfx(0, "E-4", 12, 0) end) + for w = 1, step_wait do + coroutine.yield() + end + end + + -- Clear damage display + current_damage[dmg_key] = 0 + coroutine.yield() +end + +-- === EFFECT QUEUE (restore missing functions) === +-- simple sequential effect queue used by queued visual/audio effects + effect_queue = {} effect_queue_timers = {} --- Add a coroutine to the queue, with optional duration argument (frames to wait after effect yields) -function queue_effect(fn, duration) - local co = coroutine.create(fn) - table.insert(effect_queue, co) - table.insert(effect_queue_timers, duration or 1) +-- queue_effect(fn, delay) +-- fn: function() coroutine.yield() ... end -- a coroutine body (no explicit coroutine.create needed here) +-- delay: optional number of frames to wait before starting this effect +function queue_effect(fn, delay) + local co = coroutine.create(fn) + table.insert(effect_queue, co) + table.insert(effect_queue_timers, delay or 0) end --- Run all effects in the queue (call this each frame) +-- run_effects() -- call once per frame (already called at end of TIC) +-- Processes only the first queued effect (sequential). If the effect has an initial delay, +-- we decrement the timer each frame and only resume the coroutine once the delay reaches 0. function run_effects() - if #effect_queue == 0 then - return - end - -- Only run the first effect in the queue (sequential) - local co = effect_queue[1] - local timer = effect_queue_timers[1] - if timer > 0 then - effect_queue_timers[1] = timer - 1 - return - end - local ok, _ = coroutine.resume(co) - if not ok or coroutine.status(co) == "dead" then - table.remove(effect_queue, 1) - table.remove(effect_queue_timers, 1) - else - -- Reset timer for this effect before next resume - effect_queue_timers[1] = effect_queue_timers[1] or 1 - end + if #effect_queue == 0 then return end + + -- only run the front effect to keep effects sequential + local timer = effect_queue_timers[1] + if timer > 0 then + effect_queue_timers[1] = timer - 1 + return + end + + local co = effect_queue[1] + local ok, err = coroutine.resume(co) + if not ok then + -- error: remove and discard the effect to avoid blocking the queue + -- (keep message-free to avoid spamming; developer can add logging if needed) + table.remove(effect_queue, 1) + table.remove(effect_queue_timers, 1) + return + end + + -- if coroutine finished, remove it; otherwise leave it for the next resume + if coroutine.status(co) == "dead" then + table.remove(effect_queue, 1) + table.remove(effect_queue_timers, 1) + end end --- Returns true if any effects are running function effects_running() - return #effect_queue > 0 + return #effect_queue > 0 end --- Example: wrap a card effect for the queue -function make_card_effect(effect, timing, player_won) - return function() - if timing == "post_result" then - effect.use(player, enemy, player_won) - else - effect.use(player, enemy) - end - coroutine.yield() -- allow for animation/timing if needed - end -end -- -- 000:3200000032000000322200003332000033322200333332003333322200000000 -- 001:0ff00ff0f22ff33ff22f223ff222223f0f2222f000f22f00000ff00000000000 @@ -1250,4 +1292,56 @@ end -- -- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57 -- +-- 020:022222ff0222222f022222220222222202222222002222220002222200000000 +-- 021:ff222220f222f220222fff20222fff202222f220222222002222200000000000 +-- 022:02222222022f222202fff22202fff222022f2222002222220002222200000000 +-- 023:222222202222f220222fff20222fff202222f220222222002222200000000000 +-- 024:022222ff022f222f02fff22202fff222022f2222002222220002222200000000 +-- 025:ff222220f222f220222fff20222fff202222f220222222002222200000000000 +-- 026:02222222022f222202fff2ff02fff2ff022f222f002222220002222200000000 +-- 027:22222220f222f220ff2fff20ff2fff202222f220222222002222200000000000 +-- 032:0000000000066666006666660666666606666666066666660666666f066666ff +-- 033:000000006666600066666600666666606666666066666660f6666660ff666660 +-- 034:000000000006666600666666066f666606fff66606fff666066f666606666666 +-- 035:0000000066666000666666006666666066666660666666606666666066666660 +-- 036:000000000006666600666666066f666606fff66606fff666066f666f066666ff +-- 037:000000006666600066666600666666606666666066666660f6666660ff666660 +-- 038:000000000006666600666666066f666606fff66606fff666066f666606666666 +-- 039:0000000066666000666666006666f660666fff60666fff606666f66066666660 +-- 040:000000000006666600666666066f666606fff66606fff666066f666f066666ff +-- 041:0000000066666000666666006666f660666fff60666fff60f666f660ff666660 +-- 042:000000000006666600666666066f666f06fff6ff06fff6ff066f666606666666 +-- 043:0000000066666000666666006666f660ff6fff60ff6fff60f666f66066666660 +-- 048:066666ff0666666f066666660666666606666666006666660006666600000000 +-- 049:ff666660f6666660666666606666666066666660666666006666600000000000 +-- 050:0666666606666666066666660666666606666666006666660006666600000000 +-- 051:6666666066666660666f666066fff66066fff660666f66006666600000000000 +-- 052:066666ff0666666f066666660666666606666666006666660006666600000000 +-- 053:ff666660f666f660666fff60666fff606666f660666666006666600000000000 +-- 054:06666666066f666606fff66606fff666066f6666006666660006666600000000 +-- 055:666666606666f660666fff60666fff606666f660666666006666600000000000 +-- 056:066666ff066f666f06fff66606fff666066f6666006666660006666600000000 +-- 057:ff666660f666f660666fff60666fff606666f660666666006666600000000000 +-- 058:06666666066f666606fff6ff06fff6ff066f666f006666660006666600000000 +-- 059:66666660f666f660ff6fff60ff6fff606666f660666666006666600000000000 +-- + +-- +-- 000:00000000ffffffff00000000ffffffff +-- 001:0123456789abcdeffedcba9876543210 +-- 002:0123456789abcdef0123456789abcdef +-- 003:00022345555555554433222225888888 +-- + +-- +-- 000:03070306130523043303e302f301f301f300f30ff30ef30ef30df30cf30bf30bf300f300f300f301f303f302f302f302e302e302e301e301e301e300300000000000 +-- + +-- +-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +-- + +-- +-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57 +--