Files
YataiDONNijiiro/Scripts/background/bg_objects/donbg/base.lua
T
TaikoZhongandClaude Opus 5.5 f239bc156c background: cabinet backdrops for the collab presets
Collab songs (#SCENEPRESET) now get their own cabinet backdrop, replayed by
the same objects as the default backdrop instead of the parent's collab art:

  miku, toho, gumi, ia, lovelive, i7 (id7 / tri / rev / natsu), imas
  (765 / cg / ml / sidem), yo-kai (base / matsuri / hatsukoi / tokoroten /
  yougota), mario, a3

Each preset is a pack in bg_data/collab_packs.lua naming its parts: donbg
band (1P and 2P), normal and clear backdrops, troupe, stand under the
troupe, drumroll effect and soul-max crowd.  Shared parts (the i7, imas
and yo-kai families) are shared the way the cabinet shares them.  A pack is
used only when every part it names is present, otherwise the preset falls
back to the collab script as before.  Packs also apply in 2P (donbg bands).

The objects take a part name where they took a random index:
DonBG.create_named, BGNormal.create_named, BGFever.create_named,
ArcadeDancerGroup.new("<name>"), Fever.Anim.new("<name>").  New:
renda_clip.lua (each hit replays one of the effect's parts along its own
path), and a beat-synced BGAnim for the whole-troupe dancer movies (i7,
a3).  Collab troupes stand in fixed positions (lead in the centre) and
grow from 1 to the full troupe with the gauge.

Exported placements may now mirror: bg_anim, donbg and dancer data carry a
mirror flag (drawn with mirror = horizontal and the origin moved to the
other side of the png).  Existing data has no flag and draws as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:55:05 +08:00

252 lines
8.9 KiB
Lua

-- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/donbg/base.lua.
-- The cabinet band is a 1920x276 stage whose children slide left and wrap; the
-- default pack is DATA-driven layer lists, collab paths use the parent tiler.
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 when bg_data/donbg_anim/a_NN_Mp.lua is
-- exported for this band; otherwise the layer lists.
local ok_anim, DonBGAnim = pcall(require, "background/bg_objects/donbg/anim_player")
if not ok_anim or type(DonBGAnim) ~= "table" then DonBGAnim = nil end
local DonBGBase = {}
DonBGBase.__index = DonBGBase
-- anim_name: a collab pack's band ("b_001_1p"), replayed from its exported movie only
function DonBGBase.new(index, player_num, path, anim_name)
local self = setmetatable({}, DonBGBase)
self.path = path
self.anim = nil
if anim_name and DonBGAnim then
self.name = anim_name
self.named = true
self.use_layers = true
self.anim = DonBGAnim.new(nil, player_num, path, anim_name)
else
self.name = string.format("%d_%d", index - 1, player_num)
tex.load_folder(path, string.format("donbg/%s", self.name))
self.bg_texture = tex.get_texture_info(self.name, "background")
-- DATA describes only the DEFAULT pack; collab paths get the parent tiler
self.use_layers = (path == "background")
if self.use_layers and DonBGAnim then
self.anim = DonBGAnim.new(index, player_num, path)
end
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, "background")
-- exit is a reverse fade of the same length the bg_fever wipe uses
self.exit_fade = nil
self.elapsed = 0
self.last_ms = nil
-- nil = missing (id 0 is a valid texture id); a named band has no plate folder
if not self.named then
self._id_bg = tex.get_texture(self.path .. "/" .. self.name .. "/background")
end
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_texture(self.path .. "/" .. 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,
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
-- miss streak: the band is multiplied towards black (colour of draw_texture), eased in/out
local DARK_MIN, DARK_FADE_MS = 0.65, 300
function DonBGBase:set_dark(on)
self.dark_target = on and 1 or 0
end
function DonBGBase:update_dark(current_time_ms)
local dt = current_time_ms - (self.dark_last_ms or current_time_ms)
self.dark_last_ms = current_time_ms
local d, target = self.dark or 0, self.dark_target or 0
local step = dt / DARK_FADE_MS
self.dark = d + math.max(-step, math.min(step, target - d))
end
function DonBGBase:dark_color()
if not self.dark or self.dark <= 0 then return nil end
local v = math.floor(255 * (1 - (1 - DARK_MIN) * self.dark))
return { v, v, v, 255 }
end
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
-- reverse-play instead of the old instant reset
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)
self:update_dark(current_time_ms)
-- same wall-clock accumulator bg_fever uses; guard drops the load-time gap
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.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
-- A layer may also carry a y triangle (bob), amplitude/period in cabinet frames.
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
-- default parent-contract tiler (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_band(y)
y = y or 0
-- collab path = parent contract; default pack = 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
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
self:draw_layers(self.states.fever, self.exit_fade.attribute, y)
end
end
-- Draws the band through `inner`, multiplying every texture it draws by the miss-streak
-- darkness. Subclasses draw with their own tex.draw_texture calls, so wrap that once here.
function DonBGBase:draw_dark(inner, y)
local c = self:dark_color()
if not c then return inner(self, y) end
local orig = tex.draw_texture
tex.draw_texture = function(id, p)
p = p or {}
p.color = c
return orig(id, p)
end
local ok, err = pcall(inner, self, y)
tex.draw_texture = orig
if not ok then error(err, 0) end
end
function DonBGBase:draw(y)
self:draw_dark(DonBGBase.draw_band, y or 0)
end
function DonBGBase:destroy()
if not self.named then tex.unload_folder(self.path, string.format("donbg/%s", self.name)) end
if self.anim then tex.unload_folder(self.path, self.anim.subset) end
end
return DonBGBase