Files
YataiDON14/Scripts/game/dan_game_draw.lua
T
2026-10-15 09:16:53 -04:00

215 lines
8.4 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local SKIN_S = tex.get_screen_scale()
local SKIN = {
dan_game_hori_name = { x = 115.5, y = 433.5, width = 206.25 },
dan_total_notes_margin = { x = 46 * SKIN_S },
dan_game_exam_border_text = { x = 1232 * SKIN_S, y = 409 * SKIN_S, font_size = math.floor(32 * SKIN_S), outline = 4 },
}
local WHITE, BLACK = { 255, 255, 255, 255 }, { 0, 0, 0, 255 }
local JP_THRESHOLD = {
dan_exam_text_more = "%s 以上",
dan_exam_text_less = "%s 未満",
dan_exam_text_gauge = "%s %以上",
dan_exam_text_gauge_less = "%s %未満",
}
local function exam_threshold_text(exam_type, range, num)
local gauge, less = exam_type == "gauge", range == "less"
local key = gauge and (less and "dan_exam_text_gauge_less" or "dan_exam_text_gauge")
or (less and "dan_exam_text_less" or "dan_exam_text_more")
local fmt = tex.skin_text(key)
if fmt == "" then fmt = JP_THRESHOLD[key] end
fmt = fmt:gsub("%%%%", "\1")
return (fmt:gsub("%%s", tostring(num)):gsub("\1", "%%"))
end
local function pad_for(outline, scale)
return math.floor(outline * scale) + 2
end
-- Lumen row tops (minus the first 3-row top) for 1, 2 and 3 exam rows.
local ROW_OFFSETS = { { 91 }, { 39, 135 }, { 0, 99, 198 } }
DanGameDraw = {}
DanGameDraw.__index = DanGameDraw
function DanGameDraw.new(title)
local self = setmetatable({}, DanGameDraw)
local function T(name) return tex.get_texture("game/dan_info/" .. name) end
-- Labels, thresholds and live values are engine text; only the plates, bars and digits are art.
self.t = {}
for _, n in ipairs({ "exam_bg", "exam_overlay_1", "exam_overlay_2", "exam_fail", "exam_failed",
"total_notes_counter", "frame", "exam_flash" }) do
self.t[n] = T(n)
end
self.bars = { exam_red = T("exam_red"), exam_gold = T("exam_gold"), exam_max = T("exam_max") }
self.captions = {}
self.live = {}
self.fx = {}
local ds = tex.get_skin_config("dan_select_title")
self.hori_name = text.create_raw_text(title, ds and ds.font_size or 40, WHITE, BLACK, false)
return self
end
function DanGameDraw:digit_counter(digits, margin_x, t, index, y, x_offset)
x_offset = x_offset or 0
local n = #digits
for j = 1, n do
tex.draw_texture(t, { frame = digits:byte(j) - 48, x = -(n - j + 1) * margin_x + x_offset,
y = y, index = index })
end
end
function DanGameDraw:caption(s, size, outline)
local key = s .. "\31" .. size .. "\31" .. math.floor(outline * 100)
local c = self.captions[key]
if not c then
c = text.create_raw_text(s, size, WHITE, BLACK, false, outline)
self.captions[key] = c
end
return c
end
-- Engine-drawn threshold phrase ("100 以上"), right-aligned at dan_game_exam_border_text.
local UNIT_CHAR = { hit = "打", renda = "打", score = "点" }
local LIVE_UNIT = { gauge = "%", combo = "コンボ", hit = "打", renda = "打", score = "点" }
-- Live value as engine text number right-aligned at dan_game_exam_value_text.x, unit mark after it.
-- Re-rendered only when the string changes (one cache slot per row part).
function DanGameDraw:live_caption(slot, str, size, outline)
local c = self.live[slot]
if not c or c.str ~= str then
-- first sight is not a change: only later updates pop
c = { str = str, cap = text.create_raw_text(str, size, WHITE, BLACK, false, outline),
changed = c and tex.get_current_ms() or nil }
self.live[slot] = c
end
return c.cap, c.changed
end
-- Lumen digit pop: 1.0 -> 1.15 in 33 ms, back to 1.0 in 100 ms.
local function pop_scale(changed)
if not changed then return 1.0 end
local t = tex.get_current_ms() - changed
if t < 33 then return 1.0 + 0.15 * t / 33 end
if t < 133 then return 1.15 - 0.15 * (t - 33) / 100 end
return 1.0
end
function DanGameDraw:draw_live_text(info, y, slot)
local s = tex.get_screen_scale()
local vt = { x = 490 * s, y = 384 * s }
local ol = 4.0
local size = math.floor(32 * s)
local pad = pad_for(ol, tex.get_screen_scale())
local num, changed = self:live_caption(slot, tostring(info.counter_value), size, ol)
num:draw({ x = vt.x - num.width + pad, y = vt.y - pad + y, scale = pop_scale(changed), center = true })
local unit = LIVE_UNIT[info.exam_type]
if unit then
local u = self:live_caption(slot + 10, unit, size, ol)
u:draw({ x = vt.x + 4 - pad, y = vt.y - pad + y })
end
end
function DanGameDraw:border_caption(info)
local bt = SKIN.dan_game_exam_border_text
local ol = bt.outline >= 0 and bt.outline or 4.0 / tex.get_screen_scale()
local unit = UNIT_CHAR[info.exam_type] or ""
local cap = self:caption(exam_threshold_text(info.exam_type, info.exam_range, info.red_value .. unit),
bt.font_size > 0 and bt.font_size or 36, ol)
local pad = pad_for(ol, tex.get_screen_scale())
return cap, bt.x - cap.width + pad, bt.y - pad
end
-- Exam type label ("コンボ数") right-aligned just left of the threshold phrase.
function DanGameDraw:draw_label(info, x, y)
local str = tex.skin_text("dan_exam_label_" .. info.exam_type)
if str == "" then return end
local s = tex.get_screen_scale()
local lc = { x = 8 * s, y = 409 * s }
local ol = 4.0
local cap = self:caption(str, math.floor(32 * s), ol)
local pad = pad_for(ol, tex.get_screen_scale())
cap:draw({ x = x - lc.x - cap.width + pad, y = lc.y - pad + y })
end
-- White strip over the bar. On a colour change (red -> gold -> max): alpha 0 -> 0.98 in 83 ms, out over
-- 883 ms. While cleared (max) a slow glow loops: 0 -> 0.35 in 433 ms, out over 450 ms, every 1000 ms.
function DanGameDraw:bar_flash(info, y, slot)
local fx = self.fx[slot]
local now = tex.get_current_ms()
if not fx then
fx = { bar = info.bar_texture, start = nil }
self.fx[slot] = fx
elseif fx.bar ~= info.bar_texture then
fx.bar, fx.start = info.bar_texture, now
end
local a = 0.0
if fx.start then
local t = now - fx.start
if t < 83 then a = 0.98 * t / 83
elseif t < 966 then a = 0.98 * (1.0 - (t - 83) / 883)
else fx.start = nil end
end
if fx.bar == "exam_max" then
local t = now % 1000
local g
if t < 433 then g = 0.352 * (1.0 - (1.0 - t / 433) ^ 2)
elseif t < 883 then g = 0.352 * (1.0 - (t - 433) / 450) ^ 2
else g = 0.0 end
a = math.max(a, g)
end
if a > 0.0 and info.bar_width > 0 then
tex.draw_texture(self.t.exam_flash, { y = y, x2 = info.bar_width, fade = a })
end
end
function DanGameDraw:exam_row(info, y, slot)
local t = self.t
tex.draw_texture(t.exam_bg, { y = y })
tex.draw_texture(t.exam_overlay_1, { y = y })
local bar = self.bars[info.bar_texture]
if info.failed then
if t.exam_fail then tex.draw_texture(t.exam_fail, { y = y, x2 = info.bar_width }) end
elseif bar then
tex.draw_texture(bar, { y = y, x2 = info.bar_width })
end
if not info.failed then self:bar_flash(info, y, slot) end
local cap, cx, cy = self:border_caption(info)
self:draw_label(info, cx, y)
cap:draw({ x = cx, y = cy + y })
tex.draw_texture(t.exam_overlay_2, { y = y })
if info.failed then
tex.draw_texture(t.exam_failed, { y = y })
else
self:draw_live_text(info, y, slot)
end
end
function DanGameDraw:draw(s)
local t = self.t
local tn = tex.get_skin_config("dan_game_total_notes_text")
local str = tex.skin_text("dan_game_total_notes_text")
if str ~= "" then
local ol = tn.outline >= 0 and tn.outline or 4.0 / tex.get_screen_scale()
local cap = self:caption(str, tn.font_size > 0 and tn.font_size or 36, ol)
cap:draw({ x = tn.x - cap.width / 2.0, y = tn.y, x2 = math.min(cap.width, tn.width) - cap.width })
end
self:digit_counter(tostring(s.remaining_notes), SKIN.dan_total_notes_margin.x,
t.total_notes_counter, 0, 0)
local rows = {}
for _, info in ipairs(s.rows) do
if info.exam_type ~= "gauge" and #rows < 3 then rows[#rows + 1] = info end
end
for slot, info in ipairs(rows) do
self:exam_row(info, ROW_OFFSETS[#rows][slot], slot)
end
tex.draw_texture(t.frame, { frame = s.dan_color })
local hn = SKIN.dan_game_hori_name
local h = self.hori_name
h:draw({ x = hn.x - h.width / 2.0, y = hn.y, x2 = math.min(h.width, hn.width) - h.width })
end