Files
YataiDONNijiiro/Scripts/background/bg_objects/fever.lua
T
TaikoZhongandClaude Fable 5.1 1b74eb9c99 Soul-max crowd: replay the per-group movie instead of a flat overlay
The 魂 max crowd was two flattened PNGs, so every figure sat at one fixed
position, the turtle/shiba groups lost their depth order, and nothing moved.
The crowd is now data-driven: one texture per crowd group
(background/fevera_N) plus a per-frame display list
(bg_data/fever_anim/f_N.lua) covering the rise, the loop and the fall, replayed
by bg_objects/fever_anim.lua at 60 fps. Every group bobs and squashes on its
own phase and layers exactly as authored; the base of the crowd lands on the
band. Three crowds are shipped; the old flat overlays stay as a fallback for
skins without the data.

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

171 lines
6.5 KiB
Lua

-- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/fever.lua: bounce total_distance 50*screen_scale
--
-- ROUND 52 (r52-lua-divergence-fixes): the 魂-max crowd is now EDGE-DRIVEN on
-- both sides and re-triggerable, matching the reference build `EnsoGraphicFeverEffect::Process`
-- (EnsoGraphicFeverEffect.obj.c:123-129): the cached flag at +88 is compared
-- against `IsTamashiiMax` every frame and each change plays a clip --
-- `normal_fever` rising, `fever_normal` falling. The skin used to pop the
-- crowd in and out in a single frame (draw gated on the raw is_rainbow flag in
-- background.lua) and never replayed the entrance on a re-max. The entry/exit
-- clips themselves are not exported (the cabinet's 40-frame 1920x540 flipbook
-- stays out -- ROUND 12 trade), so both edges are an alpha ramp of the same
-- 29-frame length BgFever's measured wipe uses (a documented stand-in, not a
-- measured FeverEffect number). `is_visible()` replaces background.lua's
-- is_rainbow draw gate so the exit ramp actually gets drawn.
local FEVER_EDGE_MS = 29000 / 60
local BaseFever = {}
BaseFever.__index = BaseFever
function BaseFever.new(index, bpm, path)
local self = setmetatable({}, BaseFever)
self.name = "fever_" .. index
self.path = path or "background"
tex.load_folder(self.path, "fever/" .. self.name)
local half_beat = (60000 / bpm) / 2
self.bounce_up = anim.move(half_beat, {total_distance = 50 * tex.get_screen_scale(), ease_out = "quadratic"})
self.bounce_down = anim.move(half_beat, {total_distance = 50 * tex.get_screen_scale(), ease_in = "quadratic", delay = half_beat})
-- ROUND 60: nil = missing. id 0 is a VALID texture id (the global
-- name->id map starts at 0), so "or 0" hid real textures and, worse, made
-- a missing folder draw texture id 0 (an unrelated sheet).
self._id_overlay_l = tex.get_id(self.name, "overlay_l")
self._id_overlay_r = tex.get_id(self.name, "overlay_r")
self._id_overlay = tex.get_id(self.name, "overlay")
self.active = false
self.edge_fade = anim.fade(FEVER_EDGE_MS, {initial_opacity = 0.0, final_opacity = 0.0})
return self
end
function BaseFever:start()
if self.active then return end
self.active = true
local from = self.edge_fade and self.edge_fade.attribute or 0.0
self.edge_fade = anim.fade(FEVER_EDGE_MS, {initial_opacity = from, final_opacity = 1.0})
self.edge_fade:start()
self.bounce_up:start()
self.bounce_down:start()
end
-- ROUND 52: falling edge (`fever_normal`); re-arms start().
function BaseFever:stop()
if not self.active then return end
self.active = false
local from = self.edge_fade and self.edge_fade.attribute or 1.0
self.edge_fade = anim.fade(FEVER_EDGE_MS, {initial_opacity = from, final_opacity = 0.0})
self.edge_fade:start()
end
function BaseFever:is_visible()
if self.active then return true end
return (self.edge_fade.attribute or 0) > 0.001 and not self.edge_fade.is_finished
end
function BaseFever:alpha()
if self.active and self.edge_fade.is_finished then return 1.0 end
return self.edge_fade.attribute or 0.0
end
function BaseFever:update(current_time_ms, bpm)
self.edge_fade:update(current_time_ms)
self.bounce_up:update(current_time_ms)
self.bounce_down:update(current_time_ms)
if self.bounce_down.is_finished then
local half_beat = (60000 / bpm) / 2
self.bounce_up = anim.move(half_beat, {total_distance = 50 * tex.get_screen_scale(), ease_out = "quadratic"})
self.bounce_down = anim.move(half_beat, {total_distance = 50 * tex.get_screen_scale(), ease_in = "quadratic", delay = half_beat})
self.bounce_up:start()
self.bounce_down:start()
end
end
function BaseFever:destroy()
tex.unload_folder(self.path, "fever/" .. self.name)
end
local Fever0 = setmetatable({}, {__index = BaseFever})
Fever0.__index = Fever0
function Fever0.new(index, bpm, path)
local self = setmetatable({}, Fever0)
local base = BaseFever.new(index, bpm, path)
for k, v in pairs(base) do self[k] = v end
return self
end
function Fever0:draw()
local a = self:alpha()
if a <= 0.001 then return end
local y = self.bounce_down.attribute - self.bounce_up.attribute
-- ROUND 60 (r60-collab-fix-all): fail soft when the folder shipped no art.
if self._id_overlay_l then tex.draw_texture(self._id_overlay_l, {y = y, fade = a}) end
if self._id_overlay_r then tex.draw_texture(self._id_overlay_r, {y = y, fade = a}) end
end
-- Fever1 and Fever2 share the same draw as Fever0
local Fever1 = setmetatable({}, {__index = Fever0})
Fever1.__index = Fever1
function Fever1.new(index, bpm, path)
local self = setmetatable({}, Fever1)
local base = BaseFever.new(index, bpm, path)
for k, v in pairs(base) do self[k] = v end
return self
end
function Fever1:draw() Fever0.draw(self) end
local Fever2 = setmetatable({}, {__index = Fever0})
Fever2.__index = Fever2
function Fever2.new(index, bpm, path)
local self = setmetatable({}, Fever2)
local base = BaseFever.new(index, bpm, path)
for k, v in pairs(base) do self[k] = v end
return self
end
function Fever2:draw() Fever0.draw(self) end
local Fever3 = setmetatable({}, {__index = BaseFever})
Fever3.__index = Fever3
function Fever3.new(index, bpm, path)
local self = setmetatable({}, Fever3)
local base = BaseFever.new(index, bpm, path)
for k, v in pairs(base) do self[k] = v end
return self
end
function Fever3:draw()
local a = self:alpha()
if a <= 0.001 then return end
-- ROUND 60: fail soft on a missing fever folder (see Fever0:draw).
if not self._id_overlay then return end
local y = self.bounce_down.attribute - self.bounce_up.attribute
tex.draw_texture(self._id_overlay, {y = y, fade = a})
end
local FeverAnim = require("background/bg_objects/fever_anim")
local Fever = {}
Fever.Base = BaseFever
Fever.Anim = FeverAnim
Fever.Fever0 = Fever0
Fever.Fever1 = Fever1
Fever.Fever2 = Fever2
Fever.Fever3 = Fever3
-- The cabinet ships three crowds (fever_effect0..2); when their exported movie
-- data is present that is the whole pool, otherwise the four flat overlays.
function Fever.variant_count(path)
if (path or "background") == "background" and FeverAnim.available(0) then return 3 end
return 4
end
function Fever.create(index, bpm, path)
if (path or "background") == "background" and FeverAnim.available(index) then
local crowd = FeverAnim.new(index, bpm, path)
if crowd then return crowd end
end
local map = {Fever0, Fever1, Fever2, Fever3}
return map[(index % 4) + 1].new(index % 4, bpm, path)
end
return Fever