require("background.yd14_background") local C = require("background.t12_clip") local K = tex.get_screen_width() / 640 local BEAT = 3600 local FPB = 40 -- movie frames per beat local NEVER = 1e9 local IDX = require("background.t12.index") -- layout (640 space): movie origins local UPPER_Y = 0 local DON_X, DON_Y = -42, -66 -- 256x256 Don movie; its body sits where taikonantoka's Don is local GEKI_X, GEKI_Y = 125 - 94, 129 - 118 local GEKI_BALLOON_Y = 150 -- YataiDON14's balloon_y: its 2P mirror keeps this offset -- texture roles: the six sets share one layout except geki_05 (a scarf: 7 more textures after the -- stub, so every index behind them moves by 7); told apart by the texture count (24 / 31) local GEKI_ROLES = { [24] = { bubble = 9, digit0 = 10 }, [31] = { bubble = 16, digit0 = 17 }, } local function geki_roles(clip) return GEKI_ROLES[#clip.d.size] or GEKI_ROLES[24] end local GEKI_BUBBLE = { x = 103, y = -40, w = 153.6 } local GEKI_NUM_X = { 132, 157, 182 } -- geki_num_100 / _10 / _00 local GEKI_NUM_Y = 16 local CHOCHIN_Y = 440 -- the footer strip on the bottom edge (440 + 40 = 480) local DBG_Y = 256 -- right under the lane / title band; 256 + 184 = 440 local DBG_TOP_HIDE = 2 -- its top rows stay covered (see draw_dancer_bg) local FEVER_Y = 298 local DANCER_X = { 320, 190, 450, 60, 580 } local DANCER_Y = 440 local DANCER_GAUGE = { 0, 0.25, 0.5, 0.75, 1.0 } -- x clear line: when dancer i steps in local CHIBI_FRAMES = 160 local CHIBI_X, CHIBI_Y = DON_X, -60 local FEVER_START, FEVER_LOOP, FEVER_END = 40, 81, 40 local DBG_TRANS = 69 -- normal_fever / fever_normal frames local function pick(t) return t[math.random(1, #t)] end local function fr60(ms) return ms * 60 / 1000 end -- a balloon frame without its bubble (drawn by hand, see GEKI_BUBBLE) local function without_bubble(ops, roles) if not ops then return nil end local out = {} for n = 1, #ops, 14 do if ops[n] ~= roles.bubble then for k = n, n + 13 do out[#out + 1] = ops[k] end end end return out end local base_new = Background.new function Background.new(player_num, bpm, scene_preset) local self = base_new(player_num, bpm, scene_preset) if getmetatable(self) ~= Background then return self end -- collab / tutorial scenes self.t12 = { upper = C.get(pick(IDX.upper_1p)), upper2 = C.get(pick(IDX.upper_2p)), dbg = C.get(pick(IDX.dbg)), fever = C.get(pick(IDX.fever)), chochin = C.get(pick(IDX.chochin)), } local random_costume = math.random(1, #IDX.don) local function costume_for(pn) local rows = IDX.costume_rank if not rows then return random_costume end local p = T8_PROGRESS and T8_PROGRESS[pn] local c = rows[1][1] if p and #p.scores > 0 then local n = math.min(#p.scores, 4) local total = 0 for i = #p.scores - n + 1, #p.scores do total = total + p.scores[i] end for _, r in ipairs(rows) do if total >= r[n + 1] then c = r[1] end end end return math.min(#IDX.don, c + 1) end local c1, c2 = costume_for(1), costume_for(2) self.t12.don, self.t12.katsu = C.get(IDX.don[c1]), C.get(IDX.katsu[c2]) self.t12.geki, self.t12.geki2 = C.get(IDX.geki_don[c1]), C.get(IDX.geki_katsu[c2]) if player_num == 2 then self.t12.upper, self.t12.don, self.t12.geki = self.t12.upper2, self.t12.katsu, self.t12.geki2 end local set = pick(IDX.dance_sets) self.t12.dancers = {} for i = 1, 5 do self.t12.dancers[i] = { clip = C.get(set[(i - 1) % #set + 1]), on = false, t = -NEVER } end self.t12.chibi_set = pick(IDX.chibi_sets) self.t12.dbg_state, self.t12.dbg_t = "normal", 0 self.t12.fever_state, self.t12.fever_t = nil, 0 self.t12.don_s = { gogo = false, gogo_t = -NEVER } self.t12.don2_s = { gogo = false, gogo_t = -NEVER } return self end function Background:handle_gogo(player_num, on) if not self.t12 then return end local d = (self.is_2p and player_num == 2) and self.t12.don2_s or self.t12.don_s if d.gogo ~= on then d.gogo = on; d.gogo_t = self.clk end end local base_reset = Background.reset_play function Background:reset_play() base_reset(self) if not self.t12 then return end for _, d in ipairs(self.t12.dancers) do d.on, d.t = false, -NEVER end self.t12.dbg_state, self.t12.dbg_t = "normal", 0 self.t12.fever_state = nil self.t12.don_s = { gogo = false, gogo_t = -NEVER } self.t12.don2_s = { gogo = false, gogo_t = -NEVER } end local base_update = Background.update function Background:update(current_ms, bpm) base_update(self, current_ms, bpm) local t = self.t12 if not t then return end -- dancers step in / out with the gauge local line = self.clear_line or 80 for i, d in ipairs(t.dancers) do local want = (i == 1) or (self.gauge >= DANCER_GAUGE[i] * line) if want ~= d.on then d.on = want; d.t = self.clk end end -- dancer background: normal <-> fever with the clear line local want = self.is_clear and "fever" or "normal" local cur = t.dbg_state if want == "fever" and (cur == "normal" or cur == "fever_normal") then t.dbg_state, t.dbg_t = "normal_fever", self.ms elseif want == "normal" and (cur == "fever" or cur == "normal_fever") then t.dbg_state, t.dbg_t = "fever_normal", self.ms elseif cur == "normal_fever" and fr60(self.ms - t.dbg_t) >= DBG_TRANS then t.dbg_state, t.dbg_t = "fever", self.ms elseif cur == "fever_normal" and fr60(self.ms - t.dbg_t) >= DBG_TRANS then t.dbg_state, t.dbg_t = "normal", self.ms end -- fever gallery: with the full soul gauge if self.is_full and t.fever_state == nil then t.fever_state, t.fever_t = "start", self.ms elseif not self.is_full and (t.fever_state == "start" or t.fever_state == "loop") then t.fever_state, t.fever_t = "end", self.ms end if t.fever_state == "start" and fr60(self.ms - t.fever_t) >= FEVER_START then t.fever_state, t.fever_t = "loop", self.ms elseif t.fever_state == "end" and fr60(self.ms - t.fever_t) >= FEVER_END then t.fever_state = nil end end --------------------------------------------------------------------------------------------- function Background:draw_upper(y0, u, s) local t = self.t12 if not t then return end local clip = (u == self.upper2 and self.is_2p) and t.upper2 or t.upper if not clip then return end local y = (y0 or 0) / K clip:draw(clip:loop_frame("loop", fr60(self.ms)), 0, UPPER_Y + y, K) end function Background:draw_dancer_bg() local t = self.t12 if not (t and t.dbg) then return end local st = t.dbg_state local ops if st == "normal" or st == "fever" then ops = t.dbg:loop_frame(st, fr60(self.ms)) or t.dbg:frame(st, fr60(self.ms - t.dbg_t)) local s = t.dbg:state(st) if s and #s.intro > 0 then ops = t.dbg:frame(st, fr60(self.ms - t.dbg_t)) end else ops = t.dbg:frame(st, fr60(self.ms - t.dbg_t), true) end t.dbg:draw(ops, 0, DBG_Y, K) tex.draw_rect(0, (DBG_Y - 1) * K, 640 * K, (DBG_TOP_HIDE + 1) * K, 0, 0, 0, 255) -- from 1 px up: the title band ends at 255.x end local function don_state(self, s, d) local full = s.is_clear or (s == self and self.is_clear) local clk = self.clk if s.state == 1 then return "miss", nil end if d.gogo then local e = (clk - d.gogo_t) / BEAT * FPB if e < 80 then return full and "full_sabi_start" or "sabi_start", e end return full and "full_sabi" or "sabi", nil elseif d.gogo_t > -NEVER and (clk - d.gogo_t) / BEAT * FPB < 80 then return full and "full_sabi_end" or "sabi_end", (clk - d.gogo_t) / BEAT * FPB end local e = self.now - s.hop_ms if e >= 0 and e < 700 then return full and "full_combo" or "combo", fr60(e) end return full and "full_gage" or "normal", nil end function Background:draw_chara(cy_override, c, s) s = s or self local t = self.t12 if not t then return end local is_p2 = (s ~= self) if s.balloon > 0 then return end -- the balloon movie has its own Don (draw_balloon_chara) local clip = is_p2 and t.katsu or t.don if not clip then return end local d = is_p2 and t.don2_s or t.don_s local name, f = don_state(self, s, d) if not clip:state(name) then name, f = "normal", nil end local ops if f then ops = clip:frame(name, f, true) else ops = clip:loop_frame(name, self.clk / BEAT * FPB) or clip:frame(name, 0) end local oy = DON_Y if cy_override then oy = cy_override / K - 62 + DON_Y end clip:draw(ops, DON_X, oy, K) end -- balloon: 1..6 rolling (inflating), 9 burst, 10 fail function Background:geki_ops(s) local t = self.t12 local clip = (s ~= self) and t.geki2 or t.geki if not clip then return nil end local e = fr60(self.now - s.balloon_ms) local ops if s.balloon >= 1 and s.balloon <= 6 then ops = clip:frame("hit_" .. s.balloon, e, false) elseif s.balloon == 9 then ops = clip:frame("break", e, true) elseif s.balloon == 10 then ops = clip:frame("miss", e, true) end return clip, ops, e end function Background:draw_balloon_chara(s, c, Y) s = s or self local t = self.t12 if not t or s.balloon == 0 then return end local clip, ops, e = self:geki_ops(s) if not clip then return end local roles = geki_roles(clip) -- 2P: YataiDON14 mirrors balloon_y and keeps the chara's offset from it (art not flipped) local oy = GEKI_Y if Y then oy = Y(GEKI_BALLOON_Y * K) / K - GEKI_BALLOON_Y + GEKI_Y end clip:draw(without_bubble(ops, roles), GEKI_X, oy, K) if not (s.balloon >= 1 and s.balloon <= 6) then return end local B = GEKI_BUBBLE local bt = clip:texture(roles.bubble) if bt then -- flipped as in the movie: tail down-left, towards the balloon tex.draw_texture(bt, { x = (GEKI_X + B.x) * K, y = (oy + B.y) * K, x2 = B.w * K - 256, y2 = B.w * K - 256, mirror = "horizontal" }) end local left = s.balloon_left if not left or left < 0 then return end local str = tostring(math.min(left, 999)) local sc = (e < 2) and (1 + 0.1 * e) or (e < 9) and (1.2 - 0.2 * (e - 2) / 7) or 1 for i = 1, #str do local dt = clip:texture(roles.digit0 + tonumber(str:sub(i, i))) if dt then local h = 32 * sc tex.draw_texture(dt, { x = (GEKI_X + GEKI_NUM_X[3 - #str + i]) * K, y = (oy + GEKI_NUM_Y + 32 - h) * K, x2 = 32 * K - 32, y2 = h * K - 32 }) end end end function Background:draw_dancers() local t = self.t12 if not t then return end local order = { 4, 2, 1, 3, 5 } -- draw back to front: outer ones first for _, i in ipairs(order) do local d = t.dancers[i] if d.clip then local e = (self.clk - d.t) / BEAT * FPB local ops if d.on then if e < 80 then ops = d.clip:frame("in", e, true) else ops = d.clip:loop_frame("dance", self.clk / BEAT * FPB) end elseif e < 80 and d.t > -NEVER then ops = d.clip:frame("out", e, true) end d.clip:draw(ops, DANCER_X[i], DANCER_Y, K) end end end -- the footer strip (DAIBG: the stage's front edge) goes over the dancers and the fever crowd function Background:draw_footer() local t = self.t12 if t and t.chochin then t.chochin:draw(t.chochin:frame("loop", 0), 0, CHOCHIN_Y, K) end end function Background:draw_stage() if self.is_2p then self:draw_footer() end -- 2P: no dancers / fever (draw_fiver is 1P only) end function Background:draw_fiver() self:draw_fever_crowd() self:draw_footer() end function Background:draw_fever_crowd() local t = self.t12 if not (t and t.fever and t.fever_state) then return end local e = fr60(self.ms - t.fever_t) local st = t.fever:state("fever_start") local ops if t.fever_state == "start" then ops = t.fever:frame("fever_start", e, true) elseif t.fever_state == "loop" then local lp = t.fever:state("fever") if lp and #lp.intro > 0 then ops = lp.intro[math.floor(e) % math.min(FEVER_LOOP, #lp.intro) + 1] end else ops = t.fever:frame("fever_end", e, true) end t.fever:draw(ops, 0, FEVER_Y, K) end function Background:draw_chibis() local t = self.t12 if not t then return end for _, c in ipairs(self.chibis) do if c.set.sub ~= "nk_chibi_soul" then if not c.t12 then c.t12 = C.get(t.chibi_set[math.random(1, #t.chibi_set)]) end local e = (self.clk - c.clk) / BEAT * FPB if c.t12 and e < CHIBI_FRAMES then c.t12:draw(c.t12:frame("loop", e, true), CHIBI_X, UPPER_Y + CHIBI_Y, K) end end end end local base_destroy = Background.destroy function Background:destroy() base_destroy(self) C.unload_all() end