Files
YataiDONNijiiro/Scripts/song_select/file_navigator/box_base.lua
T
2026-10-01 11:28:23 -04:00

574 lines
25 KiB
Lua

-- Shared song-select board chrome, text/colour helpers and the wheel/folder/song
-- animation timers, split out of song_select.lua. All positions are 1080p pixels;
-- box positions and engine animations are already in screen pixels.
local DIFF_BACK, DIFF_MODIFIER, DIFF_NEIRO = -3, -2, -1
local DIFF_EASY, DIFF_NORMAL, DIFF_HARD, DIFF_ONI, DIFF_URA = 0, 1, 2, 3, 4
local CROWN_NONE, CROWN_CLEAR, CROWN_FC, CROWN_DFC = 0, 1, 2, 3
-- Arcade board geometry (960x352 board texture: top/bottom caps + centre slice).
local BOARD_W = 960
local CAP_H = 56
local CEN_SRC_H = 240
local CEN_CLOSED_H = 52
local CEN_OPEN_H = 240
local GLOW_CAP_H = 80 -- cursor glow 1024x400: 80 / 240 / 80
local GLOW_W = 1024
local GUIDE_SELECT_CX, GUIDE_DECIDE_CX, GUIDE_LABEL_CY = 904, 1077, 980
local GUIDE_LABEL_SPACING = 2.0
-- tex.get_texture tries "<name>" then "<name>_<language>"; fall back to _en/_ja so we
-- never hand draw_texture a nil id.
local function get_any(subset, name)
return tex.get_texture("song_select/" .. subset .. "/" .. name)
or tex.get_texture("song_select/" .. subset .. "/" .. name .. "_en")
or tex.get_texture("song_select/" .. subset .. "/" .. name .. "_ja")
end
-- draw a w x h texture centred on (cx, cy)
local function draw_centered(id, cx, cy, w, h, extra)
extra = extra or {}
extra.x = cx - w / 2
extra.y = cy - h / 2
tex.draw_texture(id, extra)
end
-- Same, but subtracts the texture.json base offset the engine adds when it draws in 2P.
local function draw_centered_b(id, base, cx, cy, w, h, extra)
extra = extra or {}
extra.x = cx - w / 2 - base[1]
extra.y = cy - h / 2 - base[2]
tex.draw_texture(id, extra)
end
local SEL_BASE_BALLOON = { 683, 308 } -- 1p/2p_balloon(_half) = cursor bubble
-- Title centred on (bx, cy). max_w (optional): squeeze long text horizontally down to the
-- box width, never below min_scale (default 0.75). Destination x/y snap to whole pixels.
local function draw_title(text, bx, cy, fade, max_w, min_scale)
if not text then return end
local w = text.width
if max_w and w > max_w then
w = math.floor(math.max(max_w, w * (min_scale or 0.75)) + 0.5)
end
text:draw({ x = math.floor(bx - w / 2 + 0.5), y = math.floor(cy - text.height / 2 + 0.5),
x2 = w - text.width, fade = fade })
end
-- control-guide pill labels
local function draw_guide_label(t, cx, cy, fade)
if not t then return end
t:draw({ x = math.floor(cx - (t.width - GUIDE_LABEL_SPACING) / 2 + 0.5),
y = math.floor(cy - t.height / 2 + 0.5), fade = fade })
end
local SONG_TEXT_BOX_W = 860 -- main_song_board text_title / text_sub box width
local DS_TEXT_BOX_W = 1000 -- main_diff text_title_song / text_song_title_sub box width
-- OutlinedText multiplies the outline thickness by screen_scale, so divide the stage-px
-- border back out.
local function outline(border) return border / tex.get_screen_scale() end
-- Board title/subtitle outline colour by genre frame (0..12).
local BOARD_OUTLINE = {
[0] = { 18, 72, 76, 255 },
[1] = { 18, 72, 76, 255 },
[2] = { 155, 24, 99, 255 },
[3] = { 78, 29, 118, 255 },
[4] = { 150, 31, 0, 255 },
[5] = { 101, 67, 42, 255 },
[6] = { 20, 79, 20, 255 },
[7] = { 165, 50, 0, 255 },
[8] = { 38, 52, 73, 255 },
[9] = { 18, 72, 76, 255 },
[10] = { 0, 25, 95, 255 },
[11] = { 100, 6, 22, 255 },
[12] = { 119, 26, 45, 255 },
}
local BOARD_OUTLINE_BACK = { 76, 50, 20, 255 }
local function board_outline(frame)
return BOARD_OUTLINE[frame or 0] or BOARD_OUTLINE[0]
end
local function clamp01(v) return math.max(0.0, math.min(1.0, v)) end
-- Board chrome: 3-slice (cap / centre / cap) from a 960x352 texture, centred on (bx,by).
-- sx = optional horizontal scale (the folder-ENTER furl/unfurl); a furled board is drawn
-- from flat column slices so its edges stay sharp.
local function draw_board(bar_id, frame, bx, by, cen_h, fade, crop_center, sx)
local w = BOARD_W
if sx and sx ~= 1.0 then w = math.max(2, math.floor(BOARD_W * sx + 0.5)) end
local x = math.floor(bx - w / 2 + 0.5)
local top = math.floor(by - cen_h / 2 + 0.5) - CAP_H
local src_y, src_h = CAP_H, CEN_SRC_H
if crop_center ~= false and cen_h < CEN_SRC_H then
src_h = math.max(1, math.floor(cen_h + 0.5))
src_y = CAP_H + math.floor((CEN_SRC_H - src_h) / 2)
cen_h = src_h
end
local bands = { { top, 0, CAP_H, CAP_H }, { top + CAP_H, src_y, src_h, cen_h },
{ top + CAP_H + cen_h, CAP_H + CEN_SRC_H, CAP_H, CAP_H } }
if w == BOARD_W then
for _, b in ipairs(bands) do
tex.draw_texture(bar_id, { frame = frame, x = x, y = b[1], y2 = b[4] - 352, fade = fade,
src = { x = 0, y = b[2], width = BOARD_W, height = b[3] } })
end
return
end
local CORNER, BAND, PANEL = 40, 43, 794 -- source widths; 40+43+794+43+40 = 960
local cols
if w >= 2 * CORNER then
local corner = CORNER
local inner = w - 2 * corner
local band = math.floor(inner * BAND / (2 * BAND + PANEL) + 0.5)
local panel = inner - 2 * band
cols = {
{ 0, CORNER, x, corner },
{ CORNER + BAND / 2 - 4, 8, x + corner, band },
{ BOARD_W / 2 - 4, 8, x + corner + band, panel },
{ BOARD_W - CORNER - BAND / 2 - 4, 8, x + corner + band + panel, band },
{ BOARD_W - CORNER, CORNER, x + w - corner, corner },
}
else
-- narrower than both corners: draw the outline columns + a flat centre slice
local ol = math.max(2, math.min(8, math.floor(w / 6)))
local mid = math.max(0, w - 2 * ol)
cols = {
{ 13, 8, x, ol },
{ BOARD_W / 2 - 4, 8, x + ol, mid },
{ 940, 8, x + ol + mid, ol },
}
end
for _, b in ipairs(bands) do
for _, c in ipairs(cols) do
if c[4] > 0 then
tex.draw_texture(bar_id, { frame = frame, x = c[3], y = b[1], x2 = c[4] - BOARD_W, y2 = b[4] - 352,
fade = fade, src = { x = c[1], y = b[2], width = c[2], height = b[3] } })
end
end
end
end
-- Yellow cursor glow around the board.
local function draw_glow(glow_id, bx, by, cen_h, fade)
local x = math.floor(bx - GLOW_W / 2 + 0.5)
local top = math.floor(by - cen_h / 2 + 0.5) - CAP_H - 24
local src_h = math.max(1, math.min(240, math.floor(cen_h + 0.5)))
cen_h = src_h
tex.draw_texture(glow_id, { x = x, y = top, y2 = GLOW_CAP_H - 400, fade = fade,
src = { x = 0, y = 0, width = GLOW_W, height = GLOW_CAP_H } })
tex.draw_texture(glow_id, { x = x, y = top + GLOW_CAP_H, y2 = cen_h - 400, fade = fade,
src = { x = 0, y = GLOW_CAP_H + math.floor((240 - src_h) / 2), width = GLOW_W, height = src_h } })
tex.draw_texture(glow_id, { x = x, y = top + GLOW_CAP_H + cen_h, y2 = GLOW_CAP_H - 400, fade = fade,
src = { x = 0, y = GLOW_CAP_H + 240, width = GLOW_W, height = GLOW_CAP_H } })
end
local glow_ok, glow_anim = pcall(require, "anim/sampler")
if not glow_ok then glow_anim = { safe = function()
local n = {}
n.at = function() return nil end; n.get = function() return nil end
n.label = function() return nil end; n.has = function() return false end
n.first = function() return 0 end; n.last = function() return 0 end
return n
end } end
local GLOW_CLIP = glow_anim.safe("anim/cursor_glow")
local GLOW_LEN = math.max(1, GLOW_CLIP:last() - GLOW_CLIP:first() + 1) -- 60 frames = 1 s
-- song-board oni/ura chip cross-fade loop
local URA_LOOP = glow_anim.safe("anim/song_board_ura")
local URA_LOOP_F0 = URA_LOOP:first() -- label 'ura' = f10
local URA_LOOP_LEN = math.max(1, URA_LOOP:last() - URA_LOOP_F0 + 1) -- 180 frames = 3 s
local function glow_pulse()
local f = (tex.get_current_ms() * 0.06) % GLOW_LEN
return GLOW_CLIP:get("#12@0", f, "a") or 1.0
end
-- Arcade genre board open / close, sampled from anim/folder_board.
local folder_anim = glow_anim
local FOLDER_CLIP = folder_anim.safe("anim/folder_board")
local F_SELECT_ON = FOLDER_CLIP:label("select_on") or 5
local F_SELECT_OFF = FOLDER_CLIP:label("select_off") or 30
local FOLDER_CLOSE_END_F = 8 -- select_off + 8 = f38, the last frame that moves
local FOLDER_CLOSE_MS = FOLDER_CLOSE_END_F / 0.06
-- Duration the engine keeps the box "closed" between navigate() and expand_box().
local NAV_REOPEN_MS = 166
-- A transient navigation closure is < NAV_REOPEN_MS; anything longer resets the open timer.
local STALE_TIMER_MS = math.max(FOLDER_CLOSE_MS, NAV_REOPEN_MS) + 50
local CABINET_OPEN_WAIT_MS = 61 / 120 * 1000 -- = 508.33 ms
local EXTRA_OPEN_HOLD_MS = math.max(0, CABINET_OPEN_WAIT_MS - NAV_REOPEN_MS) -- = 342.33 ms
-- centre band scaleY at the two rest states -> 0..1 openness
local SY_CLOSED, SY_OPEN = 0.2168, 1.0
-- Song board timeline, same labels as the genre board.
local SONG_CLIP = folder_anim.safe("anim/song_board")
local S_SELECT_ON = SONG_CLIP:label("select_on") or 5
local S_SELECT_OFF = SONG_CLIP:label("select_off") or 30
local SONG_CLOSE_END_F = 13
local SONG_CLOSE_MS = SONG_CLOSE_END_F / 0.06 -- = 216.7 ms
local SONG_STALE_MS = math.max(SONG_CLOSE_MS, NAV_REOPEN_MS) + 50
-- Folder enter/exit wheel choreography: slots sweep out along the tilted line, hold, then
-- fly back in (sampled from anim/wheel_decide).
local WHEEL_FLY = folder_anim.safe("anim/wheel_decide")
local FLY_END_F = WHEEL_FLY:label("return") or 150
local FLY_TRACKS_UP = { "kanban_genre_2", "kanban_genre_4", "kanban_genre_6", "kanban_genre_8" }
local FLY_TRACKS_DOWN = { "kanban_genre_3", "kanban_genre_5", "kanban_genre_7" }
local FLY_CENTER_Y, FLY_ROW_GAP, FLY_ROW_PITCH = 540, 120, 135
do -- wheel geometry is skin_config-driven; follow it if restated
local ok, pitch = pcall(tex.get_skin_config, "song_select_row_pitch")
if ok and pitch and pitch.x and pitch.x > 0 then
FLY_ROW_PITCH = pitch.x
if pitch.y and pitch.y > 0 then FLY_ROW_GAP = pitch.y end
end
end
-- A wheel animation-timer key must be unique per BOX. text_name is not (two charts of one
-- song share a title), so prefer box:path() and fall back to title+subtitle+ex_data.
local function box_id(box)
local ok, p = pcall(function() return box:path() end)
if ok and p and p ~= "" then return p end
local id = box.text_name or ""
local ok_s, sub = pcall(function() return box.text_subtitle end)
if ok_s and sub then id = id .. "\1" .. sub end
local ok_x, x = pcall(function() return box:ex_data_flag() end)
if ok_x and x then id = id .. "\1" .. tostring(x) end
return id
end
-- Folder enter/exit leg state (anim/genre_open), driven by the engine's wheel events.
local folder_leg = {
clip = nil,
t0 = nil, f0 = 0, kind = nil, swapped = false,
open_f0 = 45, open_swap = 114, open_end = 145,
close_f0 = 150, close_swap = 219, close_end = 250,
open_furl = 100, -- first frame the decided genre board furls shut
seq = nil,
have_events = false,
snap_once = false, snap_ms = nil,
nohold_once = false, nohold_ms = nil,
}
do
local ok, t = pcall(require, "anim/genre_open")
if ok and type(t) == "table" and type(t.tracks) == "table" then
folder_leg.clip = t
for _, k in ipairs({ "open_f0", "open_swap", "open_end",
"close_f0", "close_swap", "close_end" }) do
if t[k] then folder_leg[k] = t[k] end
end
local rows = t.tracks and t.tracks.genre_board_sx
if rows then
for i = 1, #rows do
if rows[i][2] < 0.999 then folder_leg.open_furl = rows[i][1]; break end
end
end
end
end
-- Linear sampler over the generated {frame, v1, v2...} rows.
local function go_get(name, f, col)
if not folder_leg.clip then return nil end
local rows = folder_leg.clip.tracks[name]
if not rows or #rows == 0 then return nil end
col = col or 1
if f <= rows[1][1] then return rows[1][col + 1] end
if f >= rows[#rows][1] then return rows[#rows][col + 1] end
for i = 1, #rows - 1 do
local a, b = rows[i], rows[i + 1]
if f >= a[1] and f <= b[1] then
local span = b[1] - a[1]
if span <= 0 then return a[col + 1] end
local u = (f - a[1]) / span
return a[col + 1] + (b[col + 1] - a[col + 1]) * u
end
end
return rows[#rows][col + 1]
end
-- Current clip frame, clamped just short of the swap until the engine signals it.
local function leg_f()
if not folder_leg.clip or not folder_leg.t0 then return nil end
local f = folder_leg.f0 + (tex.get_current_ms() - folder_leg.t0) * 0.06
if not folder_leg.swapped then
-- hold until the swap: the open leg parks on the furl, the close leg one frame
-- short of its swap, so the board stays up as the loading state
local hold = (folder_leg.kind == "close") and (folder_leg.close_swap - 1) or (folder_leg.open_furl - 1)
if f > hold then f = hold end
return f
end
local fin = (folder_leg.kind == "close") and folder_leg.close_end or folder_leg.open_end
if f >= fin then folder_leg.t0 = nil; folder_leg.kind = nil; return nil end
return f
end
-- The engine's own box fade, ignored during the pre-swap half of the close leg.
local function box_fade(box)
if folder_leg.kind == "close" and folder_leg.t0 and not folder_leg.swapped then return 1.0 end
return box.fade.attribute
end
-- Start / advance the leg. `begin` puts the clock on the label frame; `swap` re-anchors
-- it on the content-swap frame.
local function leg_event(id)
local now = tex.get_current_ms()
if id == 3 then -- OPEN_BEGIN
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.open_f0, "open", false
folder_leg.dec_id, folder_leg.dec_box = folder_leg.last_open_id, folder_leg.last_open_box
elseif id == 4 then -- OPEN_SWAP
local f = (folder_leg.kind == "open" and folder_leg.t0) and (folder_leg.f0 + (now - folder_leg.t0) * 0.06) or nil
if not f or f >= folder_leg.open_furl - 1 then
folder_leg.t0, folder_leg.f0 = now, folder_leg.open_furl
end
folder_leg.kind, folder_leg.swapped = "open", true
folder_leg.snap_once, folder_leg.snap_ms = true, now
elseif id == 5 then -- CLOSE_BEGIN
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.close_f0, "close", false
elseif id == 6 then -- CLOSE_SWAP
folder_leg.t0, folder_leg.f0, folder_leg.kind, folder_leg.swapped = now, folder_leg.close_swap, "close", true
folder_leg.snap_once, folder_leg.snap_ms = true, now
elseif id == 1 or id == 7 then -- SCENE_ENTRY / COURSE_BACK
folder_leg.nohold_once, folder_leg.nohold_ms = true, now
end
-- id == 2 (CURSOR_MOVE) needs no flag.
end
-- The wheel clip shares the board clips' labels, so one clock drives all three.
local function fly_offset(box)
local f = leg_f()
if not f then return 0.0, 0.0 end
if folder_leg.kind == "open" then
if f >= FLY_END_F then return 0.0, 0.0 end
elseif folder_leg.kind == "close" then
if f >= (WHEEL_FLY:label("genre_deceide2") or 255) - 1 then return 0.0, 0.0 end
else
return 0.0, 0.0
end
local st = box:draw_state()
if st == "open" or st == "diff_select" then return 0.0, 0.0 end -- the decided board stays
local d = box:box_y() - FLY_CENTER_Y
local ring = math.floor((math.abs(d) - FLY_ROW_GAP) / FLY_ROW_PITCH + 0.5)
if ring < 1 then ring = 1 end
local track
if d < 0 then track = FLY_TRACKS_UP[math.min(ring, #FLY_TRACKS_UP)]
else track = FLY_TRACKS_DOWN[math.min(ring, #FLY_TRACKS_DOWN)] end
local x, y = WHEEL_FLY:get(track, f, "tx"), WHEEL_FLY:get(track, f, "ty")
if not x or not y then return 0.0, 0.0 end
local rx = WHEEL_FLY:get(track, FLY_END_F, "tx") or 0.0
local ry = WHEEL_FLY:get(track, FLY_END_F, "ty") or 0.0
return x - rx, y - ry
end
-- true while `box` is the genre board the player just decided on (open leg running)
local function is_decided(box)
return folder_leg.kind == "open" and folder_leg.t0 ~= nil and folder_leg.dec_id ~= nil and box_id(box) == folder_leg.dec_id
end
-- How a fresh open starts: "hold" (cursor move), "snap" (folder enter/exit), "nohold"
-- (scene entry / course back). One-shots are armed by the engine's real transitions.
-- (diff-seen timestamp lives on folder_leg so box_song can mutate it across the module split.)
local OPEN_SNAP_TTL_MS = 2000.0
local OPEN_NOHOLD_TTL_MS = 3000.0
local function open_start_mode(now)
if folder_leg.snap_once then
folder_leg.snap_once = false
if folder_leg.snap_ms and (now - folder_leg.snap_ms) < OPEN_SNAP_TTL_MS then return "snap" end
end
if folder_leg.nohold_once then
folder_leg.nohold_once = false
if folder_leg.nohold_ms and (now - folder_leg.nohold_ms) < OPEN_NOHOLD_TTL_MS then return "nohold" end
end
-- Legacy fallback for a binary without the event channel.
if not folder_leg.have_events and folder_leg.diff_seen_ms and (now - folder_leg.diff_seen_ms) < 300.0 then
return "nohold"
end
return "hold"
end
-- Shared wheel-panel open/close wall-clock timers. Keys are "<kind>\0<name>".
local wheel_open_start = {}
local wheel_open_mode = {}
local wheel_open_seen = {}
local wheel_close_at = {}
local wheel_close_start= {}
local wheel_last_draw = {}
local wheel_last_fresh_key, wheel_last_fresh_ms = nil, nil
-- Returns t in ms into the select_on growth window (0..233, clamped), or nil during the
-- 508 ms pre-growth hold (draw fully closed).
local function wheel_open_t(key)
local now = tex.get_current_ms()
wheel_close_start[key] = nil
wheel_open_seen[key] = now
wheel_close_at[key] = nil
-- off-screen for > NAV_REOPEN_MS with the animation finished -> reset the timer
local last = wheel_last_draw[key]
wheel_last_draw[key] = now
if wheel_open_start[key] and last and (now - last) > NAV_REOPEN_MS then
local hold = (wheel_open_mode[key] == "hold") and EXTRA_OPEN_HOLD_MS or 0.0
if (now - wheel_open_start[key]) >= (hold + 233.0) then
wheel_open_start[key] = nil
end
end
if not wheel_open_start[key] then
wheel_open_start[key] = now
wheel_open_mode[key] = open_start_mode(now)
wheel_last_fresh_key, wheel_last_fresh_ms = key, now
end
local mode = wheel_open_mode[key]
if mode == "snap" then return 233.0 end
local elapsed = now - wheel_open_start[key]
local hold = (mode == "hold") and EXTRA_OPEN_HOLD_MS or 0.0
if elapsed < hold then return nil end
return math.min(elapsed - hold, 233.0)
end
-- Closed-state bookkeeping + close animation. Returns e in ms into the select_off
-- timeline while closing, else nil (draw at rest).
local function wheel_close_step(key, close_ms, stale_ms)
local now = tex.get_current_ms()
if not wheel_close_start[key] then
wheel_close_start[key] = now
end
if wheel_open_start[key] and (now - wheel_close_start[key]) > stale_ms then
wheel_open_start[key] = nil
end
local seen = wheel_open_seen[key]
if seen and now - seen < 100 then -- was drawn open a frame ago -> start closing
wheel_close_at[key] = now
wheel_open_seen[key] = nil
end
local c = wheel_close_at[key]
if c then
local e = now - c
if e < close_ms then return e end
wheel_close_at[key] = nil
end
return nil
end
-- `f` is an absolute arcade frame on sprite 73's timeline.
-- pb = board progress 0..1, ch_alpha = character alpha, ch_off = character offset,
-- ta = intro ("N songs") text alpha.
local function folder_sample(f)
local sy = FOLDER_CLIP:get("instance_board_center", f, "sy") or SY_CLOSED
local pb = clamp01((sy - SY_CLOSED) / (SY_OPEN - SY_CLOSED))
local ch_alpha = FOLDER_CLIP:get("instance_character_l", f, "a") or 0.0
local ch_x = FOLDER_CLIP:get("instance_character_l", f, "tx") or -340.0
local ta = FOLDER_CLIP:get("text_kanban_intro_genre", f, "a") or 0.0
return pb, ch_alpha, -ch_x, ta
end
local function folder_open_timeline(t)
return folder_sample(F_SELECT_ON + t * 0.06)
end
local function folder_close_timeline(e)
return folder_sample(F_SELECT_OFF + e * 0.06)
end
-- Same reader for the song board: pb = board progress, ia = contents alpha, ic = NEW-icon alpha.
local function song_sample(f)
local sy = SONG_CLIP:get("instance_board_center", f, "sy") or SY_CLOSED
local pb = clamp01((sy - SY_CLOSED) / (SY_OPEN - SY_CLOSED))
local ia = SONG_CLIP:get("song_kanban_info", f, "a") or (pb >= 1.0 and 1.0 or 0.0)
local ic = SONG_CLIP:get("instance_icon", f, "a") or (1.0 - pb)
return pb, clamp01(ia), clamp01(ic)
end
local function song_open_timeline(t) return song_sample(S_SELECT_ON + t * 0.06) end
local function song_close_timeline(e) return song_sample(S_SELECT_OFF + e * 0.06) end
-- 演奏オプション header title of the C++ option panel.
local MOD_SLIDE, MOD_SLIDE_MS, MOD_JSON_Y, MOD_HEADER_CY, MOD_CX = 548, 333.33, 1080, 49, 215
-- Difficulty select geometry (shared by the diff-select board and the selector cursor).
local DS_SLOT_X = { [DIFF_BACK] = 440, [DIFF_MODIFIER] = 572, [DIFF_NEIRO] = 440,
[DIFF_EASY] = 745, [DIFF_NORMAL] = 960, [DIFF_HARD] = 1175, [DIFF_ONI] = 1390 }
local DS_BOARD_Y = 583
local DS_COURSE_NAME_DY = 49 -- course-name centre below the card origin
local URA_ANIM_MS = 1500 -- oni_main change_ura/change_oni: 90 frames @ 60 fps
local URA_SWAP_MS = 450 -- board_oni -> board_ura swap
local URA_CELLS = 90
local DS_BTN_Y = 462
local DS_CURSOR_Y = 370
local DS_NEIRO_Y = 602
-- main_diff_mark: 680x680, drawn under the buttons/cards.
local DS_MARK_X = { [1] = 200, [2] = 1720 }
local DS_MARK_Y = 408
-- main_diff_mark 'course_decide_start', one row per frame:
-- { mark alpha, mark y-scale, white alpha, white y-scale, white backing alpha }.
local DS_MARK_DECIDE = {
{ 0.5, 1, 0, 1, 0 },
{ 0.5938, 1.0447, 0.1914, 1.0447, 0.1016 },
{ 0.6797, 1.0847, 0.3594, 1.0847, 0.1992 },
{ 0.7539, 1.12, 0.5117, 1.12, 0.3008 },
{ 0.8203, 1.1506, 0.6406, 1.1506, 0.3984 },
{ 0.875, 1.1765, 0.75, 1.1765, 0.5 },
{ 0.9219, 1.1976, 0.8398, 1.1976, 0.6016 },
{ 0.9531, 1.2141, 0.9102, 1.2141, 0.6992 },
{ 0.9805, 1.2259, 0.9609, 1.2259, 0.8008 },
{ 0.9961, 1.2329, 0.9883, 1.2329, 0.8984 },
{ 1, 1.2353, 1, 1.2353, 1 },
{ 1, 1.2229, 0.9961, 1.2346, 1 },
{ 1, 1.2105, 0.9883, 1.2327, 1 },
{ 1, 1.1981, 0.9766, 1.2294, 1 },
{ 1, 1.1858, 0.957, 1.2248, 1 },
{ 1, 1.1733, 0.9297, 1.219, 1 },
{ 1, 1.161, 0.8984, 1.2118, 1 },
{ 1, 1.1486, 0.8633, 1.2033, 1 },
{ 1, 1.1362, 0.8242, 1.1936, 1 },
{ 1, 1.1238, 0.7773, 1.1825, 1 },
{ 1, 1.1114, 0.7227, 1.1701, 1 },
{ 1, 1.0991, 0.6641, 1.1564, 1 },
{ 1, 1.0867, 0.6016, 1.1414, 1 },
{ 1, 1.0743, 0.5312, 1.1251, 1 },
{ 1, 1.0619, 0.457, 1.1075, 1 },
{ 1, 1.0495, 0.375, 1.0886, 1 },
{ 1, 1.0371, 0.2891, 1.0684, 1 },
{ 1, 1.0247, 0.1992, 1.0469, 1 },
{ 1, 1.0124, 0.1016, 1.0241, 1 },
{ 1, 1, 0, 1, 1 },
}
local DS_MARK_DECIDE_MS = 1000 / 60
return {
DIFF_BACK = DIFF_BACK, DIFF_MODIFIER = DIFF_MODIFIER, DIFF_NEIRO = DIFF_NEIRO,
DIFF_EASY = DIFF_EASY, DIFF_NORMAL = DIFF_NORMAL, DIFF_HARD = DIFF_HARD,
DIFF_ONI = DIFF_ONI, DIFF_URA = DIFF_URA,
CROWN_NONE = CROWN_NONE, CROWN_CLEAR = CROWN_CLEAR, CROWN_FC = CROWN_FC, CROWN_DFC = CROWN_DFC,
BOARD_W = BOARD_W, CAP_H = CAP_H, CEN_SRC_H = CEN_SRC_H,
CEN_CLOSED_H = CEN_CLOSED_H, CEN_OPEN_H = CEN_OPEN_H,
GLOW_CAP_H = GLOW_CAP_H, GLOW_W = GLOW_W,
SEL_BASE_BALLOON = SEL_BASE_BALLOON,
GUIDE_SELECT_CX = GUIDE_SELECT_CX, GUIDE_DECIDE_CX = GUIDE_DECIDE_CX,
GUIDE_LABEL_CY = GUIDE_LABEL_CY, GUIDE_LABEL_SPACING = GUIDE_LABEL_SPACING,
SONG_TEXT_BOX_W = SONG_TEXT_BOX_W, DS_TEXT_BOX_W = DS_TEXT_BOX_W,
BOARD_OUTLINE = BOARD_OUTLINE, BOARD_OUTLINE_BACK = BOARD_OUTLINE_BACK,
FOLDER_CLOSE_MS = FOLDER_CLOSE_MS, STALE_TIMER_MS = STALE_TIMER_MS,
SONG_CLOSE_MS = SONG_CLOSE_MS, SONG_STALE_MS = SONG_STALE_MS,
URA_LOOP = URA_LOOP, URA_LOOP_F0 = URA_LOOP_F0, URA_LOOP_LEN = URA_LOOP_LEN,
MOD_SLIDE = MOD_SLIDE, MOD_SLIDE_MS = MOD_SLIDE_MS, MOD_JSON_Y = MOD_JSON_Y,
MOD_HEADER_CY = MOD_HEADER_CY, MOD_CX = MOD_CX,
DS_SLOT_X = DS_SLOT_X, DS_BOARD_Y = DS_BOARD_Y, DS_COURSE_NAME_DY = DS_COURSE_NAME_DY,
URA_ANIM_MS = URA_ANIM_MS, URA_SWAP_MS = URA_SWAP_MS, URA_CELLS = URA_CELLS,
DS_BTN_Y = DS_BTN_Y, DS_CURSOR_Y = DS_CURSOR_Y, DS_NEIRO_Y = DS_NEIRO_Y,
DS_MARK_X = DS_MARK_X, DS_MARK_Y = DS_MARK_Y, DS_MARK_DECIDE = DS_MARK_DECIDE,
DS_MARK_DECIDE_MS = DS_MARK_DECIDE_MS,
get_any = get_any, draw_centered = draw_centered, draw_centered_b = draw_centered_b,
draw_title = draw_title, draw_guide_label = draw_guide_label,
outline = outline, board_outline = board_outline, clamp01 = clamp01,
draw_board = draw_board, draw_glow = draw_glow, glow_pulse = glow_pulse,
box_id = box_id, go_get = go_get, leg_f = leg_f, box_fade = box_fade,
leg_event = leg_event, fly_offset = fly_offset, is_decided = is_decided,
open_start_mode = open_start_mode, wheel_open_t = wheel_open_t, wheel_close_step = wheel_close_step,
folder_open_timeline = folder_open_timeline, folder_close_timeline = folder_close_timeline,
song_open_timeline = song_open_timeline, song_close_timeline = song_close_timeline,
folder_leg = folder_leg,
}