The band behind the player used to be a scrolling tile plus a few static plates, so the squids, waves, fans, fish and birds never moved the way they do in the original. Each of the twelve bands (six movies x 1P/2P) is now data-driven: one texture per shape (background/donbga_NN_Mp) and, per shape instance, the period of its own loop with per-frame position/scale/rotation/alpha (bg_data/donbg_anim/a_NN_Mp.lua); flipbook frames carry a separate presence mask so the fish's arc and its frame swap keep their own periods. donbg/anim_player.lua replays all of it on one 60 fps clock; base.lua uses it when the data exists and keeps the old layer lists as the fallback (collab bands are unchanged). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
280 lines
12 KiB
Lua
280 lines
12 KiB
Lua
-- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/donbg/base.lua
|
|
--
|
|
-- ROUND 13. The band behind the player MOVES, and round 12 froze it.
|
|
--
|
|
-- The cabinet's enso/donbg/donbg_a_XX_Np.nulm is a 1920x276 stage holding one
|
|
-- group per state (normal / fever / the unreachable `miss`), and it is that
|
|
-- group's CHILDREN that slide: every one of them translates left at a constant
|
|
-- ~92 px/s and wraps on its own period (496 px for index 0, 492 for 1 and 2,
|
|
-- 490 + 986 for 3, 490 for 4, 490 + 980 for 5). Round 12 probed tx on the
|
|
-- group, which is a constant, and shipped one static plate.
|
|
--
|
|
-- ROUND 19 (r19-donbg). The character row `donbg_obj_a_01` -- the drummer, the
|
|
-- mikoshi, the fox mask, the chicks -- that rounds 12/13/18 drew on top of this
|
|
-- band is NOT on the cabinet's enso screen. `datatable/enso_post.bin` is the
|
|
-- enso placement table (33 records for target 0) and it places exactly ONE
|
|
-- donbg movie, `donbg/donbg_a_0N_1p` at (0,0); `donbg_obj_a_01` appears in no
|
|
-- enso_post record for any target, in no `enso_rand_part.bin` random pool, in
|
|
-- no `music_ensoparts` / `qrcode_info_ensoparts` row, in no lua under
|
|
-- `the reference lua tree` (there is no enso lua at all), and as no string in
|
|
-- the reference build (byte search: `donbg` 0 hits while `lumen/000_default` gets 27 and
|
|
-- `enso_post` 1, so the search is sound). It is the only `*_obj_*` movie in
|
|
-- any donbg folder in any of the 27 lumen packs -- leftover art from an older
|
|
-- build. The user's own arcade reference says the same (REFERENCE_SCREENS.md,
|
|
-- Gameplay): "Top band: pink umbrella pattern with Don (left), song title
|
|
-- top-right + genre pill" -- pattern plus the 2D Don at the LEFT, no character
|
|
-- row. So the row, and its bob, are gone.
|
|
--
|
|
-- So this draws, back to front, the layer list that
|
|
-- Scripts/background/bg_data/donbg.lua carries for the current state:
|
|
-- period > 0 : a tile, repeated across 1920 px and scrolled at vx px/s
|
|
-- period == 0: a plate drawn once at its texture.json position
|
|
-- Layer ORDER is load-bearing:
|
|
-- the cabinet puts static motifs (index 1, 2 and 4's spinning tops, kites and
|
|
-- streamers) IN FRONT of the opaque scrolling backdrop.
|
|
--
|
|
-- frame 0/1 of a name are not "normal/fever" any more -- a name's frames are
|
|
-- just its layers, and the table says which frame and which texture.json entry
|
|
-- (params.index) each draw uses. The cabinet's third state, `miss`, still has
|
|
-- no Lua hook (MAPPING.md's C list).
|
|
--
|
|
-- Fails soft: with no generated table this draws `background` frame 0 once,
|
|
-- never the parent's art -- every texture name these folders declare is ours.
|
|
local ok, DATA = pcall(require, "background/bg_data/donbg")
|
|
if not ok or type(DATA) ~= "table" then DATA = {} end
|
|
-- per-element replay of the cabinet movie (squid / waves / fans each on their own
|
|
-- motion) when bg_data/donbg_anim/a_NN_Mp.lua has been exported for this band;
|
|
-- otherwise the layer lists above.
|
|
local ok_anim, DonBGAnim = pcall(require, "background/bg_objects/donbg/anim_player")
|
|
if not ok_anim or type(DonBGAnim) ~= "table" then DonBGAnim = nil end
|
|
|
|
DonBGBase = {}
|
|
DonBGBase.__index = DonBGBase
|
|
|
|
function DonBGBase.new(index, player_num, path)
|
|
local self = setmetatable({}, DonBGBase)
|
|
|
|
self.name = string.format("%d_%d", index - 1, player_num)
|
|
self.path = path
|
|
tex.load_folder(path, string.format("donbg/%s", self.name))
|
|
self.bg_texture = tex.get_texture_info(self.name, "background")
|
|
|
|
-- ROUND 60 (r60-collab-fix-all). bg_data/donbg.lua describes the DEFAULT
|
|
-- donbg pack (arcade enso/donbg/donbg_a_XX) and NOTHING else, but its keys
|
|
-- are bare "<index>_<player>" names -- so a COLLAB DonBG (path
|
|
-- "background/collab/*", art from the parent skin) with name "0_1" was
|
|
-- picking up donbg_a_01's layer list (period 496, vx -93.29) and tiling
|
|
-- the collab's own art with it: oshiri's 168px blue/green tile drawn every
|
|
-- 496px (band full of gaps leaking the loading screen), index/frame values
|
|
-- indexing texture.json entries the collab art does not have (x[index]
|
|
-- reads out of range engine-side -> garbage coordinates). This is the
|
|
-- user-reported OSHIRI band corruption. The default table is now gated to
|
|
-- the default pack, and collab paths get the PARENT DonBGBase contract
|
|
-- back: a 5-copy tile scroll drawn through the `draw_textures(fade, y,
|
|
-- frame)` hook (frame 0 normal, frame 1 fever crossfade) that every collab
|
|
-- subclass (a3/oshiri/imas_sidem/tutorial) overrides -- those overrides
|
|
-- had been dead code since the ROUND 13 rewrite (dan.lua's ROUND 26 note
|
|
-- documented exactly this and hand-built its states instead).
|
|
self.use_layers = (path == "background")
|
|
self.anim = nil
|
|
if self.use_layers and DonBGAnim then
|
|
self.anim = DonBGAnim.new(index, player_num, path)
|
|
end
|
|
if not self.use_layers then
|
|
-- parent contract: constant scroll, one bg-width per 3000 ms loop
|
|
local w = self.bg_texture and self.bg_texture.x2[1] or 0
|
|
self.move = anim.move(3000, { total_distance = -w, loop = true })
|
|
self.move:start()
|
|
end
|
|
|
|
self.is_clear = false
|
|
self.clear_fade = tex.get_animation(1)
|
|
-- ROUND 52 (r52-lua-divergence-fixes): the falling edge. the reference build
|
|
-- `EnsoGraphicDonBg::Process` (EnsoGraphicDonBg.obj.c:114-119, 291, 330)
|
|
-- plays a clip on BOTH edges (`normal_fever` / `fever_normal`); this file
|
|
-- used to be the only one that handled the exit at all, but it reverted
|
|
-- with an instant clear_fade:reset(). The exit is now the reverse fade of
|
|
-- the same 29-frame length the bg_fever wipe uses.
|
|
self.exit_fade = nil
|
|
|
|
self.elapsed = 0
|
|
self.last_ms = nil
|
|
|
|
-- ROUND 60: id 0 is a VALID texture id (the global name->id map hands
|
|
-- out 0 to the first registered name -- live-verified: funassyi's
|
|
-- "0_1/background" IS id 0), so "or 0" would make a real texture
|
|
-- indistinguishable from a missing one. nil = missing, everywhere here.
|
|
self._id_bg = tex.get_id(self.name, "background")
|
|
|
|
local d = DATA[self.name]
|
|
self.states = {}
|
|
if d then
|
|
for _, st in ipairs({ "normal", "fever" }) do
|
|
local out = {}
|
|
for _, l in ipairs(d[st] or {}) do
|
|
local id = tex.get_id(self.name, l.tex)
|
|
if id then
|
|
local period = l.period or 0
|
|
table.insert(out, {
|
|
id = id, frame = l.frame, index = l.index,
|
|
period = period, vx = l.vx or 0,
|
|
-- ROUND 102: optional y triangle, cabinet frames at 60 fps
|
|
bob = l.bob, bob_frames = l.bob_frames,
|
|
-- enough copies to cover 1920 px whatever the phase
|
|
n = (period > 0) and (math.ceil(1920 / period) + 1) or 0,
|
|
})
|
|
end
|
|
end
|
|
self.states[st] = out
|
|
end
|
|
end
|
|
if not self.states.normal or #self.states.normal == 0 then
|
|
self.states = { normal = {} }
|
|
if self._id_bg then
|
|
self.states.normal[1] = { id = self._id_bg, frame = 0, index = 0,
|
|
period = 0, vx = 0, n = 0 }
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local EXIT_MS = 29000 / 60
|
|
|
|
function DonBGBase:set_clear(is_clear)
|
|
if is_clear and not self.is_clear then
|
|
self.is_clear = true
|
|
self.clear_fade:start()
|
|
self.exit_fade = nil
|
|
end
|
|
if not is_clear and self.is_clear then
|
|
self.is_clear = false
|
|
-- ROUND 52: reverse-play instead of the old instant reset -- fade the
|
|
-- fever layers back out from wherever the entry fade currently sits.
|
|
local from = self.clear_fade.attribute or 1.0
|
|
self.exit_fade = anim.fade(EXIT_MS, {
|
|
initial_opacity = from,
|
|
final_opacity = 0.0,
|
|
loop = false
|
|
})
|
|
self.exit_fade:start()
|
|
self.clear_fade:reset()
|
|
end
|
|
end
|
|
|
|
function DonBGBase:update(current_time_ms)
|
|
-- same wall-clock accumulator bg_fever uses: the scroll is a constant px/s,
|
|
-- and the guard drops the load-time gap and any backwards step
|
|
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
|
|
-- ROUND 60: the collab-contract scroll (and any subclass-replaced move,
|
|
-- e.g. tutorial's 6000 ms one) is advanced here, as the parent base did.
|
|
if self.move then self.move:update(current_time_ms) end
|
|
if self.anim then self.anim:update(current_time_ms) end
|
|
self.clear_fade:update(current_time_ms)
|
|
if self.exit_fade then
|
|
self.exit_fade:update(current_time_ms)
|
|
if self.exit_fade.is_finished then self.exit_fade = nil end
|
|
end
|
|
end
|
|
|
|
-- ROUND 102 (r102-bg-sweep): a layer may also carry a y triangle. `donbg_a_02`'s
|
|
-- crest motif row is a child of a container whose ty runs 138 -> 148 -> 138 on a
|
|
-- 120-frame cycle (measured frame by frame off the anim dump: +0.1667 px per frame
|
|
-- for 60 frames, then the mirror). Amplitude and period are cabinet FRAMES at the
|
|
-- stage's own 60 fps, converted here against the same wall-clock `elapsed` the
|
|
-- scroll uses, so the skin's 120 Hz Lua tick never enters the arithmetic.
|
|
local function bob_offset(l, elapsed)
|
|
if not l.bob or not l.bob_frames or l.bob_frames <= 0 then return 0 end
|
|
local half = l.bob_frames / 2.0
|
|
local t = (elapsed / (1000.0 / 60.0)) % l.bob_frames
|
|
if t < half then return l.bob * (t / half) end
|
|
return l.bob * ((l.bob_frames - t) / half)
|
|
end
|
|
|
|
function DonBGBase:draw_layers(layers, fade, y)
|
|
for _, l in ipairs(layers) do
|
|
local ly = y + bob_offset(l, self.elapsed)
|
|
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, { frame = l.frame, index = l.index,
|
|
x = off + i * l.period, y = ly, fade = fade })
|
|
end
|
|
else
|
|
tex.draw_texture(l.id, { frame = l.frame, index = l.index,
|
|
y = ly, fade = fade })
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ROUND 60 (r60-collab-fix-all): default parent-contract tiler, used by the
|
|
-- collab path when a subclass does not override it (imas/imas_cg/imas_ml use
|
|
-- the plain base; funassyi goes through donbg/4.lua). Same geometry as the
|
|
-- parent base: 5 copies at bg width, scrolled by self.move.
|
|
function DonBGBase:draw_textures(fade, y, frame)
|
|
if not self._id_bg then return end
|
|
local w = self.bg_texture and self.bg_texture.x2[1] or 0
|
|
if w <= 0 then
|
|
tex.draw_texture(self._id_bg, { frame = frame, fade = fade, y = y })
|
|
return
|
|
end
|
|
local mv = self.move and self.move.attribute or 0
|
|
for i = 0, 4 do
|
|
tex.draw_texture(self._id_bg, {
|
|
frame = frame,
|
|
fade = fade,
|
|
x = (i * w) + mv,
|
|
y = y
|
|
})
|
|
end
|
|
end
|
|
|
|
function DonBGBase:draw(y)
|
|
y = y or 0
|
|
|
|
-- ROUND 60: collab path = the parent contract (frame 0 + fever frame 1
|
|
-- crossfade through the draw_textures hook); default pack = the ROUND 13
|
|
-- DATA-driven layer lists.
|
|
if not self.use_layers then
|
|
self:draw_textures(1.0, y, 0)
|
|
if self.is_clear then
|
|
self:draw_textures(self.clear_fade.attribute, y, 1)
|
|
elseif self.exit_fade then
|
|
-- ROUND 52: falling-edge reverse fade (`fever_normal`).
|
|
self:draw_textures(self.exit_fade.attribute, y, 1)
|
|
end
|
|
return
|
|
end
|
|
|
|
if self.anim then
|
|
self.anim:draw_state("normal", 1.0, y)
|
|
if self.is_clear then
|
|
self.anim:draw_state("fever", self.clear_fade.attribute, y)
|
|
elseif self.exit_fade then
|
|
self.anim:draw_state("fever", self.exit_fade.attribute, y)
|
|
end
|
|
return
|
|
end
|
|
|
|
self:draw_layers(self.states.normal or {}, 1.0, y)
|
|
if self.is_clear and self.states.fever then
|
|
self:draw_layers(self.states.fever, self.clear_fade.attribute, y)
|
|
elseif self.exit_fade and self.states.fever then
|
|
-- ROUND 52: falling-edge reverse fade (`fever_normal`).
|
|
self:draw_layers(self.states.fever, self.exit_fade.attribute, y)
|
|
end
|
|
end
|
|
|
|
function DonBGBase:destroy()
|
|
tex.unload_folder(self.path, string.format("donbg/%s", self.name))
|
|
if self.anim then tex.unload_folder(self.path, self.anim.subset) end
|
|
end
|
|
|
|
return DonBGBase
|