Compare commits

..
Author SHA1 Message Date
TaikoZhongandClaude Opus 5.5 c14075b8fa dan select: course panel closer to the cabinet
- Course rows show the song title alone (no subtitle). In CJK languages the title is set
  in runs (ASCII / everything else) so kanji and kana keep their width; it needs the
  engine's OutlinedText.text and falls back to the engine's title without it.
- Exam values: in CJK languages the digits are narrowed beside full-width 以上/未満, with
  a little room between them; smaller size and a thicker edge, centred on the row's
  coin and right-aligned with the cabinet's.
- Exam labels (yellow_box/exam_*.png) are smaller and edged in dark ochre, centred in
  their pills.
- The 合格条件 tab, frame and exam boxes sit 5 px higher, the tab joining the frame.
- The back chip's label set across (non-CJK languages) takes a smaller size instead of
  being squeezed to the chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:59:34 +08:00
TaikoZhongandClaude Opus 5.5 2e00d1a0f5 Keep checksums.sha256 in its previous line order
Same entries as the last commit; only the order is restored, with the new
paths appended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:15:14 +08:00
TaikoZhongandClaude Opus 5.5 ec5731e3bd Update checksums.sha256 for #63
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:14:23 +08:00
GanYiZhong c78383fdc1 Merge pull request 'dan select: back chip, chip names, cursor and course rows' (#63) from GanYiZhong/YataiDONNijiiro:feat/dan-select-back-chip into main 2026-10-14 09:13:47 +02:00
TaikoZhongandClaude Opus 5.5 a029b25b22 dan select: back chip, chip names, cursor and course rows (#53)
- Back entry: a chip in the top strip (back/folder, a light blue in the parent's back
  colour) with its label from skin_config dan_back_label, so it follows the language
  (ja 戻る, en Back, zh 返回, ko 뒤로).
- Chip names are black with no outline: dan_chip_name_black moves into screen.options,
  where the engine reads it.
- The selected chip drops below the strip and eases back; the cursor glow covers the
  chip and its name but not the clear stamp.
- Course rows: the 1st/2nd/3rd labels are centred on their row, and the level is one
  "star + number" image per level (yellow_box/difficulty_level, 1..10 and 10+) in place of
  the separate star and digits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:12:37 +08:00
TaikoZhongandClaude Opus 5.5 6b5ae8b5b5 Update checksums.sha256 for #62
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:56:25 +08:00
GanYiZhong afe0bab807 Merge pull request 'dan select: cabinet clear stamps, no parent ura badge' (#62) from GanYiZhong/YataiDONNijiiro:fix/dan-select-marks into main 2026-10-14 06:56:00 +02:00
TaikoZhongandClaude Opus 5.5 d10bd49613 dan select: cabinet clear stamps, no parent ura badge (#54, #55)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:55:40 +08:00
Yonokid 20d8e142d5 Update checksums.sha256 2026-10-13 23:59:04 -04:00
TaikoZhongandClaude Opus 5.5 3c9690b3fa Update checksums.sha256 for #60 and #61
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:17:00 +08:00
GanYiZhong 00d8f8e3e1 Merge pull request 'Losslessly recompress the PNGs stored uncompressed (385 MB -> 27 MB)' (#61) from GanYiZhong/YataiDONNijiiro:perf/recompress-pngs into main 2026-10-14 05:16:45 +02:00
31 changed files with 1201 additions and 777 deletions

No files matched your search

Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
{
"folder": {
"x": -44,
"y": 16
}
}
Binary file not shown.
Binary file not shown.
+4
View File
@@ -14,5 +14,9 @@
"arrow_l": {
"x": -95,
"y": 60
},
"clear_mark": {
"x": -39,
"y": 24
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13 -4
View File
@@ -83,12 +83,12 @@
"y": 186
},
"exam_header": {
"x": 420,
"y": 568
"x": 421,
"y": 563
},
"exam_frame": {
"x": 418,
"y": 601
"y": 596
},
"exam_box_top_left": {
"x": 420,
@@ -190,7 +190,7 @@
},
"song_label": {
"x": 430,
"y": 230
"y": 246
},
"exam_seg_label": {
"x": 0,
@@ -199,5 +199,14 @@
"exam_gauge_box": {
"x": 0,
"y": 0
},
"clear_mark": {
"x": 100,
"y": 505
},
"difficulty_ura": {},
"difficulty_level": {
"x": 538,
"y": 287
}
}
+27 -12
View File
@@ -45,6 +45,7 @@
"combo_color_tiers": true,
"gauge_cell_fade_in": true,
"dan_exam_grid": true,
"dan_chip_name_black": true,
"disable_chara_3d": false
}
},
@@ -627,7 +628,8 @@
"x": 632,
"y": 262,
"height": 60,
"font_size": 36
"font_size": 36,
"outline": 2.6
},
"dan_subtitle": {
"x": 632,
@@ -833,6 +835,19 @@
"height": 88,
"font_size": 42
},
"dan_back_label": {
"x": 0,
"y": 89,
"width": 52,
"height": 88,
"font_size": 42,
"text": {
"en": "Back",
"ja": "戻る",
"zh": "返回",
"ko": "뒤로"
}
},
"dan_result_chara": {
"x": 187,
"y": 934
@@ -938,11 +953,11 @@
},
"dan_exam_gauge_box": {
"x": 436,
"y": 615
"y": 610
},
"dan_exam_score_box": {
"x": 769,
"y": 615,
"y": 610,
"height": 132
},
"dan_exam_seg": {
@@ -951,16 +966,16 @@
"width": 327
},
"dan_select_exam_border_text": {
"x": 319,
"y": 54,
"font_size": 28,
"outline": 2.0
"x": 309,
"y": 55,
"font_size": 25,
"outline": 2.6
},
"dan_select_gauge_border_text": {
"x": 300,
"y": 55,
"font_size": 36,
"outline": 2.6666666666666665
"x": 291,
"y": 67,
"font_size": 32,
"outline": 3.1
},
"dan_exam_value": {
"x": 185,
@@ -968,7 +983,7 @@
},
"dan_exam_label_pill": {
"x": 28,
"y": 2
"y": -1
},
"dan_exam_value_left": {
"x": 528
+326 -5
View File
@@ -47,12 +47,14 @@ function DanSelect.new()
end
-- pos: selected chip x (the textures' own json positions carry the rest);
-- now_ms: engine clock; moved_ms: ms since the last ribbon step (-1 = never).
local function draw_cursor(self, pos, now_ms, moved_ms)
-- now_ms: engine clock; moved_ms: ms since the last ribbon step (-1 = never);
-- y: how far the selected chip has dropped (see draw_box below).
local function draw_cursor(self, pos, now_ms, moved_ms, y)
y = y or 0
if self.t_cursor then
local f = (now_ms * 0.06) % 60
local a = CUR:get("#259@0", f, "a") or 1
tex.draw_texture(self.t_cursor, { x = pos, fade = a })
tex.draw_texture(self.t_cursor, { x = pos, y = y, fade = a })
end
if moved_ms and moved_ms >= 0 then
local f = moved_ms * 0.06
@@ -61,10 +63,10 @@ local function draw_cursor(self, pos, now_ms, moved_ms)
local a = ARW:get("#261@0", f, "a") or 0
if a > 0 then
if self.t_arrow_r then
tex.draw_texture(self.t_arrow_r, { x = pos + drift, fade = a })
tex.draw_texture(self.t_arrow_r, { x = pos + drift, y = y, fade = a })
end
if self.t_arrow_l then
tex.draw_texture(self.t_arrow_l, { x = pos - drift, fade = a })
tex.draw_texture(self.t_arrow_l, { x = pos - drift, y = y, fade = a })
end
end
end
@@ -72,3 +74,322 @@ local function draw_cursor(self, pos, now_ms, moved_ms)
end
if HAS_CLIPS then DanSelect.draw_cursor = draw_cursor end
-- The level is the cabinet's own art, one "star + number" image per level
-- (yellow_box/difficulty_level, frames 1..10 and 10+), in place of the parent's star and
-- level digits, which are held back.
local function draw_level(level, x, y, f)
local id = tex.get_texture("dan_select/yellow_box/difficulty_level")
if not id or level < 1 then return end
tex.draw_texture(id, { frame = math.min(level, 11) - 1, x = x, y = y, fade = f })
end
-- The course rows are set as the cabinet's: the song title alone (no subtitle line), and the
-- exam values with narrow digits beside full-width 以上/未満 and a little room between them.
-- The skin has one font, so each text is cut into runs of ASCII and of everything else, each
-- run made on its own and scaled as it is drawn, the runs butted together. The parent is handed
-- the box through a view whose song_subtitle_text gives nothing and whose song_title_text gives
-- such a title, and the captions it makes for the exam values are made the same way. Only CJK
-- languages are rescaled; the skin's font for the others is narrow already.
local function cjk_language()
local l = tex.language() or ""
return l:find("^ja") or l:find("^zh") or l:find("^ko") or l == "tw" or l == "cn"
end
local TITLE_CJK = { latin = 1, cjk = 1.02, sy = 1, dy = 0, max_w = 1100 }
local VALUE_CJK = { latin = 0.85, cjk = 1.0, sy = 1.0, dy = 0, gap = 8 }
local TITLE_PLAIN = { latin = 1, cjk = 1, sy = 1, dy = 0, max_w = 1100 }
local VALUE_PLAIN = { latin = 1, cjk = 1, sy = 1, dy = 0 }
local function runs_of(str)
local runs, cur, ascii = {}, {}, nil
for ch in str:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
local a = ch:byte() < 128
if ascii ~= nil and a ~= ascii then
runs[#runs + 1] = { text = table.concat(cur), ascii = ascii }
cur = {}
end
cur[#cur + 1] = ch
ascii = a
end
if #cur > 0 then runs[#runs + 1] = { text = table.concat(cur), ascii = ascii } end
return runs
end
-- create: text.create_raw_text as it was before any wrapping. The parts' sizes are read when
-- they are used, since a text's texture (and size) may still be building when it is made.
local function mixed_text(create, str, size, color, outline_color, outline, spacing, style)
local pad = math.floor((outline or 0) * tex.get_screen_scale()) + 2
local parts = {}
for _, r in ipairs(runs_of(str)) do
parts[#parts + 1] = { t = create(r.text, size, color, outline_color, false, outline, spacing),
sx = r.ascii and style.latin or style.cjk }
end
local function layout()
local function span(k)
local x = 0
for n, q in ipairs(parts) do
q.x = x
local next_sx = parts[n + 1] and parts[n + 1].sx * k or 0
x = x + (q.t.width - pad) * q.sx * k - pad * next_sx
if parts[n + 1] then x = x + (style.gap or 0) * k end
end
local last = parts[#parts]
return last and (last.x + last.t.width * last.sx * k) or 0
end
local k, w = 1, span(1)
if style.max_w and w > style.max_w then
k = style.max_w / w
w = span(k)
end
local h = 0
for _, q in ipairs(parts) do h = math.max(h, q.t.height) end
return k, w, h * style.sy
end
return setmetatable({
text = str,
draw = function(_, p)
local k = layout()
for _, q in ipairs(parts) do
local sx = q.sx * k
q.t:draw({ x = p.x + q.x, y = (p.y or 0) + style.dy, fade = p.fade,
x2 = q.t.width * (sx - 1), y2 = q.t.height * (style.sy - 1) })
end
end,
}, {
__index = function(_, key)
if key == "width" then local _, w = layout(); return w end
if key == "height" then local _, _, h = layout(); return h end
end,
})
end
local title_cache = {} -- by string, size and outline: texts are built once, not per frame
local raw_create = text.create_raw_text -- the engine's, before draw_dan_open wraps it
local function course_view(box)
local titles = {}
return setmetatable({}, {
__index = function(_, k)
if k == "song_subtitle_text" then return function() return nil end end
if k == "song_title_text" then
return function(_, i)
if titles[i] == nil then
local t = box:song_title_text(i)
local ok, str = pcall(function() return t and t.text end)
local td = tex.get_skin_config("dan_title")
if t and ok and type(str) == "string" and str ~= "" and td then
local ol = td.outline >= 0 and td.outline or 5
local key = str .. "\0" .. td.font_size .. "\0" .. ol
title_cache[key] = title_cache[key]
or mixed_text(raw_create, str, td.font_size,
{ 255, 255, 255, 255 }, { 0, 0, 0, 255 }, ol, 2,
cjk_language() and TITLE_CJK or TITLE_PLAIN)
titles[i] = title_cache[key]
else
titles[i] = t or false -- an engine without OutlinedText.text
end
end
return titles[i] or nil
end
end
local v = box[k]
if type(v) == "function" then
return function(_, ...) return v(box, ...) end
end
return v
end,
})
end
-- The course's clear stamp (yellow_box/clear_mark) sits over the dan plate and Don, as the
-- cabinet's stamp_detail does (it is above don_1p). The parent draws it with the box, under
-- both, so it is held back there and drawn with the title, which the engine draws after
-- the chara and the nameplate.
local parent_open, parent_title = DanSelect.draw_dan_open, DanSelect.draw_title
local held_stamp -- shared: the boxes and the title can be drawn through different instances
if parent_open then
function DanSelect:draw_dan_open(box)
local stamp = tex.get_texture("dan_select/yellow_box/clear_mark")
local level = tex.get_texture("dan_select/yellow_box/difficulty_level")
local hide = {}
if level then
for _, n in ipairs({ "difficulty_star", "difficulty_num", "difficulty_x" }) do
local t = tex.get_texture("dan_select/yellow_box/" .. n)
if t then hide[#hide + 1] = t end
end
end
local draw = tex.draw_texture
tex.draw_texture = function(id, p)
if id == stamp then return end
for _, t in ipairs(hide) do -- handles compare with ==, not as table keys
if id == t then return end
end
return draw(id, p)
end
local create = text.create_raw_text
text.create_raw_text = function(str, size, color, outline_color, vertical, outline, spacing, ...)
if vertical or type(str) ~= "string" then -- only the exam value captions
return raw_create(str, size, color, outline_color, vertical, outline, spacing, ...)
end
return mixed_text(raw_create, str, size, color, outline_color, outline, spacing,
cjk_language() and VALUE_CJK or VALUE_PLAIN)
end
local ok, err = pcall(parent_open, self, course_view(box))
text.create_raw_text = create
tex.draw_texture = draw
if not ok then error(err, 0) end
local f = box.open_fade.attribute
local off = tex.get_skin_config("dan_yellow_box_offset") or { x = 0, y = 0 }
if level then
for i = 0, box:song_count() - 1 do
draw_level(box:song_level(i), i * (off.x or 0), i * (off.y or 0), f)
end
end
if stamp and box.clear_mark > 0 then
held_stamp = { id = stamp, frame = box.clear_mark - 1, fade = f }
end
end
function DanSelect:draw_title()
if parent_title then parent_title(self) end
local s = held_stamp
held_stamp = nil
if s then tex.draw_texture(s.id, { frame = s.frame, fade = s.fade }) end
end
end
-- The back entry is one more chip in the top strip (back/folder, laid out like box/folder);
-- the parent's opening board is its own layout, so nothing opens under it here.
-- Its label comes from skin_config dan_back_label, so it follows the language setting. It is set
-- like the dan chip names (black, no outline: dan_chip_name_black), vertical for CJK and
-- across, squeezed to the chip, for other languages.
local CJK = { ja = true, zh = true, ko = true, tw = true, cn = true }
local back_label
local BACK_ACROSS_SIZE = 0.7 -- size of a label set across, against dan_back_label.font_size
local function get_back_label()
if back_label == nil then
local cfg = tex.get_skin_config("dan_back_label")
if not cfg then back_label = false; return false end
local lang = tex.language()
local vertical = CJK[lang] or CJK[lang:sub(1, 2)] or false
-- a word set across has only the chip's width, so it takes a smaller size than the
-- vertical labels rather than being squeezed hard
local size = vertical and cfg.font_size or math.floor(cfg.font_size * BACK_ACROSS_SIZE + 0.5)
back_label = {
cfg = cfg,
vertical = vertical,
main = raw_create(tex.skin_text("dan_back_label"), size,
{ 0, 0, 0, 255 }, { 0, 0, 0, 255 }, vertical, 0, 2),
}
end
return back_label
end
local function draw_back_label(bx, fade, y)
local label = get_back_label()
if not label then return end
local cfg, main = label.cfg, label.main
local sx, sy = 1, 1
if label.vertical then
sy = math.min(1, cfg.height / main.height)
else
sx = math.min(1, cfg.width / main.width) -- width: chip face less the border
end
local w, h = main.width * sx, main.height * sy
main:draw({ x = bx + cfg.x - w / 2, y = cfg.y + (cfg.height - h) / 2 + y,
x2 = w - main.width, y2 = h - main.height, fade = fade })
end
-- The selected chip drops below the strip, as the cabinet's does, easing there and back.
-- Only draw_cursor (called after every box) is told where the cursor is, so the boxes are
-- held back in draw_box and drawn from draw_cursor.
local SELECTED_DROP = 22
local DROP_MS = 60 -- time constant of the ease
local drop = {} -- chip key -> current drop
local held_boxes = {}
local last_ms
local function chip_key(box)
return box.is_back and "\0back" or box.text_name
end
local function chip_drop(box)
return drop[chip_key(box)] or 0
end
local function draw_chip_name(box, bx, y, fade)
local name = box:chip_name()
if not name then return end
local nb = tex.get_skin_config("dan_chip_name") or { x = 0, y = 0 }
name:draw({
x = bx + nb.x - name.width / 2,
y = nb.y + y,
y2 = math.min(name.height, nb.height or name.height) - name.height,
fade = fade,
})
end
-- the chip as the parent draws it (folder, stamp, name), at its drop
function DanSelect:draw_dan_closed(box)
local bx, fade, y = box:box_x(), box.fade.attribute, chip_drop(box)
local id = tex.get_texture("dan_select/box/folder")
if id then tex.draw_texture(id, { frame = box.dan_color, x = bx, y = y, fade = fade }) end
local mark = box.clear_mark > 0 and tex.get_texture("dan_select/box/clear_mark")
if mark then tex.draw_texture(mark, { frame = box.clear_mark - 1, x = bx, y = y, fade = fade }) end
draw_chip_name(box, bx, y, fade)
end
function DanSelect:draw_back_box(box)
local bx, fade, y = box:box_x(), box.fade.attribute, chip_drop(box)
local id = tex.get_texture("dan_select/back/folder")
if id then tex.draw_texture(id, { x = bx, y = y, fade = fade }) end
draw_back_label(bx, fade, y)
end
-- The cursor's glow goes over the chip, its emblem and name (they grey under it, as on the
-- cabinet) but not over the clear stamp, which is drawn again on top of it.
local function draw_chip_front(box)
if box.is_back or box.clear_mark <= 0 then return end
local mark = tex.get_texture("dan_select/box/clear_mark")
if mark then
tex.draw_texture(mark, { frame = box.clear_mark - 1, x = box:box_x(), y = chip_drop(box),
fade = box.fade.attribute })
end
end
local parent_draw_box = DanSelect.draw_box
function DanSelect:draw_box(box)
held_boxes[#held_boxes + 1] = box
end
local cursor_parent = DanSelect.draw_cursor
function DanSelect:draw_cursor(pos, now_ms, moved_ms)
local boxes = held_boxes
held_boxes = {}
local k = last_ms and math.min(1, math.max(0, now_ms - last_ms) / DROP_MS) or 1
last_ms = now_ms
local selected
for _, box in ipairs(boxes) do
local key = chip_key(box)
local is_sel = math.abs(box:box_x() - pos) < 1
if is_sel then selected = box end
local d = drop[key] or 0
d = d + ((is_sel and SELECTED_DROP or 0) - d) * k
drop[key] = (math.abs(d) >= 0.05) and d or nil
end
-- draw every box even if one fails, then report the first failure
local err
for _, box in ipairs(boxes) do
local ok, e = pcall(parent_draw_box, self, box)
if not ok and not err then err = e end
end
if err then error(err, 0) end
local y = selected and chip_drop(selected) or 0
if cursor_parent then
cursor_parent(self, pos, now_ms, moved_ms, y)
elseif self.t_cursor then
tex.draw_texture(self.t_cursor, { x = pos, y = y })
end
if selected then draw_chip_front(selected) end
return true
end
+758 -740
View File
File diff suppressed because it is too large. Load diff