236 lines
9.6 KiB
Lua
236 lines
9.6 KiB
Lua
local DonBGBase = require("background/bg_objects/donbg/base")
|
|
local BGNormal = require("background/bg_objects/bg_normal")
|
|
local PetalFever = require("background/bg_objects/petal_fever")
|
|
local Fever = require("background/bg_objects/fever")
|
|
local Footer = require("background/bg_objects/footer")
|
|
local Dancer = require("background/bg_objects/dancer")
|
|
local RendaController = require("background/bg_objects/renda")
|
|
local ChibiController = require("background/bg_objects/chibi")
|
|
local BaseChibi = require("background/bg_objects/chibi/base")
|
|
local Timing = require("background/bg_objects/timing")
|
|
|
|
-- Touken Ranbu
|
|
local COLLAB_PATH = "background/collab/touken"
|
|
local MAX_DANCERS = 3
|
|
|
|
-- Don band: the base scrolls two tiles left every 5.33s; the clear set scrolls with it under
|
|
-- a pink glow strip that pulses every second (IMAS's band with half-width tiles). Ambient, in ms.
|
|
local TOUKEN_DONBG = {
|
|
fade = 0.2,
|
|
layers = {
|
|
{ key = "background", ms = 5333, dx = -328 },
|
|
{ key = "glow", clear_only = true, tile = 0, alpha = 0.148, blend = "additive",
|
|
period = 1000, pulse = { { 500, -1000 }, { 500, 1000, "out" } } },
|
|
},
|
|
}
|
|
|
|
-- Clear background: the shrine courtyard (bg_normal) stays underneath; a pink glow fades in
|
|
-- over it and two streams of cherry petals fall across, a new pass every 1.25s. Lumen screens
|
|
-- the glow onto the courtyard; glow.png is that screen baked as an additive layer.
|
|
local TOUKEN_FEVER = {
|
|
path = COLLAB_PATH, haze = { key = "glow", blend = "additive" }, haze_in = 1.5, haze_out = 1,
|
|
petals_out = 0.225,
|
|
streams = {
|
|
{ key = "petals", flight = 2500, dx = -332, dy = 622, warm = true },
|
|
{ key = "petals_2", flight = 2500, dx = -259, dy = 650, warm = true },
|
|
},
|
|
}
|
|
|
|
-- Dancers: three swordsmen jump up from below, land with a squash, dance an 8-beat routine and
|
|
-- hop up and drop away when removed. Timed in beats; poses are offsets of their feet (texture.json
|
|
-- y 686), the pivot for the squash. Dancer 2 (left) has an extra landing frame, so its routine
|
|
-- frames are one higher.
|
|
local SQUASH = 0.978
|
|
-- one 2-beat phrase: two poses with a dip, a hop on the third, then two quarter-beat poses
|
|
local function phrase(a, b, c, d, e)
|
|
return {
|
|
{ a, 0.225, y = 0 }, { a, 0.25, sy = SQUASH }, { b, 0.25, sy = SQUASH }, { b, 0.275, y = 0 },
|
|
{ c, 0.2, y = -11 }, { c, 0.15, y = -6 }, { c, 0.15, y = 0, sy = SQUASH }, { d, 0.25 }, { e, 0.25 },
|
|
}
|
|
end
|
|
|
|
local function touken_dancer(shift)
|
|
local function routine(...)
|
|
local poses = Timing.steps(...)
|
|
for _, p in ipairs(poses) do p[2] = p[2] + shift end
|
|
return poses
|
|
end
|
|
local dance = routine(phrase(1, 2, 3, 4, 5), phrase(6, 7, 8, 9, 10), phrase(1, 2, 3, 4, 5),
|
|
phrase(6, 7, 11, 12, 13))
|
|
dance[1][3] = 0
|
|
return {
|
|
origin = { 128, 312 },
|
|
enter = {
|
|
beats = 2, key = "loop",
|
|
{ 0.2, 0, 0, 394, to = { 0, -40, "out" } }, -- jumps up from below
|
|
{ 0.875, 0, 0, -40, to = { 0, -4, "in" } },
|
|
{ 1.35, 1, 0, 0, to = { nil, nil, "out", sy = 0.94 } }, -- lands with a squash
|
|
{ 1.575, 1, 0, 0, sy = 0.94, to = { nil, nil, "in", sy = 1 } },
|
|
{ 1.825, 1, 0, 0 },
|
|
},
|
|
dance = dance,
|
|
leave = {
|
|
beats = 1.125, key = "loop",
|
|
{ 0, 14 + shift, 0, 0, to = { 0, -40, "out" } }, -- hops up and drops away
|
|
{ 0.45, 14 + shift, 0, -40, to = { 0, 394, "in" } },
|
|
},
|
|
}
|
|
end
|
|
local TOUKEN_DANCERS = Dancer.define({ default = touken_dancer(0), [1] = touken_dancer(1) })
|
|
|
|
-- Slots (left, centre, right) from Lumen's dancer positions; centre on top
|
|
local DANCER_CENTERS = { 427, 640, 853 }
|
|
local DANCER_DRAW_ORDER = { 3, 1, 2 }
|
|
local DANCER_SPAWN = { 1, 0, 2 } -- 0-indexed slots in join order: centre, left, right
|
|
-- dancer index by join order: dancer1 (centre), dancer2 (left), dancer3 (right)
|
|
local DANCER_JOIN_ORDER = { 2, 1, 0 }
|
|
|
|
-- Chibis for good hits: a crest flower fades in growing from half size as it rises onto the
|
|
-- don band, zigzags 33px down and up seven times at a steady pace, then rolls on level.
|
|
-- Offsets from texture.json (Lumen's attach point), in px; timed in beats.
|
|
local CHIBI_LIFE = 5
|
|
local CHIBI_HOP = { length = 0.5,
|
|
{ 0, 0, -424, 13, scale = 0.5, alpha = 0, to = { -355, 17, scale = 0.65, alpha = 0.3 } },
|
|
{ 0.15, 0, -355, 17, scale = 0.65, alpha = 0.3, to = { -206, -24, scale = 1, alpha = 1 } },
|
|
}
|
|
local CHIBI_RUN = { from = -206, to = 938 }
|
|
local CHIBI_ZIG = { top = -24, bottom = 9, half = 0.51, halves = 7 }
|
|
local CHIBI_KEYS = { "1", "2", "3" } -- red, blue, purple
|
|
|
|
local ToukenChibi = setmetatable({}, { __index = BaseChibi })
|
|
ToukenChibi.__index = ToukenChibi
|
|
|
|
function ToukenChibi.new(spec, path, folder, bpm, is_2p)
|
|
local self = setmetatable({}, ToukenChibi)
|
|
self:init(spec, path, folder, bpm, is_2p)
|
|
return self
|
|
end
|
|
|
|
function ToukenChibi:update(current_time_ms)
|
|
self.t0 = self.t0 or current_time_ms
|
|
self.t = (current_time_ms - self.t0) / self.beat
|
|
self.done = self.t >= CHIBI_LIFE
|
|
end
|
|
|
|
function ToukenChibi:draw()
|
|
if not self.t then return end
|
|
local t = self.t
|
|
if t < CHIBI_HOP.length then
|
|
local p = Timing.pose_at(CHIBI_HOP, t)
|
|
self:draw_image({ x = p.x, y = p.y, sx = p.sx, sy = p.sy, fade = p.alpha })
|
|
return
|
|
end
|
|
local run, z = t - CHIBI_HOP.length, CHIBI_ZIG
|
|
local leg, y = run / z.half, z.bottom
|
|
if leg < z.halves then
|
|
local half = leg - math.floor(leg)
|
|
if math.floor(leg) % 2 == 1 then half = 1 - half end -- even legs fall, odd ones rise
|
|
y = z.top + (z.bottom - z.top) * half
|
|
end
|
|
local x = CHIBI_RUN.from + (CHIBI_RUN.to - CHIBI_RUN.from) * run / (CHIBI_LIFE - CHIBI_HOP.length)
|
|
self:draw_image({ x = x, y = y })
|
|
end
|
|
|
|
-- Drumroll: notes fly up and to the right, two turns over the 2s flight, centred on Lumen's
|
|
-- spawn area (x 0-640, y 720-820)
|
|
local RENDA = { ms = { 2000 }, spin = 720, x = -55, x_range = 640, y = -55, y_range = 100 }
|
|
|
|
-- Background
|
|
|
|
local ToukenBackground = {}
|
|
ToukenBackground.__index = ToukenBackground
|
|
|
|
function ToukenBackground.new(player_num, bpm)
|
|
local self = setmetatable({}, ToukenBackground)
|
|
self.donbg = DonBGBase.new(1, player_num, COLLAB_PATH, TOUKEN_DONBG)
|
|
self.bg_normal = BGNormal.Base.new(0, COLLAB_PATH)
|
|
self.bg_fever = PetalFever.new(bpm, TOUKEN_FEVER)
|
|
-- no dodai: the courtyard is the stage. No fever of its own either: the game uses a default
|
|
self.footer = Footer.new(COLLAB_PATH, -1)
|
|
self.fever = Fever.create(math.random(0, 3), bpm, "background")
|
|
self.dancer = Dancer.BaseDancerGroup.new(0, bpm, MAX_DANCERS, COLLAB_PATH, TOUKEN_DANCERS)
|
|
self.dancer.spawn_positions = DANCER_SPAWN
|
|
local by_index = {}
|
|
for _, d in ipairs(self.dancer.dancers) do by_index[d.index] = d end
|
|
for i, index in ipairs(DANCER_JOIN_ORDER) do self.dancer.dancers[i] = by_index[index] end
|
|
self.renda = RendaController.new(COLLAB_PATH, 0, RENDA)
|
|
self.chibi = ChibiController.new(bpm, COLLAB_PATH, 1)
|
|
self.is_clear = false
|
|
self.is_rainbow = false
|
|
self.last_milestone = 1
|
|
return self
|
|
end
|
|
|
|
function ToukenBackground:update(current_ms, bpm)
|
|
self.donbg:update(current_ms, bpm)
|
|
self.bg_fever:update(current_ms, bpm)
|
|
self.fever:update(current_ms, bpm)
|
|
self.dancer:update(current_ms, bpm)
|
|
self.renda:update(current_ms)
|
|
self.chibi:update(current_ms, bpm)
|
|
end
|
|
|
|
function ToukenBackground:add_chibi(player_num)
|
|
BaseChibi.add(self.chibi, { key = CHIBI_KEYS[math.random(#CHIBI_KEYS)] }, COLLAB_PATH, player_num, ToukenChibi)
|
|
end
|
|
|
|
function ToukenBackground:handle_good(player_num) self:add_chibi(player_num) end
|
|
|
|
function ToukenBackground:handle_ok(player_num) self:add_chibi(player_num) end
|
|
|
|
function ToukenBackground:handle_bad(player_num) self.chibi:add_chibi(player_num, true) end
|
|
|
|
function ToukenBackground:handle_drumroll(player_num) self.renda:add_renda() end
|
|
|
|
function ToukenBackground:handle_balloon(player_num) self.renda:add_renda() end
|
|
|
|
function ToukenBackground:handle_gauge(player_num, progress, is_clear, is_rainbow)
|
|
self.donbg:set_clear(is_clear)
|
|
if is_clear and not self.is_clear then self.bg_fever:start() end
|
|
if not is_clear and self.is_clear then self.bg_fever:stop() end
|
|
if is_rainbow and not self.is_rainbow then self.fever:start() end
|
|
if not is_rainbow and self.is_rainbow then self.fever:stop() end
|
|
self.is_clear = is_clear
|
|
self.is_rainbow = is_rainbow
|
|
|
|
local current_milestone
|
|
if is_clear then
|
|
current_milestone = MAX_DANCERS
|
|
else
|
|
current_milestone = math.min(MAX_DANCERS - 1, math.floor(progress * MAX_DANCERS))
|
|
end
|
|
if current_milestone > self.last_milestone then
|
|
for _ = 1, current_milestone - self.last_milestone do self.dancer:add_dancer() end
|
|
self.last_milestone = current_milestone
|
|
elseif current_milestone < self.last_milestone then
|
|
for _ = 1, self.last_milestone - current_milestone do self.dancer:leave_dancer() end
|
|
self.last_milestone = current_milestone
|
|
end
|
|
end
|
|
|
|
function ToukenBackground:draw_back()
|
|
self.bg_normal:draw()
|
|
self.bg_fever:draw()
|
|
self.footer:draw()
|
|
self.donbg:draw()
|
|
self.renda:draw()
|
|
self.dancer:draw_slots(DANCER_CENTERS, DANCER_DRAW_ORDER)
|
|
self.fever:draw()
|
|
self.chibi:draw()
|
|
end
|
|
|
|
function ToukenBackground:draw_fore() end
|
|
|
|
function ToukenBackground:destroy()
|
|
self.donbg:destroy()
|
|
self.bg_normal:destroy()
|
|
self.bg_fever:destroy()
|
|
self.footer:destroy()
|
|
self.fever:destroy()
|
|
self.dancer:destroy()
|
|
self.renda:destroy()
|
|
self.chibi:destroy()
|
|
end
|
|
|
|
return ToukenBackground
|