local F, DX, DY = 0.75, -166.5, 61.75 local F1 = 0.87 local CFG = { dan_exam_info = { x = 22 * F, y = 94 * F, width = 940 * F + 40 }, dan_result_song_name = { x = 1240 * F1 + -153.6, y = 90 * F1 + 49.36, width = 705 * F1, font_size = math.floor(40 * F1 + 0.5) }, -- banner right edge (1280) - 40 score_info_counter_margin = { x = 24 * F }, p1_counter_margin = { x = 24 * F1 }, dan_result_info_height = { y = 191 * F1 }, dan_result_hori_name = { x = 146.0, y = 123.5, width = 275 * F }, -- centred on the dan emblem dan_result_diff_num_margin = { x = 12 * F1 }, } -- exam captions: where the value starts, right of the label (Green px from its x 1163) local CAP_DX = { gauge = 6, hit = 33, combo = 22, score = 0, judgegood = 23, judgebad = 32, renda = 5, judgeperfect = 23 } local CAP_RIGHT = 180 local LABEL_R = { gauge = 2, hit = 29, combo = 18, score = -31, judgegood = 19, judgebad = 28, renda = 1, judgeperfect = 19 } -- label ink right edge (Green px from x 1163) local COL_LABEL_END, COL_NUM_END, COL_UNIT = 30, 127, 135 local TITLE_FONT = math.floor(40 * F + 0.5) local function cfg(k) return CFG[k] end 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 * F1 local SLIDE_BREAK_AT = 5.0 / 22.0 local SLIDE_BREAK_X = 35.0 * F1 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") for _, n in ipairs({"good", "ok", "bad", "drumroll", "counter"}) do self["p1_" .. n] = T("result_info/p1_" .. n) end 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 * F 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_select_title") or {} local font_size = TITLE_FONT 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 or -1) >= 0 and dan_title_cfg.outline or 5.0) local sn_cfg = cfg("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, p1f) local height = cfg("dan_result_info_height").y local margin = cfg("p1_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) * (p1f 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 = cfg("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 -- right-aligned at sn.x inside the banner (Green: left-aligned at its x 1230) sn_name:draw({x = sn.x - draw_w + sx, y = y + sn.y, x2 = draw_w - tw, fade = rf}) 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 = cfg("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 - #lv) * dm, y = y, fade = rf}) 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.p1_counter, {frame = tonumber(ch), x = sx - (k - 1) * margin, y = y, index = idx}) end end draw_stat(self.p1_good, song.good, 0) draw_stat(self.p1_ok, song.ok, 1) draw_stat(self.p1_bad, song.bad, 2) draw_stat(self.p1_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}) if clear and landed then tex.draw_texture(self.g_tamashii_overlay, {y = y, fade = fade}) end -- the fire is drawn by draw_gauge_fire, under the page-2 totals end function DanResultDraw:draw_gauge_fire(fade, now) if not self.g_fire or self.gauge_value < 100 then return end local on_page = now - self.page_start_ms local gi = self.gauge_exam if gi == nil or gi < 0 then return end local row = self.rows[gi + 1] if not row or on_page < row.land then return end local landed = self.page2_skipped or on_page >= self.totals_start + self.gauge_value * GAUGE_UNIT_MS if not landed then return end local y = 0 -- the soul gauge is in the top block local frame = math.floor(tex.get_current_ms() / 50.0) % 8 tex.draw_texture(self.g_fire, {frame = frame, y = y, fade = fade}) 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 = cfg("dan_select_exam_info") local offset_y = dei.y local margin = dei.x local border_pitch = margin -- no exam_border_counter in this skin local hide_gauge = #rd.exams >= 4 local slot = 0 for i, exam in ipairs(rd.exams) do local ed = rd.exam_data[i] if not ed then goto continue end if hide_gauge and exam.type == "gauge" then goto continue end local y = slot * offset_y slot = slot + 1 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 = false -- the soul gauge itself is drawn in the top block 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] local red_str = tostring(exam.red) local csc = tamashii_row and (25.0 / 36.0) or 1.0 local gap1, gap2 = 4.0 * F * csc, 10.0 * F * csc local cap_w = #red_str * border_pitch * csc if exam.type == "gauge" then cap_w = cap_w + gap1 + 31.0 * F * csc end if exam.range == "less" or exam.range == "more" then cap_w = cap_w + gap2 + 70.0 * F * csc end local num_w = #red_str * border_pitch * csc if exam.type == "gauge" then num_w = num_w + gap1 + 29.0 * F * csc end -- the %'s ink ends at 29 of 32 px local cap_dx, cap_dy = COL_NUM_END * F - num_w, 0.0 if icon then tex.draw_texture(icon, {x = (COL_LABEL_END - (LABEL_R[exam.type] or 0)) * F, y = y, fade = fade}) end if tamashii_row then cap_dx = self:gauge_border_x() - cap_w * 0.5 cap_dy = 52.0 * F 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 + 17.0 * F, y = y + cap_dy, fade = fade, index = 0}) after = after + gap1 + 31.0 * F * csc end after = (COL_UNIT - 5) * F -- the unit's anchor is 5 px right of the value's 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 - 2.0 * F, 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 * F, fade = fade}) end tex.draw_texture(self.t_result_2_pullout, {fade = fade}) tex.draw_texture(self.t_dan_emblem, {frame = rd.dan_color, fade = fade}) if self.hori_name then local hn = cfg("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, fade = fade, }) end local margin = cfg("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 self:draw_gauge_fire(fade, now) 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 * 0.94, self.t_score_counter, 0, 0.0, fade, 1.0, 12.0, roll_t) end local gi = self.gauge_exam local grow = gi and gi >= 0 and self.rows[gi + 1] if self.g_unfilled and grow and on_page >= grow.land then self:draw_gauge_row(0, fade, now) 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 local p2f = s.page2_fade or 0.0 if p2f < 1.0 then self:draw_page1(now - self.page1_start_ms, 1.0 - p2f) end 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 -- no character and no nameplate on this screen: both moved off screen function DanResultDraw:chara_pos() return { -4000, -4000, 1.0 } end function DanResultDraw:nameplate_pos() return { -4000, -4000, 0.0 } end