Files
2026-08-02 16:20:10 -04:00

622 lines
29 KiB
Lua

-- SongSelectState constants (must match enums.h)
local STATE_BROWSING = 0
local STATE_SONG_SELECTED = 1
local STATE_DIFF_SORTING = 2
local STATE_SEARCHING = 3
local STATE_DAN_SELECTED = 4
-- Difficulty constants (must match global_data.h Difficulty enum)
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
-- Crown/Rank ints (must match global_data.h)
local CROWN_NONE, CROWN_CLEAR, CROWN_FC, CROWN_DFC = 0, 1, 2, 3
SongSelect = {}
SongSelect.__index = SongSelect
function SongSelect.new()
local self = setmetatable({}, SongSelect)
tex.load_animations("song_select")
tex.load_folder("song_select", "global")
-- script_manager.tex (this Lua-facing wrapper) is a separate TextureWrapper
-- instance from the global `tex` C++ box-drawing code uses — Screen::on_screen_start()
-- only preloads the latter, so every subset this script draws from needs an
-- explicit load here too, not just "global".
tex.load_folder("song_select", "box")
tex.load_folder("song_select", "yellow_box")
tex.load_folder("song_select", "diff_select")
-- Animations (IDs from song_select/animation.json)
self.text_fade_in = tex.get_animation(4) -- fade in/out the top-left label text
self.text_fade_in:start()
-- Cached texture IDs: global
self._id_footer = tex.get_id("global", "footer") or 0
self._id_arrow = tex.get_id("global", "arrow") or 0
self._id_song_num_bg = tex.get_id("global", "song_num_bg") or 0
self._id_song_select = tex.get_id("global", "song_select") or 0
self._id_difficulty_select = tex.get_id("global", "difficulty_select") or 0
-- Cached texture IDs: box (ribbon/backdrop chrome, ported from
-- TJAPlayer3-Develop-ReWrite's Bar_Genre/GenreBackground assets)
self._id_bar_genre = tex.get_id("box", "bar_genre")
self._id_bar_genre_back = tex.get_id("box", "bar_genre_back")
self._id_bar_genre_overlay = tex.get_id("box", "bar_genre_overlay")
self._id_background = tex.get_id("box", "background")
self._id_background_static = tex.get_id("box", "background_static")
self._id_box_chara = tex.get_id("box", "box_chara")
self._id_back_graphic = tex.get_id("box", "back_graphic")
-- Cached texture IDs: yellow_box (crown/score badges reused as-is, plus the new
-- Frame_Score tower + Level_Number digits)
self._id_crown_dfc = tex.get_id("yellow_box", "crown_dfc")
self._id_crown_fc = tex.get_id("yellow_box", "crown_fc")
self._id_crown_clear = tex.get_id("yellow_box", "crown_clear")
self._id_frame_score = tex.get_id("yellow_box", "frame_score")
self._id_level_number = tex.get_id("yellow_box", "level_number")
self._id_score_rank = tex.get_id("yellow_box", "score_rank")
self._id_crown_badge = tex.get_id("yellow_box", "crown_badge")
self._id_branch_indicator = tex.get_id("yellow_box", "branch_indicator") -- browsing "open" state
self._id_branch_diff = tex.get_id("yellow_box", "branch_indicator_diff") -- diff-select ribbon
self._id_branch_ura = tex.get_id("yellow_box", "branch_indicator_ura") -- diff-select ribbon
-- Cached texture IDs: diff_select
self._id_difficulty_back = tex.get_id("diff_select", "difficulty_back")
self._id_difficulty_crown = tex.get_id("diff_select", "difficulty_crown")
self._id_difficulty_star = tex.get_id("diff_select", "difficulty_star")
self._id_difficulty_bar = tex.get_id("diff_select", "difficulty_bar")
self._id_difficulty_select_bar = tex.get_id("diff_select", "difficulty_select_bar")
self._id_difficulty_select_bar2 = tex.get_id("diff_select", "difficulty_select_bar2")
self._id_ura_oni_plate = tex.get_id("diff_select", "ura_oni_plate")
-- Native pixel sizes (needed for x2/y2 additive-delta math and manual centering —
-- tex.draw_texture's .x/.y is a top-left corner, not a center; see box comments
-- throughout for the reasoning, ported verbatim from the original C++ draw code).
self._frame_score_info = tex.get_texture_info("yellow_box", "frame_score")
self._level_number_info = tex.get_texture_info("yellow_box", "level_number")
self._crown_badge_info = tex.get_texture_info("yellow_box", "crown_badge")
self._score_rank_info = tex.get_texture_info("yellow_box", "score_rank")
self._difficulty_crown_info = tex.get_texture_info("diff_select", "difficulty_crown")
self._difficulty_bar_info = tex.get_texture_info("diff_select", "difficulty_bar")
self._sel_bar_info = tex.get_texture_info("diff_select", "difficulty_select_bar")
self._sel_bar2_info = tex.get_texture_info("diff_select", "difficulty_select_bar2")
self._difficulty_back_info = tex.get_texture_info("diff_select", "difficulty_back")
self._ura_oni_plate_info = tex.get_texture_info("diff_select", "ura_oni_plate")
self._box_chara_info = tex.get_texture_info("box", "box_chara")
return self
end
function SongSelect:update(current_ms)
self.text_fade_in:update(current_ms)
end
-- Call whenever the screen state changes so the label cross-fades correctly.
function SongSelect:restart_text_fade()
self.text_fade_in:start()
end
function SongSelect:draw_footer()
tex.draw_texture(self._id_footer)
end
-- Draw the navigation arrows and the top-left state label.
-- state: one of the STATE_* constants above (passed from C++ as an int).
function SongSelect:draw_overlays(state)
-- Top-left label: cross-fade between "SONG SELECT" and "DIFFICULTY SELECT".
local label_fade = self.text_fade_in.attribute
if state == STATE_SONG_SELECTED then
tex.draw_texture(self._id_difficulty_select, { fade = label_fade })
tex.draw_texture(self._id_song_select, { fade = 1 - label_fade })
else
tex.draw_texture(self._id_difficulty_select, { fade = 1 - label_fade })
tex.draw_texture(self._id_song_select, { fade = label_fade })
end
end
--------------------------------------------------------------------------------
-- Scrolling genre backdrop (ref: Background.png static frame + GenreBackground_N
-- scrolling layer). Called from Navigator::draw_background(); nav exposes just the
-- scroll/cross-fade animation state and the pre-mapped ref frame indices.
--------------------------------------------------------------------------------
function SongSelect:draw_background(nav)
tex.draw_texture(self._id_background_static)
local info = tex.get_texture_info("box", "background")
local width = info.width
local move = nav:background_move().attribute
local fade_change = nav:background_fade_change().attribute
local last_frame = nav:last_bg_genre_frame()
local curr_frame = nav:bg_genre_frame()
for i = 0, width * 4 - 1, width do
tex.draw_texture(self._id_background, { frame = last_frame, x = i - move })
tex.draw_texture(self._id_background, { frame = curr_frame, x = i - move, fade = 1 - fade_change })
end
end
--------------------------------------------------------------------------------
-- Shared flat-bar chrome (ref: Bar_Genre_N.png / Bar_Genre_Overlay.png / Bar_Genre_Back.png)
--------------------------------------------------------------------------------
-- Closed row: flat bar at native 632x92, centered on (box_x, box_y).
function SongSelect:draw_chrome_closed(bar_id, box)
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local half_w, half_h = 316.0 * s, 46.0 * s
tex.draw_texture(bar_id, { frame = box:genre_frame(), x = bx - half_w, y = by - half_h, fade = box.fade.attribute })
tex.draw_texture(self._id_bar_genre_overlay, { x = bx - half_w, y = by - half_h, fade = box.fade.attribute })
end
-- Closed-row title, centered on the flat bar at native size (song/folder both use this
-- via box:name(), the lazily-cached horizontal/fore_color title — see
-- BaseBox::horizontal_name() in box_base.h).
function SongSelect:draw_closed_text(box)
local bx, by = box:box_x(), box:box_y()
local name_cfg = tex.get_skin_config("song_box_name")
local title = box:name()
title:draw({ x = bx + name_cfg.x - title.width / 2, y = by + name_cfg.y, fade = box.fade.attribute })
end
-- Selected/open row: 3-slice bounce-pop bar (top/bottom slices spring apart, middle
-- stretches) driven by box:bar_anime_count() — ref CActSelect曲リスト.cs ~1315-1335.
function SongSelect:draw_chrome_selected(bar_id, box, fade)
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local frame = box:genre_frame()
local bar_anim = box:bar_anime_count()
local x = bx - 316.0 * s -- native width 632, left edge of a bar centered on bx
local top_center = by - (34.0 + bar_anim) * s
local top_h = 21.0 * s
local mid_extra = (bar_anim == 0.0) and 0.0 or (bar_anim / 23.6) * 48.0
local mid_h = (48.0 + mid_extra) * s
local bottom_center = by + (34.0 + bar_anim) * s
local bottom_h = 23.0 * s
tex.draw_texture(bar_id, { frame = frame, x = x, y = top_center - top_h / 2, y2 = top_h - 92.0 * s, fade = fade, src = { x = 0, y = 0, width = 632, height = 21 } })
tex.draw_texture(bar_id, { frame = frame, x = x, y = by - mid_h / 2, y2 = mid_h - 92.0 * s, fade = fade, src = { x = 0, y = 21, width = 632, height = 48 } })
tex.draw_texture(bar_id, { frame = frame, x = x, y = bottom_center - bottom_h / 2, y2 = bottom_h - 92.0 * s, fade = fade, src = { x = 0, y = 69, width = 632, height = 23 } })
if bar_anim > 0.0 then
tex.draw_texture(self._id_bar_genre_overlay, { x = x, y = top_center - top_h / 2, y2 = top_h - 92.0 * s, fade = fade, src = { x = 0, y = 0, width = 632, height = 21 } })
tex.draw_texture(self._id_bar_genre_overlay, { x = x, y = by - mid_h / 2, y2 = mid_h - 92.0 * s, fade = fade, src = { x = 0, y = 21, width = 632, height = 48 } })
tex.draw_texture(self._id_bar_genre_overlay, { x = x, y = bottom_center - bottom_h / 2, y2 = bottom_h - 92.0 * s, fade = fade, src = { x = 0, y = 69, width = 632, height = 23 } })
end
end
-- Title/subtitle: rises from the closed/center position as the box opens (browsing),
-- or sits large and fixed above the diff-select ribbon (diff_select_mode). OutlinedText:
-- draw() has no .scale support (only tex.draw_texture does — see text.cpp), so
-- diff-select's bigger size comes from a separately-constructed, bigger-font cached
-- instance (box:subtitle_large()/box:name_large()) rather than a runtime scale.
function SongSelect:draw_song_text(box, diff_select_mode)
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local open_progress = math.min(1.0, box.open_anim.attribute / (150.0 * s))
local title_extra_up, subtitle_extra_up
if diff_select_mode then
title_extra_up, subtitle_extra_up = -190.0 * s, -165.0 * s
else
title_extra_up, subtitle_extra_up = -open_progress * 50.0 * s, -open_progress * 35.0 * s
end
local name_cfg = tex.get_skin_config("song_box_name")
local sub_cfg = tex.get_skin_config("yb_subtitle")
local text_fade = diff_select_mode and box.diff_fade_in.attribute or box.open_fade.attribute
local sub = diff_select_mode and box:subtitle_large() or box:subtitle()
local sub_h2 = diff_select_mode and 0.0 or (math.min(sub.height, sub_cfg.height) - sub.height)
sub:draw({
x = bx + sub_cfg.x - sub.width / 2, y = by + sub_cfg.y + subtitle_extra_up,
y2 = sub_h2, fade = text_fade
})
local title = diff_select_mode and box:name_large() or box:name()
local title_h2 = diff_select_mode and 0.0 or (math.min(title.height, name_cfg.height) - title.height)
title:draw({
x = bx + name_cfg.x - title.width / 2, y = by + name_cfg.y + title_extra_up,
y2 = title_h2, fade = text_fade
})
end
--------------------------------------------------------------------------------
-- SongBox
--------------------------------------------------------------------------------
function SongSelect:draw_song_closed(box)
self:draw_chrome_closed(self._id_bar_genre, box)
self:draw_closed_text(box)
-- Crown badge for the highest scored+charted difficulty. Centered near the bar's
-- right edge (56x56 icon) instead of the stale (20,64) offset from the old tall
-- narrow panel.
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local highest, highest_crown = -1, CROWN_NONE
for diff = 0, DIFF_URA do
local info = box:course_info(diff)
if info.has_course and info.crown ~= CROWN_NONE then
highest, highest_crown = diff, info.crown
end
end
if highest >= 0 then
local frame = math.min(DIFF_URA, highest)
local crown_x, crown_y = bx + 260.0 * s - 28.0 * s, by - 28.0 * s
local crown_id
if highest_crown == CROWN_DFC then crown_id = self._id_crown_dfc
elseif highest_crown == CROWN_FC then crown_id = self._id_crown_fc
else crown_id = self._id_crown_clear end
tex.draw_texture(crown_id, { frame = frame, x = crown_x, y = crown_y, fade = box.fade.attribute })
end
end
-- Backdrop drawn before the cursor highlight/ribbon on the diff-select screen (see
-- Navigator::draw_diff_select_bg()) — ref's genre-colored Difficulty_Back_N.png.
function SongSelect:draw_song_diff_select_bg(box)
local bx, by = box:box_x(), box:box_y()
local info = self._difficulty_back_info
tex.draw_texture(self._id_difficulty_back, {
frame = box:genre_frame(), x = bx - info.width / 2, y = by - info.height / 2,
fade = box.diff_fade_in.attribute
})
end
-- ref's Difficulty_Bar.png is one continuous ribbon: a closed/option cap (171px)
-- joined to 4 adjacent difficulty sections (143px each, the 4th swapping between two
-- crop regions depending on the Oni/Ura toggle) — not 4 separate towers with gaps
-- (CActSelect難易度選択画面.cs ~287-365).
function SongSelect:draw_song_diff_select(box)
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local fade = box.diff_fade_in.attribute
local closed_w, section_w, bar_h = 171.0, 143.0, 236.0
local total_w = closed_w + section_w * 4.0
local left_edge = bx - (total_w * s) / 2.0
local bar_top = by - (bar_h * s) / 2.0 + 40.0 * s
local bar_info = self._difficulty_bar_info
tex.draw_texture(self._id_difficulty_bar, {
x = left_edge, y = bar_top,
x2 = (closed_w * s) - bar_info.width, y2 = (bar_h * s) - bar_info.height,
fade = fade, src = { x = 0, y = 0, width = closed_w, height = bar_h }
})
local is_ura = box.is_ura
for i = 0, 3 do
local section_x = left_edge + (closed_w + section_w * i) * s
local section_cx = section_x + (section_w * s) / 2.0
local is_ura_slot = (i == DIFF_ONI and is_ura)
local diff = is_ura_slot and DIFF_URA or i
local info = box:course_info(diff)
local section_fade = info.has_course and fade or math.min(fade, 0.4)
local crop_x = (i == DIFF_ONI) and (is_ura and 743.0 or 600.0) or (closed_w + section_w * i)
local crop_w = (i == DIFF_ONI and is_ura) and 137.0 or section_w
tex.draw_texture(self._id_difficulty_bar, {
x = section_x, y = bar_top,
x2 = (section_w * s) - bar_info.width, y2 = (bar_h * s) - bar_info.height,
fade = section_fade, src = { x = crop_x, y = 0, width = crop_w, height = bar_h }
})
if i == DIFF_ONI and is_ura then
local plate = self._ura_oni_plate_info
tex.draw_texture(self._id_ura_oni_plate, {
x = section_cx - plate.width / 2, y = by - plate.height / 2, fade = fade
})
end
-- Crown badge near the top of this section.
local crown_info = self._difficulty_crown_info
local crown_w, crown_h = 24.5 * s, 26.0 * s
tex.draw_texture(self._id_difficulty_crown, {
x = section_cx - crown_w / 2, y = bar_top + 14.0 * s,
x2 = crown_w - crown_info.width, y2 = crown_h - crown_info.height,
fade = (info.has_course and info.crown ~= CROWN_NONE) and fade or math.min(fade, 0.25),
src = { x = 24.5 * info.crown, y = 0, width = 24.5, height = 26.0 }
})
-- Star rating row underneath the ribbon, left-aligned within the section.
if info.has_course then
local star_y = bar_top + (bar_h - 47.0) * s
local star_x0 = section_x + 17.0 * s
for j = 0, info.level - 1 do
tex.draw_texture(self._id_difficulty_star, { x = star_x0 + j * 10.0 * s, y = star_y, fade = fade })
end
if info.is_branching and (math.floor(tex.get_current_ms() / 1000) % 2 == 0) then
local branch_id = (diff == DIFF_URA) and self._id_branch_ura or self._id_branch_diff
local binfo = tex.get_texture_info("yellow_box", (diff == DIFF_URA) and "branch_indicator_ura" or "branch_indicator_diff")
tex.draw_texture(branch_id, { x = section_cx - binfo.width / 2, y = star_y - binfo.height / 2, fade = fade })
end
end
end
self:draw_song_text(box, true)
end
function SongSelect:draw_song_open(box)
self:draw_chrome_selected(self._id_bar_genre, box, math.min(1.0, box.fade.attribute))
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
local offset = tex.get_skin_config("yb_diff_offset").x
local col0 = bx - offset * 1.5
-- Staggered reveal: foreground content only shows once open_anim has progressed
-- far enough, matching FolderBox's own reveal gate (shared open_anim animation).
if box.open_anim.attribute < (100.0 * s) then return end
local has_ura = box:has_ura()
-- Continuous 6s sine cross-fade between Oni and Ura.
local ura_weight = 0.0
if has_ura then
local t = tex.get_current_ms() / 1000.0
t = t - math.floor(t / 4.0) * 4.0
ura_weight = (math.sin(t * (2.0 * math.pi / 4.0)) + 1.0) / 2.0
end
local bar_anim = box:bar_anime_count()
local badge_scale = 0.8 + bar_anim / 620.0
local badge_x = bx - 286.0 * s
local function draw_badge(diff, weight)
if weight <= 0.0 then return end
local info = box:course_info(diff)
if not info.has_course or info.crown == CROWN_NONE then return end
local badge_fade = weight * box.open_fade.attribute
local cb = self._crown_badge_info
local crown_w, crown_h = 43.2 * s * badge_scale, 39.0 * s * badge_scale
local crown_frame = diff * 3 + (info.crown - 1)
tex.draw_texture(self._id_crown_badge, {
x = badge_x - crown_w / 2, y = by - 16.0 * s - bar_anim / 1.1 * s - crown_h / 2,
x2 = crown_w - cb.width, y2 = crown_h - cb.height, fade = badge_fade,
src = { x = 43.2 * crown_frame, y = 0, width = 43.2, height = 39.0 }
})
if info.rank ~= 0 then
local sr = self._score_rank_info
local rank_w, rank_h = 50.0 * s * badge_scale, 42.71 * s * badge_scale
tex.draw_texture(self._id_score_rank, {
x = badge_x - rank_w / 2, y = by + 14.0 * s - bar_anim / 1.15 * s - rank_h / 2,
x2 = rank_w - sr.width, y2 = rank_h - sr.height, fade = badge_fade,
src = { x = 0, y = 42.71 * (info.rank - 1), width = 50.0, height = 42.71 }
})
end
end
if has_ura then
draw_badge(DIFF_ONI, 1.0 - ura_weight)
draw_badge(DIFF_URA, ura_weight)
else
draw_badge(DIFF_ONI, 1.0)
end
-- EX-data / new-song balloon (tex.get_id already falls back to a "_<language>"
-- suffixed key, matching the classic C++ lookup).
local ex_flag = box:ex_data_flag()
if ex_flag == 1 then tex.draw_texture(tex.get_id("yellow_box", "ex_data_new_audio"), { x = bx, y = by, fade = box.open_fade.attribute })
elseif ex_flag == 2 then tex.draw_texture(tex.get_id("yellow_box", "ex_data_old_audio"), { x = bx, y = by, fade = box.open_fade.attribute })
elseif ex_flag == 3 then tex.draw_texture(tex.get_id("yellow_box", "ex_data_limited_time"), { x = bx, y = by, fade = box.open_fade.attribute })
elseif ex_flag == 4 then tex.draw_texture(tex.get_id("yellow_box", "ex_data_new_song"), { x = bx, y = by, fade = box.open_fade.attribute })
end
if tex.display_bpm() then
local bpm_cfg = tex.get_skin_config("song_box_bpm")
box:bpm_text():draw({ x = bx + bpm_cfg.x, y = by + bpm_cfg.y, fade = box.open_fade.attribute })
end
if box.is_favorite then
tex.draw_texture(tex.get_id("yellow_box", "favorite_" .. tex.player_num() .. "p"), { x = bx, y = by, fade = box.open_fade.attribute })
end
-- Frame_Score tower + level-number digits, one per difficulty column. Ura doesn't
-- get its own column — it shares the Oni slot, cross-fading in/out.
local fs, ln = self._frame_score_info, self._level_number_info
local frame_w = fs.width / 5.0 -- native 122px/column
local dest_w, dest_h = frame_w * s, 65.0 * s
local dx2, dy2 = dest_w - fs.width, dest_h - fs.height
local frame_y = by + 40.0 * s
local digit_w, digit_h = 21.0 * s, 25.0 * s
local digit_dx2 = digit_w - (ln.width - 2)
local digit_dy2 = digit_h - ln.height
local digit_y = frame_y - digit_h / 2.0
local function draw_level(diff, lvl_fade)
local info = box:course_info(diff)
if not info.has_course then return end
local lvl_str = string.format("%d", info.level)
local digit_advance = 16.0 * s
local lx = col0 + (diff == DIFF_URA and DIFF_ONI or diff) * offset - (digit_advance * #lvl_str) / 2.0 + 40.0 * s
for k = 1, #lvl_str do
local d = tonumber(lvl_str:sub(k, k))
tex.draw_texture(self._id_level_number, {
x = lx, y = digit_y + 2, x2 = digit_dx2, y2 = digit_dy2, fade = lvl_fade,
src = { x = 21.0 * d, y = 0, width = 21.0, height = 25.0 }
})
lx = lx + digit_advance
end
end
for i = 0, DIFF_ONI do
local cx = col0 + i * offset
local is_oni_slot = (i == DIFF_ONI)
local info = box:course_info(i)
local tile_fade = box.open_fade.attribute
if is_oni_slot and has_ura then
tile_fade = tile_fade * math.max(1.0 - ura_weight, ura_weight)
end
tex.draw_texture(self._id_frame_score, {
x = cx - dest_w / 2, y = frame_y - dest_h / 2,
x2 = dx2, y2 = dy2, fade = tile_fade,
src = { x = frame_w * i, y = 150.0, width = frame_w, height = 65.0 }
})
local branch_diff = i
if is_oni_slot and has_ura then
draw_level(DIFF_ONI, box.open_fade.attribute * (1.0 - ura_weight))
draw_level(DIFF_URA, box.open_fade.attribute * ura_weight)
branch_diff = (ura_weight > 0.5) and DIFF_URA or DIFF_ONI
elseif info.has_course then
draw_level(i, box.open_fade.attribute)
end
local branch_info = box:course_info(branch_diff)
if branch_info.has_course and branch_info.is_branching and (math.floor(tex.get_current_ms() / 1000) % 2 == 0) then
tex.draw_texture(self._id_branch_indicator, { x = cx, y = by, fade = box.open_fade.attribute })
end
end
self:draw_song_text(box, false)
end
--------------------------------------------------------------------------------
-- FolderBox
--------------------------------------------------------------------------------
function SongSelect:draw_folder_closed(box)
self:draw_chrome_closed(self._id_bar_genre, box)
self:draw_closed_text(box)
end
function SongSelect:draw_folder_open(box)
self:draw_chrome_selected(self._id_bar_genre, box, box.fade.attribute)
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
-- Same rise-as-it-opens behavior as SongBox's title (draw_song_text's browsing
-- branch), just without a subtitle — folders have no text_subtitle equivalent.
local open_progress = math.min(1.0, box.open_anim.attribute / (150.0 * s))
local title_extra_up = -open_progress * 50.0 * s
local name_cfg = tex.get_skin_config("song_box_name")
local title = box:name()
title:draw({
x = bx + name_cfg.x - title.width / 2, y = by + name_cfg.y + title_extra_up,
fade = box.open_fade.attribute
})
if box:has_box_texture() then
box:draw_box_texture({ x = bx, y = by, fade = box.open_fade.attribute })
return
end
-- ref: Box_Chara_N.png, split in half and slid together as the folder opens
-- (CActSelect曲リスト.cs ~1572-1583). "anime" is a triangular 0->150->~114 ramp
-- keyed off the same bounce-pop bar_anime_count() used by draw_chrome_selected.
local frame = box:genre_frame()
local bar_anim = box:bar_anime_count()
local anime = (bar_anim <= 45.0) and (bar_anim * 3.333333333) or (150.0 - (bar_anim - 45.0) * 2.11764705)
local info = self._box_chara_info
local half_w = (info.width / 2.0) * s
local dest_x2 = half_w - info.width
local left_center_x = bx - (info.width / 4.0) * s + (114.0 - anime) * s
local right_center_x = bx + (info.width / 4.0) * s - (114.0 - anime) * s
tex.draw_texture(self._id_box_chara, { frame = frame, x = left_center_x - half_w / 2, y = by - (info.height / 2.0) * s, x2 = dest_x2, fade = box.open_fade.attribute, src = { x = 0, y = 0, width = info.width / 2.0, height = info.height } })
tex.draw_texture(self._id_box_chara, { frame = frame, x = right_center_x - half_w / 2, y = by - (info.height / 2.0) * s, x2 = dest_x2, fade = box.open_fade.attribute, src = { x = info.width / 2.0, y = 0, width = info.width / 2.0, height = info.height } })
end
--------------------------------------------------------------------------------
-- BackBox
--------------------------------------------------------------------------------
function SongSelect:draw_back_closed(box)
self:draw_chrome_closed(self._id_bar_genre_back, box)
end
function SongSelect:draw_back_open(box)
self:draw_chrome_selected(self._id_bar_genre_back, box, math.min(1.0, box.fade.attribute))
local bx, by = box:box_x(), box:box_y()
local s = tex.get_screen_scale()
tex.draw_texture(self._id_back_graphic, { scale = 0.3 * s, center = true, x = bx - 180.0 * s, y = by, fade = math.min(box.fade.attribute, box.open_fade.attribute) })
end
--------------------------------------------------------------------------------
-- Dispatch
--------------------------------------------------------------------------------
function SongSelect:draw_box(box)
local kind, state = box:lua_kind(), box:draw_state()
if kind == "song" then
if state == "diff_select" then self:draw_song_diff_select(box)
elseif state == "open" then self:draw_song_open(box)
else self:draw_song_closed(box) end
elseif kind == "folder" then
if state == "open" then self:draw_folder_open(box)
else self:draw_folder_closed(box) end
elseif kind == "back" then
if state == "open" then self:draw_back_open(box)
else self:draw_back_closed(box) end
end
end
function SongSelect:draw_box_bg(box)
if box:lua_kind() ~= "song" then return end
self:draw_song_diff_select_bg(box)
end
--------------------------------------------------------------------------------
-- Diff-select cursor highlight (ref: Difficulty_Select_Bar[player], highlighted over
-- the current ribbon section — CActSelect難易度選択画面.cs ~301-304). Geometry mirrors
-- draw_song_diff_select's Difficulty_Bar ribbon exactly (same closed_w/section_w/
-- bar_top math) so the cursor lines up with the section it's highlighting. NEIRO has
-- no ref equivalent so it gets no highlight; BACK uses the ribbon's closed cap,
-- MODIFIER approximates ref's OPTION slot just to the right of it.
--------------------------------------------------------------------------------
function SongSelect:draw_selector(player, is_half, fade_in)
local diff = player:selected_difficulty()
if diff == DIFF_NEIRO then return end
local fade = (player:neiro_active() or player:modifier_active()) and 0.5 or fade_in
local s = tex.get_screen_scale()
local bx, by = 640.0 * s, 360.0 * s
local closed_w, section_w, bar_h = 171.0, 143.0, 236.0
local total_w = closed_w + section_w * 4.0
local left_edge = bx - (total_w * s) / 2.0
local bar_top = by - (bar_h * s) / 2.0 + 40.0 * s
local bar_center_y = bar_top + (bar_h * s) / 2.0
local section_cx, section_cy = 0.0, bar_center_y
local short_crop
local cap_center = left_edge + (closed_w * s) / 2.0
if diff == DIFF_BACK then
section_cx = cap_center - 48.0 * s
section_cy = bar_center_y - 60.0 * s
short_crop = true
elseif diff == DIFF_MODIFIER then
section_cx = cap_center + 40.0 * s
section_cy = bar_center_y - 60.0 * s
short_crop = true
else
local col = math.min(DIFF_ONI, diff)
local section_x = left_edge + (closed_w + section_w * col) * s
section_cx = section_x + (section_w * s) / 2.0
short_crop = false
end
local crop_w = 259.0
local crop_y = short_crop and 387.0 or 114.0
local crop_h = short_crop and 111.0 or 275.0
local dest_w, dest_h = crop_w * s, crop_h * s
local bar_info = (player:player_num() == 1) and self._sel_bar_info or self._sel_bar2_info
local bar_id = (player:player_num() == 1) and self._id_difficulty_select_bar or self._id_difficulty_select_bar2
tex.draw_texture(bar_id, {
x = section_cx - dest_w / 2, y = section_cy - dest_h / 2,
x2 = dest_w - bar_info.width, y2 = dest_h - bar_info.height,
fade = fade, src = { x = 0, y = crop_y, width = crop_w, height = crop_h }
})
end