Files
PyTaikoGreen/Scripts/result/dan_result_draw.lua
T
2026-09-22 10:46:07 -04:00

447 lines
18 KiB
Lua

local FRAME_MS = 1000.0 / 60.0
local DIGIT_ROLL_MS = 500.0
local GAUGE_UNIT_MS = 3 * (1000.0 / 120.0)
local GAUGE_NUMIN_MS = 10 * FRAME_MS + 500.0
local SLIDE_MS = 22 * FRAME_MS
local SLIDE_DIST = 1600.0
local SLIDE_BREAK_AT = 5.0 / 22.0
local SLIDE_BREAK_X = 35.0
local function slide_offset(t)
if t <= 0 then return SLIDE_DIST end
if t >= SLIDE_MS then return 0.0 end
local p = t / SLIDE_MS
if p <= SLIDE_BREAK_AT then
return SLIDE_DIST + (SLIDE_BREAK_X - SLIDE_DIST) * (p / SLIDE_BREAK_AT)
end
return SLIDE_BREAK_X * (1.0 - (p - SLIDE_BREAK_AT) / (1.0 - SLIDE_BREAK_AT))
end
local function pen_count(subset, name)
local info = tex.get_texture_info(subset, name)
return info and #info.x or 1
end
DanResultDraw = {}
DanResultDraw.__index = DanResultDraw
function DanResultDraw.new(rd, prev_arrival, prev_best_score, best_score_show,
gauge_exam, gauge_value, gauge_border)
local self = setmetatable({}, DanResultDraw)
self.rd = rd
self.prev_arrival = prev_arrival
self.gauge_exam = gauge_exam
self.gauge_value = gauge_value
self.gauge_border = gauge_border
local function T(path) return tex.get_texture("dan_result/" .. path) end
self.t_genre_banner = T("background/genre_banner")
self.t_result_2_bg = T("background/result_2_bg")
self.t_result_2_divider = T("background/result_2_divider")
self.t_result_2_pullout = T("background/result_2_pullout")
self.t_song_num = T("result_info/song_num")
self.t_difficulty = T("result_info/difficulty")
self.t_diff_star = T("result_info/diff_star")
self.t_diff_x = T("result_info/diff_x")
self.t_diff_num = T("result_info/diff_num")
self.t_good = T("result_info/good")
self.t_ok = T("result_info/ok")
self.t_bad = T("result_info/bad")
self.t_drumroll = T("result_info/drumroll")
self.t_counter = T("result_info/counter")
self.t_max_combo = T("result_info/max_combo")
self.t_max_hits = T("result_info/max_hits")
self.t_score_box = T("result_info/score_box")
self.t_score_counter = T("result_info/score_counter")
self.t_exam_header = T("result_info/exam_header")
self.t_dan_emblem = T("result_info/dan_emblem")
self.t_exam_bg = T("exam_info/exam_bg")
self.t_exam_overlay_1 = T("exam_info/exam_overlay_1")
self.t_exam_overlay_2 = T("exam_info/exam_overlay_2")
self.t_exam_max = T("exam_info/exam_max")
self.t_exam_gold = T("exam_info/exam_gold")
self.t_exam_red = T("exam_info/exam_red")
self.t_value_counter = T("exam_info/value_counter")
self.t_exam_less = T("exam_info/exam_less")
self.t_exam_more = T("exam_info/exam_more")
self.t_exam_percent = T("exam_info/exam_percent")
self.t_exam_fail = T("exam_info/fail")
self.t_gold_clear = T("exam_info/gold_clear")
self.t_red_clear = T("exam_info/red_clear")
self.vpen = pen_count("exam_info", "value_counter") > 1 and 1 or 0
self.ppen = pen_count("exam_info", "exam_percent") > 1 and 1 or 0
self.exam_icons = {
gauge = T("exam_info/exam_gauge"), combo = T("exam_info/exam_combo"),
hit = T("exam_info/exam_hit"), judgebad = T("exam_info/exam_judgebad"),
judgegood = T("exam_info/exam_judgegood"), judgeperfect = T("exam_info/exam_judgeperfect"),
score = T("exam_info/exam_score"), renda = T("exam_info/exam_drumroll"),
}
self.g_unfilled = T("gauge/1p_unfilled")
self.g_bar = T("gauge/1p_bar")
self.g_overlay = T("gauge/overlay")
self.g_rainbow = T("gauge/rainbow")
self.g_footer = T("gauge/footer")
self.g_tamashii = T("gauge/tamashii")
self.g_tamashii_dark = T("gauge/tamashii_dark")
self.g_tamashii_overlay = T("gauge/tamashii_overlay")
self.g_fire = T("gauge/tamashii_fire")
self.gauge_bar_width = 712.0
local r = rd.odai_result
if r > 0 then
self.t_clear_variant = (r >= 4) and self.t_gold_clear or self.t_red_clear
end
local dan_title_cfg = tex.get_skin_config("dan_title")
local font_size = dan_title_cfg.font_size
self.hori_name = text.create_raw_text(rd.dan_title, font_size,
{255,255,255,255}, {0,0,0,255}, false,
dan_title_cfg.outline >= 0 and dan_title_cfg.outline or 5.0)
local sn_cfg = tex.get_skin_config("dan_result_song_name")
local song_font_size = (sn_cfg.font_size and sn_cfg.font_size > 0) and sn_cfg.font_size or font_size
local song_outline = (sn_cfg.outline and sn_cfg.outline >= 0) and sn_cfg.outline or 5.0
self.song_names = {}
for i, song in ipairs(rd.songs) do
local title = song.song_title
if song.hidden and song.unreached and prev_arrival < i then
title = "??????"
end
self.song_names[i] = text.create_raw_text(title, song_font_size,
{255,255,255,255}, {0,0,0,255}, false, song_outline)
end
return self
end
function DanResultDraw:draw_digit_counter(digits, margin_x, id, index, y, fade, scale, x_off, roll_t)
x_off = x_off or 0.0
roll_t = roll_t or -1.0
local n = #digits
for j = 1, n do
local r = n - j
local frame = tonumber(digits:sub(j, j))
if roll_t >= 0.0 then
if roll_t < r * DIGIT_ROLL_MS then goto continue end
if roll_t < (r + 1) * DIGIT_ROLL_MS then
frame = math.floor(roll_t / 83.0 + r) % 10
end
end
local x = x_off - (n - j + 1) * margin_x
tex.draw_texture(id, {frame = frame, scale = scale, x = x, y = y, fade = fade, index = index})
::continue::
end
end
function DanResultDraw:draw_page1(now)
local height = tex.get_skin_config("dan_result_info_height").y
local margin = tex.get_skin_config("score_info_counter_margin").x
for i, song in ipairs(self.rd.songs) do
local y = (i - 1) * height
local land
if i <= 3 then
land = ({40, 65, 90})[i] * FRAME_MS
else
land = 90 * FRAME_MS + (i - 3) * 25 * FRAME_MS
end
if now < land then goto continue end
local sx = slide_offset(now - land)
local rf = song.unreached and 0.45 or 1.0
tex.draw_texture(self.t_genre_banner, {frame = song.genre_index, x = sx, y = y, fade = rf})
local sn_name = self.song_names[i]
if sn_name then
local sn = tex.get_skin_config("dan_result_song_name")
local tw = sn_name.width
local draw_w = (sn.width and sn.width > 0) and math.min(tw, sn.width) or tw
sn_name:draw({x = sn.x + sx, y = y + sn.y, x2 = draw_w - tw})
end
tex.draw_texture(self.t_song_num, {frame = i - 1, x = sx, y = y, fade = rf})
tex.draw_texture(self.t_difficulty, {frame = song.selected_difficulty, x = sx, y = y, fade = rf})
tex.draw_texture(self.t_diff_star, {x = sx, y = y, fade = rf})
tex.draw_texture(self.t_diff_x, {x = sx, y = y, fade = rf})
local lv = tostring(song.diff_level)
local dm = tex.get_skin_config("dan_result_diff_num_margin").x
for j = 1, #lv do
tex.draw_texture(self.t_diff_num, {frame = tonumber(lv:sub(j, j)),
x = sx + (j - 1) * dm, y = y})
end
local function draw_stat(icon, val, idx)
tex.draw_texture(icon, {x = sx, y = y, fade = rf})
if song.unreached then return end
local sv = tostring(val)
local n = #sv
for k = 1, n do
local ch = sv:sub(n - k + 1, n - k + 1)
tex.draw_texture(self.t_counter, {frame = tonumber(ch),
x = sx - (k - 1) * margin, y = y, index = idx})
end
end
draw_stat(self.t_good, song.good, 0)
draw_stat(self.t_ok, song.ok, 1)
draw_stat(self.t_bad, song.bad, 2)
draw_stat(self.t_drumroll, song.drumroll, 3)
::continue::
end
end
function DanResultDraw:draw_gauge_row(y, fade, now)
local on_page = now - self.page_start_ms
local cur = self.gauge_value
local landed = true
if not self.page2_skipped and on_page < self.totals_start + self.gauge_value * GAUGE_UNIT_MS then
cur = math.max(0.0, (on_page - self.totals_start) / GAUGE_UNIT_MS)
if cur > self.gauge_value then cur = self.gauge_value end
landed = false
end
local clear = cur >= self.gauge_border and self.gauge_border > 0
local w = self.gauge_bar_width * (cur / 100.0)
tex.draw_texture(self.g_unfilled, {y = y, fade = fade})
if w > 0 then
tex.draw_texture(self.g_bar, {y = y, x2 = w, fade = fade})
if clear and self.g_rainbow then
local frame = math.floor(tex.get_current_ms() / 50.0) % 8
tex.draw_texture(self.g_rainbow, {frame = frame, y = y, x2 = w, fade = fade})
end
end
tex.draw_texture(self.g_overlay, {y = y, fade = fade})
if self.gauge_border > 0 then
tex.draw_texture(self.g_footer, {y = y, x = self:gauge_border_x(), fade = fade})
end
local fever = self.gauge_value >= 100 and landed
tex.draw_texture((clear and landed) and self.g_tamashii or self.g_tamashii_dark, {y = y, fade = fade})
tex.draw_texture(self.g_tamashii_overlay, {y = y, fade = fade})
if fever and self.g_fire then
local frame = math.floor(tex.get_current_ms() / 50.0) % 8
tex.draw_texture(self.g_fire, {frame = frame, y = y, fade = fade})
end
end
function DanResultDraw:gauge_border_x()
return self.gauge_bar_width * (self.gauge_border / 100.0)
end
-- No exam_rainbow/exam_down80 tiers in this skin: only up_50 / up_80 / max.
function DanResultDraw:draw_exam_fill(state, w, y, fade)
if w <= 0 or state == "empty" then return end
local id = self.t_exam_max
if state == "up_50" then id = self.t_exam_red
elseif state == "up_80" or state == "down_80" then id = self.t_exam_gold
end
tex.draw_texture(id, {x2 = w, y = y, fade = fade})
end
function DanResultDraw:draw_exam_info(fade, now)
local rd = self.rd
local on_page = now - self.page_start_ms
local dei = tex.get_skin_config("dan_exam_info")
local offset_y = dei.y
local margin = dei.x
local border_pitch = margin -- no exam_border_counter in this skin
for i, exam in ipairs(rd.exams) do
local ed = rd.exam_data[i]
if not ed then goto continue end
local y = (i - 1) * offset_y
local row = self.rows[i] or {land = 0, fill0 = 0, filld = 0, numin = 0}
if on_page < row.land then goto continue end
local frac = 1.0
if not self.page2_skipped and row.filld > 0 then
frac = math.max(0.0, math.min(1.0, (on_page - row.fill0) / row.filld))
end
local row_numin = self.page2_skipped or on_page >= row.numin
tex.draw_texture(self.t_exam_bg, {y = y, fade = fade})
local tamashii_row = (i - 1 == self.gauge_exam) and self.g_unfilled ~= nil
if tamashii_row then
self:draw_gauge_row(y, fade, now)
else
tex.draw_texture(self.t_exam_overlay_1, {y = y, fade = fade})
local p = ed.progress
if exam.range == "less" then p = 1.0 - (1.0 - p) * frac else p = p * frac end
local bar_w = dei.width * p
local bs = ed.bar_state
if frac < 1.0 then
local g = math.floor(p * 100.0)
bs = g <= 0 and "empty" or g <= 49 and "up_50" or g <= 99 and "up_80" or "up_100"
end
self:draw_exam_fill(bs, bar_w, y, fade)
end
local icon = self.exam_icons[exam.type]
if icon then tex.draw_texture(icon, {y = y, fade = fade}) end
local red_str = tostring(exam.red)
local csc = tamashii_row and (25.0 / 36.0) or 1.0
local gap1, gap2 = 4.0 * csc, 10.0 * csc
local cap_w = #red_str * border_pitch * csc
if exam.type == "gauge" then cap_w = cap_w + gap1 + 31.0 * csc end
if exam.range == "less" or exam.range == "more" then cap_w = cap_w + gap2 + 70.0 * csc end
local cap_dx, cap_dy = 0.0, 0.0
if tamashii_row then
cap_dx = self:gauge_border_x() - cap_w * 0.5
cap_dy = 52.0
end
for k = 1, #red_str do
tex.draw_texture(self.t_value_counter, {frame = tonumber(red_str:sub(k, k)),
x = cap_dx + (k - 1) * border_pitch * csc,
y = y + cap_dy, fade = fade, index = 0})
end
local after = cap_dx + #red_str * border_pitch * csc
if exam.type == "gauge" then
tex.draw_texture(self.t_exam_percent, {x = after + gap1, y = y + cap_dy, fade = fade, index = 0})
after = after + gap1 + 31.0 * csc
end
after = after + gap2
if exam.range == "less" then
tex.draw_texture(self.t_exam_less, {x = after, y = y + cap_dy, fade = fade})
elseif exam.range == "more" then
tex.draw_texture(self.t_exam_more, {x = after, y = y + cap_dy, fade = fade})
end
if not tamashii_row then
tex.draw_texture(self.t_exam_overlay_2, {y = y, fade = fade})
if row_numin then
local cur_str = tostring(ed.counter_value)
for k = 1, #cur_str do
tex.draw_texture(self.t_value_counter, {frame = tonumber(cur_str:sub(k, k)),
x = (k - 1) * margin, y = y, fade = fade, index = self.vpen})
end
if exam.type == "gauge" then
tex.draw_texture(self.t_exam_percent, {x = #cur_str * margin + 6.0, y = y, fade = fade, index = self.ppen})
end
end
end
::continue::
end
end
function DanResultDraw:draw_page2(fade, now)
local rd = self.rd
local on_page = now - self.page_start_ms
tex.draw_texture(self.t_result_2_bg, {fade = fade})
for i = 0, 4 do
tex.draw_texture(self.t_result_2_divider, {x = i * 240, fade = fade})
end
tex.draw_texture(self.t_result_2_pullout, {fade = fade})
tex.draw_texture(self.t_dan_emblem, {frame = rd.dan_color})
if self.hori_name then
local hn = tex.get_skin_config("dan_result_hori_name")
self.hori_name:draw({
x = hn.x - self.hori_name.width / 2.0,
y = hn.y,
x2 = math.min(self.hori_name.width, hn.width) - self.hori_name.width,
})
end
local margin = tex.get_skin_config("score_info_counter_margin").x
local total_good, total_ok, total_bad, total_dr = 0, 0, 0, 0
for _, s in ipairs(rd.songs) do
total_good = total_good + s.good; total_ok = total_ok + s.ok
total_bad = total_bad + s.bad; total_dr = total_dr + s.drumroll
end
local totals_landed = self.page2_skipped or on_page >= self.totals_end
local roll_t = totals_landed and -1.0 or (on_page - self.totals_start)
if totals_landed or on_page >= self.totals_start then
local function draw_total(icon, total, idx_counter)
tex.draw_texture(icon, {fade = fade, index = 1})
self:draw_digit_counter(tostring(total), margin, self.t_counter, idx_counter, 0.0, fade, 1.0, margin, roll_t)
end
draw_total(self.t_good, total_good, 4)
draw_total(self.t_ok, total_ok, 5)
draw_total(self.t_bad, total_bad, 6)
draw_total(self.t_drumroll, total_dr, 7)
tex.draw_texture(self.t_max_combo, {fade = fade})
self:draw_digit_counter(tostring(rd.max_combo), margin, self.t_counter, 8, 0.0, fade, 1.0, margin, roll_t)
local total_hits = total_good + total_ok + total_bad + total_dr
tex.draw_texture(self.t_max_hits, {fade = fade})
self:draw_digit_counter(tostring(total_hits), margin, self.t_counter, 9, 0.0, fade, 1.0, margin, roll_t)
else
tex.draw_texture(self.t_good, {fade = fade, index = 1})
tex.draw_texture(self.t_ok, {fade = fade, index = 1})
tex.draw_texture(self.t_bad, {fade = fade, index = 1})
tex.draw_texture(self.t_drumroll, {fade = fade, index = 1})
tex.draw_texture(self.t_max_combo, {fade = fade})
tex.draw_texture(self.t_max_hits, {fade = fade})
end
tex.draw_texture(self.t_exam_header, {fade = fade})
tex.draw_texture(self.t_score_box, {fade = fade})
if totals_landed or on_page >= self.totals_start then
self:draw_digit_counter(tostring(rd.score), margin, self.t_score_counter, 0, 0.0, fade, 1.0, 0.0, roll_t)
end
self:draw_exam_info(fade, now)
if on_page < self.stamp_at then return end
if rd.odai_result >= 0 then
local r = rd.odai_result
if r == 0 then
tex.draw_texture(self.t_exam_fail, {fade = fade})
else
tex.draw_texture(self.t_clear_variant, {fade = fade})
end
else
local any_failed = false
for _, ed in ipairs(rd.exam_data) do if ed.failed then any_failed = true break end end
if any_failed then
tex.draw_texture(self.t_exam_fail, {fade = fade})
else
tex.draw_texture(self.t_red_clear, {fade = fade})
end
end
end
function DanResultDraw:draw(s)
local now = s.now
self.page_start_ms = s.page_start_ms
self.page1_start_ms = s.page1_start_ms
self.page2_skipped = s.page2_skipped
self.totals_start = s.totals_start
self.totals_end = s.totals_end
self.rows = s.rows
self.stamp_at = s.stamp_at
self.voice_at = s.voice_at
self:draw_page1(now - self.page1_start_ms)
self:draw_page2(s.page2_fade, now)
tex.draw_rect(0, 0, tex.get_screen_width(), tex.get_screen_height(), 0, 0, 0, math.floor(s.fade_out * 255))
end
function DanResultDraw:chara_pos()
return nil
end
function DanResultDraw:nameplate_pos()
return nil
end