622 lines
29 KiB
Lua
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
|