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

203 lines
8.3 KiB
Lua

-- Song-loading (rainbow) transition backdrop, sampled from the arcade timeline.
local ok, anim = pcall(require, "anim/sampler")
local CLIP = (ok and anim and anim.safe) and anim.safe("anim/loading_song") or nil
SongTransition = {}
SongTransition.__index = SongTransition
local FADE_DELAY, FADE_MS, FADE_OUT_MS = 266.0, 266.0, 133.0
local SCENE_MS = FADE_DELAY + FADE_MS -- 532 ms: how long each scene shows
local F_IN, F_STOP, F_OUT, F_END = 5, 55, 60, 109
local F_FULL_A, F_FULL_B = 35, 78 -- the full-rainbow window
-- sub-rects inside the two atlases
local SRC_CURT_L = { x = 0, y = 0, width = 960, height = 1080 }
local SRC_CURT_R = { x = 960, y = 0, width = 960, height = 1080 }
local SRC_BAND = { x = 0, y = 0, width = 1600, height = 256 }
local SRC_GLOW = { x = 0, y = 288, width = 1600, height = 512 }
local SRC_STAR = { x = 0, y = 832, width = 128, height = 128 }
-- rest positions from texture.json; draws pass a delta off them
local REST_CHARA_L = { -120, 506 }
local REST_CHARA_R = { 1480, 506 }
local REST_HINT = { 0, 568 }
-- eleven star placements, already in screen px
local STARS = {
{ 209.75, 542.55, 0.59998 }, { 92.35, 283.00, 0.47829 },
{ 154.20, 330.75, 0.26784 }, { 292.35, 246.35, 0.44998 },
{ 1259.00, 201.85, 0.78998 }, { 1845.70, 228.45, 0.37422 },
{ 1791.70, 268.40, 0.24998 }, { 1388.50, 664.80, 0.44713 },
{ 1821.20, 423.20, 0.56000 }, { 1472.00, 607.70, 0.25038 },
{ 552.60, 628.95, 0.74998 },
}
local function clamp(v, a, b) if v < a then return a elseif v > b then return b else return v end end
-- read a track field, falling back to `default` when the table is gone
local function track(name, f, field, default)
if not CLIP then return default end
local v = CLIP:get(name, f, field)
if v == nil then return default end
return v
end
-- 演奏スキップON badge (624x104 pill at (38,54))
local SKIP_BADGE_X, SKIP_BADGE_Y = 38, 54
local SKIP_BADGE_CX = 350 -- 38 + 624/2, the EditText's centre
local SKIP_TEXT_CY = 109 -- 69 + 80/2, the EditText box's centre
local SKIP_TEXT_SIZE = 30
-- badge.png declares its own (38,54) origin, so pass it back as rest to cancel
local SKIP_REST = { 38, 54 }
function SongTransition.new(title, subtitle, is_second)
local self = setmetatable({}, SongTransition)
self.is_second = is_second and true or false
self.t0 = nil
self.f = self.is_second and F_OUT or F_IN
self.info = 0 -- band / hint opacity, 0..1
self.rainbow = tex.get_texture("global/rainbow_transition/rainbow_bg")
self.atlas_c = tex.get_texture("global/rainbow_transition/rainbow_bg_top")
self.atlas_b = tex.get_texture("global/rainbow_transition/rainbow_bg_bottom")
self.chara_l = tex.get_texture("global/rainbow_transition/chara_left")
self.chara_r = tex.get_texture("global/rainbow_transition/chara_right")
self.hint = tex.get_texture("global/rainbow_transition/chara_center")
-- the hint block is baked in Japanese; other languages draw it as text
local lang = tex.language and tex.language() or "ja"
if lang ~= "ja" and text and text.create_text then
local white, black = { 255, 255, 255, 255 }, { 0, 0, 0, 255 }
self.hint_title = text.create_text("transition_hint_title", white, black, false, 4.0, 2.0)
self.hint_line1 = text.create_text("transition_hint_line1", white, black, false, 3.0, 2.0)
self.hint_line2 = text.create_text("transition_hint_line2", white, black, false, 3.0, 2.0)
end
-- 演奏スキップON badge
self.skip_badge = tex.get_texture("loading/skip_badge/badge")
-- resolved on first draw (skip state isn't ready at construction)
self.skip_on = nil
if text and text.create_raw_text then
-- built here (not on first draw) so the glyph atlas is ready
self.skip_text = text.create_raw_text(tex.skin_text("skip_badge_text"), SKIP_TEXT_SIZE,
{ 0, 0, 0, 255 }, { 0, 0, 0, 0 }, false, 0, 2)
end
return self
end
-- resolved lazily on first draw, once init_skip() has run
function SongTransition:skip_armed()
if self.skip_on == nil then
self.skip_on = (tex.skip_enabled ~= nil) and tex.skip_enabled() or false
end
return self.skip_on
end
function SongTransition:update(current_ms, rainbow_up, song_info_fade)
if not self.t0 then self.t0 = current_ms end
local t = clamp(current_ms - self.t0, 0, SCENE_MS)
if self.is_second then
self.f = F_OUT + (F_END - F_OUT) * (t / SCENE_MS)
self.info = clamp(1.0 - t / FADE_OUT_MS, 0, 1)
else
self.f = F_IN + (F_STOP - F_IN) * (t / SCENE_MS)
-- use the engine's fade so the band tracks the title it draws on top
self.info = clamp(song_info_fade or ((t - FADE_DELAY) / FADE_MS), 0, 1)
end
end
-- draw a texture into an absolute screen rect (scale=0 -> exact x2/y2)
local function put(id, rest, x, y, w, h, a, src, blend)
if not id then return end
tex.draw_texture(id, {
x = x - rest[1], y = y - rest[2],
scale = 0, x2 = w, y2 = h,
fade = a, src = src, blend = blend,
})
end
local ORIGIN = { 0, 0 }
function SongTransition:draw_bg(total_offset, rainbow_up, mini_up, chara_down)
local f = self.f
-- 1. backdrop: two curved curtains, or the flat full-screen rainbow
if f >= F_FULL_A and f <= F_FULL_B then
put(self.rainbow, ORIGIN, 0, 0, 1920, 1080, 1)
else
local lx = track("#47@0", f, "tx", -240)
local ls = track("#47@0", f, "sx", 0.5)
local rx = track("#49@1", f, "tx", 2160)
local rs = track("#49@1", f, "sx", 0.5)
put(self.atlas_c, ORIGIN, lx - 480 * ls, 0, 960 * ls, 1080, 1, SRC_CURT_L)
put(self.atlas_c, ORIGIN, rx - 480 * rs, 0, 960 * rs, 1080, 1, SRC_CURT_R)
end
-- 2. additive glow along the bottom
local ga = track("#55@3", f, "a", 0)
if ga > 0.002 then
put(self.atlas_b, ORIGIN, 0, 568, 1920, 512, ga, SRC_GLOW, "additive")
end
-- 3. the eleven sparkles — fixed placements, one group alpha
local sa = track("#3@4", f, "a", 0)
if sa > 0.002 then
for i = 1, #STARS do
local s = STARS[i]
local d = 128 * s[3]
put(self.atlas_b, ORIGIN, s[1] - d * 0.5, s[2] - d * 0.5, d, d, sa, SRC_STAR)
end
end
-- 4. don / katsu on their arc
local da = track("don", f, "a", 1)
if da > 0.002 then
put(self.chara_l, REST_CHARA_L,
track("don", f, "tx", 160) - 280, track("don", f, "ty", 786) - 280,
560, 560, da)
end
local ka = track("katsu", f, "a", 1)
if ka > 0.002 then
put(self.chara_r, REST_CHARA_R,
track("katsu", f, "tx", 1760) - 280, track("katsu", f, "ty", 786) - 280,
560, 560, ka)
end
-- 5. the title band (opens/closes on the engine's title fade)
local info = self.info
if info > 0.002 then
local bw, bh
if self.is_second then
bw = 1600 * (1.5 - 0.5 * info)
bh = 256 * (0.1 + 0.9 * info)
else
bw = 1600 * (0.25 + 0.75 * info)
bh = 256
end
put(self.atlas_b, ORIGIN, 960 - bw * 0.5, 400 - bh * 0.5, bw, bh, info, SRC_BAND)
-- 6. the hint block on the same curve
if self.hint_title then
-- texture rows, all centred on x = 960
local function centred(t, y)
if t then t:draw({ x = math.floor(960 - t.width / 2 + 0.5), y = y, fade = info }) end
end
centred(self.hint_title, 568 + 165)
centred(self.hint_line1, 568 + 228)
centred(self.hint_line2, 568 + 282)
else
put(self.hint, REST_HINT, 0, 568, 1920, 512, info)
end
end
-- 7. 演奏スキップON badge, top-left, on the same fade
if self:skip_armed() and self.skip_badge then
put(self.skip_badge, SKIP_REST, SKIP_BADGE_X, SKIP_BADGE_Y, 624, 104, info)
if self.skip_text then
self.skip_text:draw({
x = SKIP_BADGE_CX - self.skip_text.width / 2,
y = SKIP_TEXT_CY - self.skip_text.height / 2,
fade = info,
})
end
end
end