Files
YataiDONNijiiro/Scripts/result/result_bg_layers.lua
T
2026-09-30 15:29:22 -04:00

154 lines
6.3 KiB
Lua

-- Animated 1P result backdrop: sky halves, Mt Fuji and drifting cloud bars, plus
-- a white header band, all driven by the exported result_bg timeline.
local anim = require("anim/sampler")
local ResultBgLayers = {}
ResultBgLayers.__index = ResultBgLayers
local LOOP = 720 -- frames, sprite 83 / 87
local FUJI_LOOP = 90 -- frames, huji_1p_l_mc (sprite 86), clear state only
local MS_TO_F = 0.06 -- 60 fps
-- depth, cloud texture index, y, scale (|sx| == sy for every one of them), mirror, source width
-- (only a mirrored layer needs it -- and for both of those the base and clear bakes are the same
-- width), arcade track name, frame-0 tx, frame-0 alpha
local LAYERS = {
{ 3, 0, 162.60, 1.0, false, 592, "#67@3", 1705.70, 0 },
{ 4, 1, 905.25, 1.0, false, 960, "#69@4", 1240.00, 0 },
{ 5, 1, 934.00, 1.2, true, 960, "#69@5", 1106.30, 0 },
{ 6, 2, 622.00, 1.0, false, 840, "#71@6", 1382.90, 1 },
{ 7, 3, 873.25, 1.0, false, 824, "#73@7", -272.10, 1 },
{ 8, 4, 873.25, 1.0, false, 800, "#75@8", 668.65, 0 },
{ 9, 5, 595.25, 1.0, false, 848, "#77@9", 468.00, 0 },
{ 10, 6, -17.70, 1.0, false, 616, "#79@10", 1645.00, 1 },
{ 11, 2, 75.10, 0.6, true, 840, "#71@11", 346.40, 0 },
{ 12, 6, 214.30, 1.0, false, 616, "#79@12", 767.80, 1 },
{ 13, 7, 400.75, 1.0, false, 600, "#81@13", -190.35, 0 },
}
local FUJI_X = 583.00 -- shape 65, base
local FUJI_Y = 280.00
local FUJI_CLEAR_X = 584.65 -- shape 84 inside huji_1p_l_mc, clear
local FUJI_H = 800
local FUJI_TRACK = "huji_1p_l_mc/#84@0"
function ResultBgLayers.new(player_num)
local self = setmetatable({}, ResultBgLayers)
self.player_num = player_num
self.two_player = (player_num == 3)
self.t0 = nil
self.fuji_t0 = nil
if self.two_player then
-- 2P: single baked plate (no clouds or Fuji) + header
self.t_bg = tex.get_texture("result/background/background_2p")
self.t_header = tex.get_texture("result/background/bg_header")
return self
end
self.t_sky = tex.get_texture("result/background/background_1p")
self.t_sky_clear = tex.get_texture("result/background/bg_sky_clear")
self.t_fuji = tex.get_texture("result/background/bg_fuji")
self.t_fuji_clear = tex.get_texture("result/background/bg_fuji_clear")
self.t_header = tex.get_texture("result/background/bg_header")
self.t_cloud, self.t_cloud_clear = {}, {}
for i = 0, 7 do
self.t_cloud[i] = tex.get_texture("result/background/bg_cloud_" .. i)
self.t_cloud_clear[i] = tex.get_texture("result/background/bg_cloud_clear_" .. i)
end
-- FAIL SOFT: `anim.safe` returns a NULL clip whose readers return nil, so a missing or
-- malformed table degrades to the LAYERS constants instead of raising.
self.clip = anim.safe("anim/result_bg")
self.fuji = anim.safe("anim/result_bg_fuji")
return self
end
-- arcade frame of the 720-frame loop, from the engine clock
function ResultBgLayers:frame()
local ms = tex.get_current_ms()
if not self.t0 then self.t0 = ms end
return math.fmod((ms - self.t0) * MS_TO_F, LOOP)
end
-- Mt Fuji runs on its own clock, zeroed when the clear state fires.
function ResultBgLayers:fuji_frame(clear)
if clear <= 0 then
self.fuji_t0 = nil -- re-arm: the next clear starts from rest again
return 0
end
local ms = tex.get_current_ms()
if not self.fuji_t0 then self.fuji_t0 = ms end
-- monotonic; the sampler parks it on frame 89 (FUJI_LOOP - 1) forever after.
return (ms - self.fuji_t0) * MS_TO_F
end
local function draw_cloud(id, w, x, y, scale, mirror, a)
if not id or a <= 0.004 then return end
local left = mirror and (x - w * scale) or x
tex.draw_texture(id, {
x = left, y = y, scale = scale, fade = a,
mirror = mirror and "horizontal" or nil,
})
end
-- `fade` multiplies everything (the screen-entry wipe); `clear` is 0..1, the success cross-fade.
function ResultBgLayers:draw(fade, clear)
fade = fade or 1.0
clear = clear or 0.0
if fade <= 0 then return end
if self.two_player then
if self.t_bg then tex.draw_texture(self.t_bg, { fade = fade }) end
if self.t_header then tex.draw_texture(self.t_header, { fade = fade }) end
return
end
local f = self:frame()
-- 1. sky. Base: shape 3 at x=0 and mirrored at x=960. Clear: shape 5 (gold, a different
-- atlas) the other way round -- x=960 upright, x=0 mirrored.
if self.t_sky then
tex.draw_texture(self.t_sky, { index = 0, fade = fade })
tex.draw_texture(self.t_sky, { index = 1, fade = fade, mirror = "horizontal" })
end
if clear > 0 and self.t_sky_clear then
tex.draw_texture(self.t_sky_clear, { index = 0, fade = fade * clear })
tex.draw_texture(self.t_sky_clear, { index = 1, fade = fade * clear, mirror = "horizontal" })
end
-- 2. Mt Fuji, pulsing only in the clear state (a 90-frame one-shot, held at the end).
if self.t_fuji and clear < 1.0 then
tex.draw_texture(self.t_fuji, { x = FUJI_X, y = FUJI_Y, fade = fade * (1.0 - clear) })
end
if clear > 0 and self.t_fuji_clear then
local ty, sy = self.fuji:at(FUJI_TRACK, self:fuji_frame(clear))
ty = ty or FUJI_Y
sy = sy or 1.0
tex.draw_texture(self.t_fuji_clear, {
x = FUJI_CLEAR_X, y = ty, y2 = FUJI_H * sy - FUJI_H, fade = fade * clear,
})
end
-- 3. the eleven cloud bars, driven off the one exported table for both states.
for i = 1, #LAYERS do
local L = LAYERS[i]
local idx, y, scale, mirror, w, track = L[2], L[3], L[4], L[5], L[6], L[7]
local tx, a = self.clip:at(track, f)
tx = tx or L[8]
a = a or L[9]
if clear < 1.0 then
draw_cloud(self.t_cloud[idx], w, tx, y, scale, mirror, fade * a * (1.0 - clear))
end
if clear > 0 then
draw_cloud(self.t_cloud_clear[idx], w, tx, y, scale, mirror, fade * a * clear)
end
end
-- 4. header band, drawn last (above the clouds).
if self.t_header then tex.draw_texture(self.t_header, { fade = fade }) end
end
return ResultBgLayers