Files
TaikoZhongandClaude Fable 5.1 3e6783f8fb Lower backdrops: replay every element of the original movies
The five normal and three cleared backdrops were flat plates plus a few hand
rebuilt effects, so the koi, the treasure ships, the lantern glow and the
festival stalls never moved as they do in the original. Each movie is now
exported per shape instance with its own loop (bg_data/bg_anim/<name>.lua,
textures under background/bga_<name>) and replayed by bg_objects/bg_anim.lua at
60 fps on the lower half of the screen. bg_normal.lua and bg_fever.lua use it
when the data exists and keep their old drawing as the fallback; the cleared
backdrop's rise/fall wipe is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 12:28:29 +08:00

183 lines
6.9 KiB
Lua

-- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/bg_fever.lua
--
-- ROUND 12. The cabinet's gogo/clear backdrop is
-- enso/background/bg_fever_a_{01,02,03}.nulm: a 1920x540 stage (the lower half
-- of the screen, same anchor as bg_nomal) made of one static plate plus one to
-- three named `scroll_*_mc` strips, each sliding at a constant px/frame with a
-- fixed repeat period. There is no flipbook anywhere in it, so the whole rig
-- ports as: plate + per-strip {tile, period, px/s}, replayed here.
--
-- The measured numbers live in Scripts/background/bg_data/bg_fever.lua,
-- generated by scratchpad/r12bg/bgfever.py. If that table is missing this
-- falls back to drawing the static plate only -- never to the parent's art,
-- because Graphics/background/bg_fever/* is fully ours (every texture name the
-- generated id map declares is either a shipped layer or an 8x8 transparent
-- stub).
local ok, DATA = pcall(require, "background/bg_data/bg_fever")
if not ok or type(DATA) ~= "table" then DATA = {} end
-- per-element replay of the cabinet movie (koi, fans, tops... each on its own
-- loop) when bg_data/bg_anim/bg_fever_N.lua has been exported; the layer lists
-- above stay as the fallback. The rise/fall wipe is still this file's fade.
local ok_anim, BGAnim = pcall(require, "background/bg_objects/bg_anim")
if not ok_anim or type(BGAnim) ~= "table" then BGAnim = nil end
local ArcadeBGFever = {}
ArcadeBGFever.__index = ArcadeBGFever
-- The cabinet wipes normal->fever over `normal_fever` (root frames 1..29 at
-- 60 fps). We cannot reproduce the wipe with one texture, so it is a fade of
-- the same length, and `transitioned` flips when it ends -- that is the flag
-- background.lua uses to stop drawing bg_normal underneath.
--
-- ROUND 52 (r52-lua-divergence-fixes): the wipe now runs on BOTH edges and is
-- re-triggerable, matching the reference build `EnsoGraphicBgFever::Process`
-- (EnsoGraphicBgFever.obj.c:297-308): the cached flag at +88 is compared
-- against `IsNormaClear` EVERY frame, and each change of state GotoAndPlay's a
-- clip -- `normal_fever` on the rising edge, `fever_normal` on the falling
-- edge. The old `started`/`transitioned` one-way latch meant only the FIRST
-- clear ever wiped and a gauge dropping below the norma hard-cut the fever
-- backdrop in a single frame. `stop()` is the falling-edge clip (a fade of
-- the same 29-frame length, from wherever the current fade sits, so an edge
-- mid-wipe reverses smoothly), and `start()` re-arms after it.
-- `is_visible()` is the new flag background.lua uses to keep drawing this
-- object while the exit fade is still alive.
local TRANSITION_MS = 29000 / 60
function ArcadeBGFever.new(index, path)
local self = setmetatable({}, ArcadeBGFever)
self.name = "bg_fever_" .. index
self.path = path or "background"
tex.load_folder(self.path, "bg_fever/" .. self.name)
self.transitioned = false
self.started = false
self.elapsed = 0
self.last_ms = nil
self.fade = anim.fade(TRANSITION_MS, {
initial_opacity = 0.0,
final_opacity = 1.0,
loop = false
})
self.anim = nil
if BGAnim and (path or "background") == "background" then
self.anim = BGAnim.new(self.name, path)
end
self.layers = {}
local d = DATA[self.name]
if d and d.layers then
for _, l in ipairs(d.layers) do
local id = tex.get_id(self.name, l.tex)
if id then
local period = l.period or 0
table.insert(self.layers, {
id = id,
period = period,
vx = l.vx or 0,
-- enough copies to cover 1920 px whatever the phase
n = (period > 0) and (math.ceil(1920 / period) + 1) or 0
})
end
end
end
if #self.layers == 0 then
-- fail soft: the generated table is missing or named textures this
-- folder does not have. Draw the one name every slot ships.
local id = tex.get_id(self.name, "background")
if id then table.insert(self.layers, {id = id, period = 0, vx = 0, n = 0}) end
end
return self
end
function ArcadeBGFever:start()
if self.started then return end
self.started = true
self.transitioned = false
-- `normal_fever`: wipe in from wherever the (possibly mid-exit) fade sits.
local from = self.fade and self.fade.attribute or 0.0
self.fade = anim.fade(TRANSITION_MS, {
initial_opacity = from,
final_opacity = 1.0,
loop = false
})
self.fade:start()
end
-- ROUND 52: falling edge (`fever_normal`). Re-arms start() for the next clear.
function ArcadeBGFever:stop()
if not self.started then return end
self.started = false
self.transitioned = false
local from = self.fade and self.fade.attribute or 1.0
self.fade = anim.fade(TRANSITION_MS, {
initial_opacity = from,
final_opacity = 0.0,
loop = false
})
self.fade:start()
end
-- ROUND 52: true while anything of this backdrop is on screen -- entering,
-- steady, or exiting. background.lua draws bg_normal beneath whenever the
-- fever plate is not yet (or no longer) fully opaque.
function ArcadeBGFever:is_visible()
if self.started then return true end
return (self.fade.attribute or 0) > 0.001 and not self.fade.is_finished
end
function ArcadeBGFever:update(current_time_ms)
if self.last_ms then
local dt = current_time_ms - self.last_ms
if dt > 0 and dt < 500 then self.elapsed = self.elapsed + dt end
end
self.last_ms = current_time_ms
if self.anim then self.anim:update(current_time_ms) end
if not self.transitioned then
self.fade:update(current_time_ms)
if self.started and self.fade.is_finished then self.transitioned = true end
end
end
function ArcadeBGFever:draw()
local fade = self.transitioned and 1.0 or self.fade.attribute
if self.anim then
self.anim:draw(fade)
return
end
-- back to front, in the cabinet's own depth order: the static art is NOT
-- all behind the strips (bg_fever_a_02's spinning tops sit in front of an
-- opaque checker strip), so the order in the table is load-bearing.
for _, l in ipairs(self.layers) do
if l.period > 0 then
local off = (self.elapsed / 1000.0) * l.vx
off = off % l.period
if off > 0 then off = off - l.period end
for i = 0, l.n do
tex.draw_texture(l.id, { x = off + i * l.period, fade = fade })
end
else
tex.draw_texture(l.id, { fade = fade })
end
end
end
function ArcadeBGFever:destroy()
tex.unload_folder(self.path, "bg_fever/" .. self.name)
if self.anim then self.anim:destroy() end
end
local BGFever = {}
BGFever.Base = ArcadeBGFever
BGFever.Arcade = ArcadeBGFever
function BGFever.create(index, path)
return ArcadeBGFever.new(index, path)
end
return BGFever