325 lines
13 KiB
Lua
325 lines
13 KiB
Lua
|
|
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
|