-- DAN_SELECT paint surface: selection cursor breathing loop and arrow chips, -- sampled from the cabinet's own clips. The dan boxes themselves are drawn by the -- parent skin's dan_select.lua, which is loaded first and extended here. -- the parent skin's copy of this script: the second match on package.path (the first is this file) local function load_parent(name) local seen = false for tmpl in package.path:gmatch("[^;]+") do local chunk = loadfile((tmpl:gsub("%?", name))) if chunk then if seen then return chunk end seen = true end end end local parent = load_parent("dan_select/dan_select") if parent then parent() end if not DanSelect then DanSelect = {} DanSelect.__index = DanSelect end local parent_new = DanSelect.new local ok, anim = pcall(require, "anim/sampler") if not ok then anim = { safe = function() local n = {} n.at = function() return nil end; n.get = function() return nil end n.label = function() return nil end; n.has = function() return false end n.first = function() return 0 end; n.last = function() return 0 end return n end } end local CUR = anim.safe("anim/dan_cursor") local ARW = anim.safe("anim/dan_arrow") local ARW_OFF = ARW:label("arrow_off") or 61 -- No real clip data at all -> no draw_cursor: the engine draws the cursor from its -- inline tables (the boxes are still drawn by the parent's DanSelect). local HAS_CLIPS = CUR:has("#259@0") or ARW:has("#261@0") function DanSelect.new() local self = parent_new and parent_new() or setmetatable({}, DanSelect) self.t_cursor = tex.get_texture("dan_select/box/cursor") self.t_arrow_r = tex.get_texture("dan_select/box/arrow_r") self.t_arrow_l = tex.get_texture("dan_select/box/arrow_l") return self 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); -- 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, y = y, fade = a }) end if moved_ms and moved_ms >= 0 then local f = moved_ms * 0.06 if f <= (ARW_OFF - 1) then local drift = ARW:get("#261@0", f, "tx") or 0 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, y = y, fade = a }) end if self.t_arrow_l then tex.draw_texture(self.t_arrow_l, { x = pos - drift, y = y, fade = a }) end end end end 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'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 ok, err = pcall(parent_open, self, box) 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 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 back_label = { cfg = cfg, vertical = vertical, main = text.create_raw_text(tex.skin_text("dan_back_label"), cfg.font_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