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

300 lines
8.9 KiB
Lua

local anim = require("anim/sampler")
local CLIP = anim.safe("anim/dani_intro")
local M = {}
local SCREEN_W, SCREEN_H = 1920, 1080
local DOOR_W = 1200 -- door panel local width (main__shape5)
local KANJI = 400 -- each kanji cell is 400x400 (main__shape7/9/11/13)
local SCRIM_SRC = { x = 1200, y = 800, width = 64, height = 64 }
local CELLS = {
{ track = "#7@4", sx = 1200, sy = 0 }, -- 段
{ track = "#9@5", sx = 1600, sy = 0 }, -- 位
{ track = "#11@6", sx = 1200, sy = 400 }, -- 道
{ track = "#13@7", sx = 1600, sy = 400 }, -- 場
}
local CLOSE_POSE = 62 -- shut doors, scrim already eased to 0.5
local STAMP_FROM = 62 -- the arcade's scrim has just settled to 0.5
local STAMP_TO = 182 -- all four landed, before the fade
local OPEN_FROM = 182 -- kanji fade (182-185), doors part (187-200),
local OPEN_TO = 262 -- scrim clears (203), bg push-in + `out` fade
local MS_PER_FRAME = 1000.0 / 60.0
local CLOSE_MS = 300.0
local STAMP_MS = (STAMP_TO - STAMP_FROM) * MS_PER_FRAME -- 120 frames = 2000 ms
local OPEN_MS = (OPEN_TO - OPEN_FROM) * MS_PER_FRAME -- 80 frames = 1333 ms
local DURATION_MS = 2300.0
local function duration_ms() return DURATION_MS end
local FB_SCRIM = 0.5 -- #1@3's settled alpha (f62..193 in the arcade data)
local function clamp01(v) return math.max(0.0, math.min(1.0, v)) end
local function clip() return CLIP end
local _id, _id_tried
local function tex_id()
if not _id_tried then
_id_tried = true
_id = tex.get_texture("song_select/dan_transition/background")
end
return _id
end
local _bg, _bg_tried
local function bg_id()
if not _bg_tried then
_bg_tried = true
_bg = tex.get_texture("dan_select/global/bg")
end
return _bg
end
local function fallback_door()
return -240, 960, DOOR_W, 0, FB_SCRIM
end
local function fallback_kanji(i, shown)
if not shown or shown <= 0 then return nil end
return 1.0, 1.0 -- scale 1, alpha 1 — the landed pose, nothing finer is recoverable
end
local function fire_sfx()
if M.sfx_done then return end
if audio and audio.play_sound then audio.play_sound("dan_intro", "sound") end
M.sfx_done = true
end
local function phases()
local d = duration_ms()
local move = CLOSE_MS + STAMP_MS
local k = (d >= move) and 1.0 or (d / move) -- compress only if we must
local c = CLOSE_MS * k
local st = STAMP_MS * k
return c / d, (c + st) / d
end
-- u (0..1 across our transition) -> arcade frame.
local function frame_of(u)
local close_u, stamp_u = phases()
if u <= close_u then
return CLOSE_POSE
elseif u <= stamp_u then
local v = (u - close_u) / (stamp_u - close_u)
return STAMP_FROM + (STAMP_TO - STAMP_FROM) * v
end
return STAMP_TO -- parked: doors shut, kanji up
end
local function draw_frame(f, op)
local id = tex_id()
if not id then return false end
local c = clip()
op = (op == nil) and 1.0 or clamp01(op)
if op <= 0 then return true end
local btx, bty, bsx, _, ba = c:at("#3@0", f)
if btx and ba and ba > 0 then
local bid = bg_id()
if bid then
local w, h = SCREEN_W * (bsx or 1), SCREEN_H * (bsx or 1)
tex.draw_texture(bid, {
x = (btx or 960) - w * 0.5, y = (bty or 540) - h * 0.5,
x2 = w - SCREEN_W, y2 = h - SCREEN_H, fade = ba * op,
})
end
end
-- doors
local lx, rx, dw, dtop, scrim
local rtx, rty, rsx = c:at("#5@1", f)
local ltx, _, lsx = c:at("#5@2", f)
if rtx and ltx then
dw = DOOR_W * math.abs(rsx)
rx = rtx
lx = ltx + DOOR_W * lsx
dtop = rty - SCREEN_H * 0.5
scrim = c:get("#1@3", f, "a") or FB_SCRIM
else
lx, rx, dw, dtop, scrim = fallback_door()
end
-- left door = the same panel mirrored (a negative src width is raylib's flip)
tex.draw_texture(id, { src = { x = 0, y = 0, width = -DOOR_W, height = SCREEN_H },
x = lx, y = dtop, x2 = dw, y2 = SCREEN_H, fade = op })
tex.draw_texture(id, { src = { x = 0, y = 0, width = DOOR_W, height = SCREEN_H },
x = rx, y = dtop, x2 = dw, y2 = SCREEN_H, fade = op })
-- the full-screen black at depth 3: above both doors, below all four kanji
if scrim and scrim > 0 then
tex.draw_texture(id, { src = SCRIM_SRC,
x = 0, y = 0, x2 = SCREEN_W, y2 = SCREEN_H,
fade = scrim * op })
end
-- kanji
for i = 1, #CELLS do
local cell = CELLS[i]
local cx, cy, s, a
local tx, ty, sx, _, al = c:at(cell.track, f)
-- glyph tracks start at their placed frame, so gate explicitly
local first = c:has(cell.track) and c.tracks[cell.track][1][1] or nil
if tx and first and f >= first then
cx, cy, s, a = tx, ty, sx, al
elseif not tx then
local fs, fa = fallback_kanji(i, (f >= STAMP_TO) and 1 or 0)
if fs then
cx = (i == 1 or i == 3) and 455 or 1420
cy = (i <= 2) and 255 or 765
s, a = fs, fa
end
end
if cx and a and a > 0 then
local w = KANJI * s
tex.draw_texture(id, {
src = { x = cell.sx, y = cell.sy, width = KANJI, height = KANJI },
x = cx - w * 0.5,
y = cy - w * 0.5,
x2 = w, y2 = w, fade = a * op,
})
end
end
return true
end
M.armed = false
M.engine_u = nil
M.has_draw_top = false
M.t0 = nil
M.armed_open = false
M.open_t0 = nil
M.sfx_done = false
function M.arm(active)
M.armed = active and true or false
if not M.armed then
M.t0 = nil -- let the fallback clock restart cleanly next time
end
end
function M.set_progress(p)
M.has_draw_top = true
if p and p >= 0 then
M.engine_u = p
else
M.engine_u = nil
M.armed_open = false
M.open_t0 = nil
end
end
function M.progress()
if M.has_draw_top then
-- the engine supplies real progress every frame; nil/negative = not running
if M.engine_u and M.engine_u >= 0 then return clamp01(M.engine_u) end
return nil
end
-- fallback: no draw_top on this engine build, self-clock off arm()
if not M.armed then return nil end
local now = tex.get_current_ms()
if not M.t0 then M.t0 = now end
local d = duration_ms()
local u = (now - M.t0) / d
if u >= 1.0 then return nil end
return u
end
local function off_screen()
local scr = tex.current_screen and tex.current_screen() or nil
if scr == nil or scr == "SONG_SELECT" or scr == "DAN_SELECT" then return false end
M.engine_u = nil
M.armed = false
M.armed_open = false
M.open_t0 = nil
M.t0 = nil
M.open_held = false
return true
end
function M.draw()
if off_screen() then return false end
local u = M.progress()
if not u then return false end
fire_sfx(u)
local close_u = phases()
local f = frame_of(u)
if not draw_frame(f, (u <= close_u) and (u / close_u) or nil) then return false end
if not M.armed_open then
M.armed_open = true
M.open_t0 = nil
end
M._drawn_ms = tex.get_current_ms()
return true
end
-- true iff M.draw() already painted this exact engine frame
function M.drawn_this_frame()
return M._drawn_ms ~= nil and M._drawn_ms == tex.get_current_ms()
end
local INTRO_FROM = 5 -- clip label `in`
local REVEAL_F = OPEN_FROM -- 182
function M.draw_open()
if off_screen() then __hss_dan_intro = nil; return false end
local req = __hss_dan_intro
if req ~= nil then
__hss_dan_intro = nil
if req == "full" and not M.armed_open then
M.armed_open = true
M.open_t0 = nil
M.open_held = false
M.open_from = INTRO_FROM
fire_sfx()
elseif req == "open" then
M.open_from = OPEN_FROM
end
end
if not M.armed_open then return false end
local now = tex.get_current_ms()
if not M.open_t0 then M.open_t0 = now end
local from = M.open_from or OPEN_FROM
local f = from + (now - M.open_t0) / MS_PER_FRAME
local ready = (__hss_dan_ready ~= false)
if not ready then
if f > REVEAL_F then
f = REVEAL_F
M.open_held = true
end
elseif M.open_held then
M.open_held = false
M.open_t0 = now - (REVEAL_F - from) * MS_PER_FRAME
f = REVEAL_F
end
M.open_f = f
if f >= OPEN_TO then
M.armed_open = false
M.open_t0 = nil
M.open_held = false
M.open_from = nil
M.sfx_done = false
M.engine_u = nil
M.t0 = nil
return false
end
return draw_frame(f, nil)
end
return M