634 lines
27 KiB
Lua
634 lines
27 KiB
Lua
local DonBGBase = require("background/bg_objects/donbg/base")
|
|
local Fever = require("background/bg_objects/fever")
|
|
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")
|
|
|
|
local pose_at, steps, concat = Timing.pose_at, Timing.steps, Timing.concat
|
|
|
|
-- Sachiko Kobayashi: a concert stage. Sachiko rises out of the floor and the dons rise onto
|
|
-- their platforms as the gauge fills; on clear the hall sinks away and she flies off into the
|
|
-- night sky on a crane, dressed as the queen.
|
|
local COLLAB_PATH = "background/collab/kobayashi"
|
|
local MAX_DANCERS = 5
|
|
|
|
-- Beats since the background was made, re-anchored when the bpm changes so it doesn't jump
|
|
local function new_clock(bpm) return { beats = 0, ms = 0, beat = 60000 / bpm } end
|
|
local function clock_update(c, current_time_ms, bpm)
|
|
if c.last then c.beats = c.beats + (current_time_ms - c.last) / c.beat end
|
|
c.last, c.ms = current_time_ms, current_time_ms
|
|
if bpm then c.beat = 60000 / bpm end
|
|
end
|
|
|
|
-- Runs fn with every draw moved down dy px (the hall sinking on clear)
|
|
local function shifted(dy, fn)
|
|
if dy == 0 then return fn() end
|
|
local orig = tex.draw_texture
|
|
tex.draw_texture = function(id, p)
|
|
p = p or {}
|
|
p.y = (p.y or 0) + dy
|
|
return orig(id, p)
|
|
end
|
|
local ok, err = pcall(fn)
|
|
tex.draw_texture = orig
|
|
if not ok then error(err, 0) end
|
|
end
|
|
|
|
-- 0 -> 1 -> 0 over a loop of `ms`
|
|
local function triangle(now, ms)
|
|
local k = (now % ms) / ms
|
|
return 1 - math.abs(1 - 2 * k)
|
|
end
|
|
|
|
-- Don band: the red strip (gold when cleared) scrolls one tile left every 14.67 s, the waves
|
|
-- over it two tiles and the clouds and cranes three, so the layers slide past at different
|
|
-- speeds. Ambient, in ms.
|
|
local DONBG = {
|
|
layers = {
|
|
{ key = "background", ms = 14667 },
|
|
{ key = "waves", ms = 14667, dx = -672 },
|
|
{ key = "sky", ms = 14667, dx = -1008 },
|
|
},
|
|
}
|
|
|
|
-- Stage: the concert hall, its right half mirrored, with a column of lamps either side whose
|
|
-- two sets of bulbs take turns, each fading up and down over 750 ms. Ambient, in ms.
|
|
local LAMP_MS = 750
|
|
|
|
local BGNormal = {}
|
|
BGNormal.__index = BGNormal
|
|
|
|
function BGNormal.new(clock)
|
|
local self = setmetatable({}, BGNormal)
|
|
self.name = "bg_0"
|
|
self.clock = clock
|
|
tex.load_folder(COLLAB_PATH, "bg_normal/" .. self.name)
|
|
self._id_bg = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/background") or 0
|
|
self._id_lamps = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/lamps") or 0
|
|
return self
|
|
end
|
|
|
|
function BGNormal:draw()
|
|
tex.draw_texture(self._id_bg)
|
|
tex.draw_texture(self._id_bg, { index = 1, mirror = "horizontal" })
|
|
local a = triangle(self.clock.ms, LAMP_MS)
|
|
for i = 0, 1 do
|
|
local mirror = i == 1 and "horizontal" or nil
|
|
-- frames 0, 1: the two sets of bulbs; 2, 3: their glow
|
|
for frame = 0, 3 do
|
|
tex.draw_texture(self._id_lamps, { index = i, frame = frame, fade = frame % 2 == 0 and a or 1 - a,
|
|
mirror = mirror, blend = frame >= 2 and "additive" or nil })
|
|
end
|
|
end
|
|
end
|
|
|
|
function BGNormal:destroy() tex.unload_folder(COLLAB_PATH, "bg_normal/" .. self.name) end
|
|
|
|
-- Dancers: Sachiko in the middle and four dons, each on a slot of the stage. Texture.json
|
|
-- positions are relative to the slot's centre; a layer scales and squashes about its
|
|
-- `origin` (default the image's centre). Lumen lets the game ask for 1, 3 or 5 dancers only,
|
|
-- so the dons join and leave in pairs. Timed in beats; the dance routines count a beat per
|
|
-- Lumen label (22.5 frames).
|
|
local SLOTS = { 160, 360, 640, 920, 1120 }
|
|
local DRAW_ORDER = { 5, 1, 4, 2, 3 } -- slots back to front
|
|
local DANCERS = { 1, 3, 3, 5, 5 } -- dancers on stage for 0..4 milestones
|
|
|
|
local function layer(key, poses, opts)
|
|
poses.key = key
|
|
for k, v in pairs(opts or {}) do poses[k] = v end
|
|
return poses
|
|
end
|
|
local function still(key, frame, opts) return layer(key, { { 0, frame or 0, 0, 0 } }, opts) end
|
|
-- hidden until `at`, then fades in over `beats`
|
|
local function fade_in(key, at, beats, opts, frame)
|
|
frame = frame or 0
|
|
return layer(key, { { 0, frame, 0, 0, hide = true }, { at, frame, alpha = 0, to = { 0, 0, alpha = 1 } },
|
|
{ at + beats, frame } }, opts)
|
|
end
|
|
local ADD = { blend = "additive" }
|
|
|
|
-- A don rises from below the stage onto its platform as the light comes on, overshoots and
|
|
-- settles with a squash; it leaves with a hop and drops back down as the light goes out.
|
|
local DON_ORIGIN = { 100, 184 } -- squashes about its feet
|
|
local DON_ENTER = {
|
|
beats = 2,
|
|
fade_in("floor", 0, 0.175),
|
|
fade_in("beam", 0, 0.175, ADD),
|
|
layer("ring", {
|
|
{ 0, 0, 0, 0, scale = 0.1, alpha = 0, to = { 0, 0, "out", scale = 0.9, alpha = 0.5 } },
|
|
{ 0.55, 0, scale = 0.9, alpha = 0.5, to = { 0, 0, scale = 1, alpha = 1 } }, { 0.85, 0 },
|
|
}),
|
|
layer("start", {
|
|
{ 0, 0, 0, 220, to = { nil, -44, "out" } }, { 0.575, 0, to = { nil, 0, "in" } },
|
|
{ 0.825, 0, to = { nil, 0, "out", sy = 0.95 } }, { 1.075, 0, sy = 0.95, to = { nil, 0, "in", sy = 1 } },
|
|
{ 1.325, 0 },
|
|
}, { origin = DON_ORIGIN }),
|
|
}
|
|
local function fade_out(key, at, beats, opts)
|
|
return layer(key, { { 0, 0, 0, 0 }, { at, 0, to = { 0, 0, alpha = 0 } }, { at + beats, 0, hide = true } }, opts)
|
|
end
|
|
local DON_LEAVE = {
|
|
beats = 1.25,
|
|
fade_out("floor", 1.075, 0.175),
|
|
fade_out("beam", 1.075, 0.175, ADD),
|
|
layer("ring", {
|
|
{ 0, 0, 0, 0, to = { 0, 0, "out", scale = 0.8, alpha = 0.5 } },
|
|
{ 0.6, 0, scale = 0.8, alpha = 0.5, to = { 0, 0, "in", scale = 0.1, alpha = 0 } }, { 1.25, 0, hide = true },
|
|
}),
|
|
layer("start", { { 0, 1, 0, 0, to = { nil, -44, "out" } }, { 0.525, 1, to = { nil, 220, "in" } } },
|
|
{ origin = DON_ORIGIN }),
|
|
}
|
|
-- The dons dance two bars: every beat a pose held most of the beat and a quick accent, the
|
|
-- first bar facing one way and the second the other; the shadow under them follows the pose
|
|
local function bar(first)
|
|
return { { first, 0.625 }, { first + 1, 0.375 }, { first + 2, 0.625 }, { first + 3, 0.375 } }
|
|
end
|
|
local function don_routine()
|
|
local poses = steps(bar(0), bar(0), bar(4), bar(4))
|
|
poses[1][3], poses[1][4] = 0, 0
|
|
return poses
|
|
end
|
|
local function don_dance()
|
|
return { beats = 8, still("floor"), still("beam", 0, ADD), layer("shadow", don_routine()),
|
|
layer("loop", don_routine()) }
|
|
end
|
|
|
|
-- Sachiko rises steadily through a trapdoor on a lift over 2.6 beats with her silhouette over her,
|
|
-- overshoots, settles, and the lights come up (lit lift and lip, floor light, beam); then she
|
|
-- opens her eyes. She sings with her hands together for a bar and a half, then waves.
|
|
local function rise(frame, alpha)
|
|
return { { 0, frame, 0, 323, alpha = alpha, to = { nil, -6 } },
|
|
{ 2.6, frame, alpha = alpha, to = { nil, 0, "in" } }, { 2.725, frame, alpha = alpha } }
|
|
end
|
|
local SACHI = {
|
|
enter = {
|
|
beats = 4.5,
|
|
still("_strip"),
|
|
fade_in("_floor", 2.725, 0.2),
|
|
still("_hole"),
|
|
layer("_lift", rise(0)),
|
|
fade_in("_lift", 2.725, 0.2, nil, 1),
|
|
fade_in("_beam", 2.725, 0.2, ADD),
|
|
layer("start", concat(rise(0), { { 2.95, 1 } })),
|
|
layer("_silhouette", concat(rise(0, 0.6), { { 2.75, 0, alpha = 0.6, to = { nil, nil, alpha = 0 } },
|
|
{ 2.95, 0, hide = true } })),
|
|
still("_lip"),
|
|
fade_in("_lip", 2.725, 0.2, nil, 1),
|
|
layer("_feet", { { 0, 0, 0, 0, hide = true }, { 2.725, 0 } }),
|
|
},
|
|
dance = {
|
|
beats = 4,
|
|
still("_floor"), still("_hole"), still("_lift", 1), still("_beam", 0, ADD),
|
|
layer("loop", steps({ { 0, 1.5, x = 0, y = 0 }, { 1, 0.25 }, { 2, 0.25 }, { 3, 1.5 }, { 4, 0.25 }, { 5, 0.25 } })),
|
|
still("_lip", 1),
|
|
layer("_feet", steps({ { 1, 1.5, x = 0, y = 0 }, { 2, 0.25 }, { 3, 0.25 }, { 4, 1.5 }, { 5, 0.25 }, { 6, 0.25 } })),
|
|
},
|
|
}
|
|
|
|
local SPECS = { [0] = SACHI }
|
|
for i = 1, 4 do SPECS[i] = { enter = DON_ENTER, dance = don_dance(), leave = DON_LEAVE } end
|
|
|
|
-- draws the current part's layers with the slot's centre at x (only the keys in `only`, if
|
|
-- given); beams move `beam_dy` px further (they hang from the rig, which sinks faster)
|
|
local function kobayashi_draw(self, x, beam_dy, only)
|
|
if not self.state then return end
|
|
local t = self:part_time()
|
|
for _, l in ipairs(self.parts[self.state]) do
|
|
local pose = pose_at(l, t)
|
|
if pose and (not only or only[l.key]) then
|
|
local tx = self:texture(pose.key or l.key)
|
|
local info = tx.info
|
|
if info and info.width then
|
|
local w, h = info.width, info.height
|
|
local o = l.origin or { w / 2, h / 2 }
|
|
local sx, sy = pose.sx, pose.sy
|
|
tex.draw_texture(tx.id, {
|
|
frame = pose.frame,
|
|
x = x + (pose.x or 0) + o[1],
|
|
y = (pose.y or 0) + o[2] + (l.key:find("beam") and beam_dy or 0),
|
|
x2 = w * (sx - 1),
|
|
y2 = h * (sy - 1),
|
|
origin = { o[1] * sx, o[2] * sy },
|
|
fade = pose.alpha,
|
|
blend = (pose.blend == nil and l.blend or pose.blend) or nil,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Clear: Lumen's normal_fever / fever_normal. Timed in beats from gaining or losing clear.
|
|
-- In: the lift pops Sachiko up out of the floor by 0.75 beats (a don holds it up from below),
|
|
-- then the whole hall (stage, dancers and rig) sinks 832px over 2.2 beats while she stays
|
|
-- put and the stars and the Earth's rim rise behind it. Lanterns drop in from 2 beats and
|
|
-- rays fade in from 2.5. A crane flies up behind her at 1.8, a white flash at 2.7 hides her
|
|
-- turning into the queen, the crane spreads its wings, and from 3.25 the queen rides it,
|
|
-- bobbing over a 4-beat loop under twinkling stars.
|
|
-- Out: the crane flaps and flies off, the queen fades back into Sachiko on the lift, the hall
|
|
-- comes back up over 1.45 beats and the lanterns lift away.
|
|
-- Groups slide into place (progress 0 -> 1) `at` beats after the clear over `beats`, and
|
|
-- slide back out linearly over `back`, from wherever they are. `ease` is how strongly they
|
|
-- slow down (Lumen's ease-out strength: 1 is quadratic, 0 linear).
|
|
local GROUPS = {
|
|
stage = { at = 0.75, beats = 2.225, ease = 0.8, back = 1.45 },
|
|
stars = { at = 0.75, beats = 2.225, ease = 0.5, back = 1.45 },
|
|
earth = { at = 0.75, beats = 2.225, back = 1.45 },
|
|
lanterns = { at = 1.975, beats = 1, ease = 0.4, back = 0.475 },
|
|
rays = { at = 2.5, beats = 0.675, back = 0.3 },
|
|
}
|
|
local STAGE_DROP, STARS_RISE, EARTH_RISE, GLOW_DROP = 832, 120, 196, 100
|
|
-- the rig sinks faster than the stage (parallax): the sign and truss 877px, the lighting bar
|
|
-- and its beams 924px
|
|
local TRUSS_EXTRA, RIG_EXTRA = 45 / STAGE_DROP, 92 / STAGE_DROP
|
|
local LANTERN_DROP = { 380, 380, 394, 394 } -- by lanterns/glows index (back row lower)
|
|
local GLOW_MS = 1000 -- lantern glows dim to 30% and back; rays swap
|
|
local OUT_BEATS = 1.725
|
|
|
|
-- the lift (and Sachiko on it) pops up 71px and sinks back as the hall drops; the don under
|
|
-- it pushes up 31px and stays
|
|
local LIFT_IN = {
|
|
{ 0, 0, 0, 0, to = { nil, 4, "out" } }, { 0.1, 0, to = { nil, -71 } }, { 0.775, 0, to = { nil, 0, "in" } },
|
|
{ 2.9, 0 }, { 2.925, 0, hide = true },
|
|
}
|
|
local HELPER_IN = { { 0, 0, 0, 0, to = { nil, 4, "out" } }, { 0.1, 0, to = { nil, -31 } }, { 0.75, 0 } }
|
|
-- her shadow fades as she lifts off and goes down with the hall
|
|
local FEET_IN = {
|
|
{ 0, 0, 0, 0 }, { 0.225, 0, to = { 0, 0, alpha = 0.4 } }, { 0.75, 0, alpha = 0.4, to = { 0, 0, "out", alpha = 0 } },
|
|
{ 2.975, 0, hide = true },
|
|
}
|
|
-- the crane: rises 237px, then spreads its wings (body and wings share frames)
|
|
local CRANE_IN = {
|
|
{ 0, 0, 0, 0, hide = true }, { 1.825, 0, 0, 237, to = { 0, 0, "out" } }, { 2.4, 0 }, { 2.55, 1 }, { 2.7, 2 },
|
|
{ 2.825, 3 }, { 3.25, 0, hide = true },
|
|
}
|
|
local FLASH = {
|
|
{ 0, 0, 0, 0, hide = true }, { 2.675, 0, alpha = 0, to = { 0, 0, alpha = 1 } }, { 2.9, 0 },
|
|
{ 3.05, 0, to = { 0, 0, alpha = 0 } }, { 3.225, 0, hide = true },
|
|
}
|
|
local QUEEN_AT, RIDE_AT = 2.925, 3.25
|
|
-- riding: the queen's poses and the crane's flaps over 4 beats, all bobbing together
|
|
local QUEEN_DANCE = steps({ { 0, 1.4, x = 0, y = 0 }, { 1, 0.3 }, { 2, 0.3 }, { 3, 1.7 }, { 4, 0.3 } })
|
|
local FLAP = 3.5 / 6
|
|
local CRANE_DANCE = steps({ { 0, 0.5, x = 0, y = 0 }, { 1, FLAP }, { 2, FLAP }, { 3, FLAP }, { 4, FLAP }, { 5, FLAP },
|
|
{ 6, FLAP } })
|
|
local BOB = { { 0, 0, 0, 0, to = { 0, 4 } }, { 0.75, 0, to = { 0, -16 } }, { 2.75, 0, to = { 0, 0 } }, length = 4 }
|
|
-- twinkles round the queen in the same 4-beat loop (90 Lumen frames): { frame, x, y, size }
|
|
-- from her centre; a star grows from 11% to 70% over 14 frames, then flares to full size and
|
|
-- fades in 6
|
|
local SPARKLE_LOOP, SPARKLE_LIFE = 90, 20
|
|
local SPARKLES = {
|
|
{ 0, -291, -86, 0.6 }, { 0, 291, -86, 0.6 }, { 7, -318, 91, 0.6 }, { 7, 318, 91, 0.6 },
|
|
{ 16, -543, -51, 0.6 }, { 16, 446, -118, 0.6 }, { 23, -385, -26, 0.6 }, { 23, 385, -26, 0.6 },
|
|
{ 25, -543, 99, 0.6 }, { 25, 446, 152, 0.6 }, { 28, 0, -116, 1 }, { 40, -157, -16, 1 }, { 40, 180, -16, 1 },
|
|
{ 40, -385, -26, 0.6 }, { 40, 385, -26, 0.6 }, { 45, -200, 136, 0.6 }, { 45, 200, 136, 0.6 },
|
|
{ 47, -543, -51, 0.6 }, { 47, 446, -118, 0.6 }, { 48, -244, 45, 1 }, { 48, 244, 45, 1 },
|
|
{ 53, -318, 91, 0.6 }, { 53, 318, 91, 0.6 }, { 57, -460, 105, 1 }, { 57, 350, 38, 1 },
|
|
{ 61, -446, -118, 0.6 }, { 61, 543, -51, 0.6 }, { 66, -385, -105, 0.6 }, { 66, 385, -105, 0.6 },
|
|
{ 68, -446, 152, 0.6 }, { 68, 543, 99, 0.6 }, { 69, -350, 38, 1 }, { 69, 460, 105, 1 },
|
|
}
|
|
local SPARKLE_HALF = 32
|
|
|
|
-- Out, from losing clear: the queen holds, then fades; Sachiko is back from 0.55 and gives a
|
|
-- little bob as the lift settles
|
|
local QUEEN_OUT = { { 0, 0, 0, 0 }, { 0.575, 0, to = { 0, 0, alpha = 0 } }, { 0.7, 0, hide = true } }
|
|
local SACHI_OUT = {
|
|
{ 0, 1, 0, 0, hide = true }, { 0.55, 1 }, { 1.45, 1, to = { nil, 3 } }, { 1.6, 0, y = 4, to = { nil, 1 } },
|
|
{ 1.7, 1, y = 0 },
|
|
}
|
|
local LIFT_OUT = { { 0, 0, 0, 0 }, { 1.325, 0, to = { nil, 4 } }, { 1.6, 0, to = { nil, 0 } }, { 1.7, 0 } }
|
|
local HELPER_OUT = { { 0, 0, 0, -31 }, { 1.3, 0, y = 70, to = { nil, 3 } }, { 1.5, 0 } }
|
|
-- the crane flaps twice and flies off up the screen
|
|
local CRANE_OUT = {
|
|
{ 0, 0, 0, 14 }, { 0.125, 1 }, { 0.25, 2 }, { 0.375, 3 }, { 0.7, 2, 0, 0 }, { 0.825, 1, 0, -22 },
|
|
{ 0.95, 0, 0, -62, to = { 0, -435 } }, { 1.3, 0, hide = true },
|
|
}
|
|
|
|
local function tween_value(tw, t)
|
|
if not tw then return 0 end
|
|
local k = math.max(0, math.min(1, (t - tw.at) / tw.beats))
|
|
k = k + (tw.ease or 0) * k * (1 - k)
|
|
return tw.from + (tw.to - tw.from) * k
|
|
end
|
|
|
|
local BGFever = {}
|
|
BGFever.__index = BGFever
|
|
|
|
function BGFever.new(clock)
|
|
local self = setmetatable({}, BGFever)
|
|
self.name = "bg_fever_0"
|
|
self.clock = clock
|
|
tex.load_folder(COLLAB_PATH, "bg_fever/" .. self.name)
|
|
self.ids = {}
|
|
for _, key in ipairs({ "backlight", "truss", "sign", "rig", "stars", "ground", "rim", "earth_glow",
|
|
"lantern_glow", "lanterns", "glows", "rays", "shaft", "column", "helper", "crane", "crane_dance", "wing",
|
|
"queen", "flash", "sparkle" }) do
|
|
self.ids[key] = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/" .. key) or 0
|
|
end
|
|
self.groups = {}
|
|
return self
|
|
end
|
|
|
|
-- Sachiko and her lift are the centre dancer's textures (dancer folder, loaded by the group)
|
|
function BGFever:dancer_textures()
|
|
if self.sachi then return self.sachi end
|
|
self.sachi = {}
|
|
for _, key in ipairs({ "0_start", "0_hole", "0_lift", "0_lip", "0_feet" }) do
|
|
self.sachi[key] = tex.get_texture(COLLAB_PATH .. "/dancer_0/" .. key) or 0
|
|
end
|
|
return self.sachi
|
|
end
|
|
|
|
function BGFever:progress(name) return tween_value(self.groups[name], self.clock.beats) end
|
|
|
|
function BGFever:start()
|
|
local now = self.clock.beats
|
|
for name, g in pairs(GROUPS) do
|
|
self.groups[name] = { from = self:progress(name), to = 1, at = now + g.at, beats = g.beats, ease = g.ease }
|
|
end
|
|
self.state, self.t0 = "in", now
|
|
end
|
|
|
|
function BGFever:stop()
|
|
if not self.state then return end
|
|
local now = self.clock.beats
|
|
for name, g in pairs(GROUPS) do
|
|
self.groups[name] = { from = self:progress(name), to = 0, at = now, beats = g.back }
|
|
end
|
|
self.state, self.t0 = "out", now
|
|
end
|
|
|
|
function BGFever:update()
|
|
if self.state == "out" and self.clock.beats - self.t0 >= OUT_BEATS then self.state = nil end
|
|
end
|
|
|
|
function BGFever:active() return self.state ~= nil end
|
|
|
|
-- how far the hall has sunk, px
|
|
function BGFever:camera()
|
|
return self.state and math.floor(STAGE_DROP * self:progress("stage")) or 0
|
|
end
|
|
|
|
-- behind the hall: the night sky, the Earth's rim, the lanterns and the rays
|
|
function BGFever:draw_back()
|
|
if not self.state then return end
|
|
local ids, now = self.ids, self.clock.ms
|
|
local stars, earth = self:progress("stars"), self:progress("earth")
|
|
local lanterns, rays = self:progress("lanterns"), self:progress("rays")
|
|
tex.draw_texture(ids.stars, { y = STARS_RISE * stars })
|
|
tex.draw_texture(ids.ground, { y = EARTH_RISE * earth })
|
|
tex.draw_texture(ids.rim, { y = EARTH_RISE * earth })
|
|
for i = 0, 1 do
|
|
tex.draw_texture(ids.earth_glow, { index = i, y = EARTH_RISE * earth, blend = "additive",
|
|
mirror = i == 1 and "horizontal" or nil })
|
|
end
|
|
if lanterns > 0 then
|
|
tex.draw_texture(ids.lantern_glow, { y = GLOW_DROP * lanterns, fade = lanterns, blend = "additive" })
|
|
end
|
|
if rays > 0 then
|
|
local a = triangle(now, GLOW_MS)
|
|
tex.draw_texture(ids.rays, { frame = 0, fade = rays * a, blend = "additive" })
|
|
tex.draw_texture(ids.rays, { frame = 1, fade = rays * (1 - a), blend = "additive" })
|
|
end
|
|
if lanterns <= 0 then return end
|
|
local glow = 0.3 + 0.7 * (1 - triangle(now, GLOW_MS))
|
|
for row = 0, 1 do
|
|
for side = 0, 1 do
|
|
local i = row * 2 + side
|
|
tex.draw_texture(ids.lanterns, { index = i, y = -LANTERN_DROP[i + 1] * (1 - lanterns),
|
|
mirror = side == 1 and "horizontal" or nil })
|
|
end
|
|
for side = 0, 1 do
|
|
local i = row * 2 + side
|
|
tex.draw_texture(ids.glows, { index = i, y = -LANTERN_DROP[i + 1] * (1 - lanterns), fade = glow,
|
|
blend = "additive", mirror = side == 1 and "horizontal" or nil })
|
|
end
|
|
end
|
|
end
|
|
|
|
-- the rig above the stage, normally hidden under the lane: the sign with its backlight and
|
|
-- the truss either side (under the dancers' beams), and the lighting bar (over them)
|
|
function BGFever:draw_stage(cam)
|
|
if not self.state then return end
|
|
local ids, y = self.ids, cam * TRUSS_EXTRA
|
|
tex.draw_texture(ids.backlight, { y = y })
|
|
for i = 0, 1 do
|
|
tex.draw_texture(ids.truss, { index = i, y = y, mirror = i == 1 and "horizontal" or nil })
|
|
tex.draw_texture(ids.sign, { index = i, y = y })
|
|
end
|
|
end
|
|
|
|
function BGFever:draw_rig(cam)
|
|
if not self.state then return end
|
|
for i = 0, 1 do
|
|
tex.draw_texture(self.ids.rig, { index = i, y = cam * RIG_EXTRA, mirror = i == 1 and "horizontal" or nil })
|
|
end
|
|
end
|
|
|
|
local function draw_crane(ids, pose, dy)
|
|
if not pose then return end
|
|
tex.draw_texture(ids.crane, { frame = pose.frame, y = pose.y + dy })
|
|
for i = 0, 1 do
|
|
tex.draw_texture(ids.wing, { index = i, frame = pose.frame, y = pose.y + dy,
|
|
mirror = i == 1 and "horizontal" or nil })
|
|
end
|
|
end
|
|
|
|
function BGFever:draw_sparkles(t)
|
|
local f = (t * 22.5) % SPARKLE_LOOP
|
|
for _, s in ipairs(SPARKLES) do
|
|
local u = (f - s[1]) % SPARKLE_LOOP
|
|
if u < SPARKLE_LIFE then
|
|
local size, alpha = 0.109 + 0.591 * u / 13, 1
|
|
if u >= 14 then
|
|
local k = (u - 14) / 5
|
|
size, alpha = 0.75 + 0.25 * k, 0.832 * (1 - k)
|
|
end
|
|
size = size * s[4]
|
|
local o = SPARKLE_HALF * size
|
|
tex.draw_texture(self.ids.sparkle, {
|
|
x = s[2] + SPARKLE_HALF, y = s[3] + SPARKLE_HALF, x2 = 2 * SPARKLE_HALF * (size - 1),
|
|
y2 = 2 * SPARKLE_HALF * (size - 1), origin = { o, o }, fade = alpha, blend = "additive",
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
-- in front of the hall: Sachiko on her lift, the queen, the crane, the flash and the twinkles.
|
|
-- The trapdoor, the light column and her shadow go down with the hall (cam); the lift, the
|
|
-- don holding it and Sachiko stay put.
|
|
function BGFever:draw_front(cam)
|
|
if not self.state then return end
|
|
local ids, sachi = self.ids, self:dancer_textures()
|
|
local t = self.clock.beats - self.t0
|
|
local out = self.state == "out"
|
|
local lift = pose_at(out and LIFT_OUT or LIFT_IN, t)
|
|
local helper = pose_at(out and HELPER_OUT or HELPER_IN, t)
|
|
local feet = out and { alpha = 1 - self:progress("stage") } or pose_at(FEET_IN, t)
|
|
tex.draw_texture(sachi["0_hole"], { x = 640, y = cam })
|
|
if lift then tex.draw_texture(ids.shaft, { y = lift.y }) end
|
|
tex.draw_texture(ids.helper, { y = helper.y })
|
|
tex.draw_texture(ids.column, { y = cam })
|
|
if lift then tex.draw_texture(sachi["0_lift"], { x = 640, frame = 1, y = lift.y }) end
|
|
tex.draw_texture(sachi["0_lip"], { x = 640, frame = 1, y = cam })
|
|
if feet then tex.draw_texture(sachi["0_feet"], { x = 640, y = cam, fade = feet.alpha }) end
|
|
|
|
if out then
|
|
local p = pose_at(SACHI_OUT, t)
|
|
if p then tex.draw_texture(sachi["0_start"], { x = 640, frame = p.frame, y = p.y }) end
|
|
p = pose_at(QUEEN_OUT, t)
|
|
if p then tex.draw_texture(ids.queen, { fade = p.alpha }) end
|
|
draw_crane(ids, pose_at(CRANE_OUT, t), 0)
|
|
return
|
|
end
|
|
if t < QUEEN_AT then
|
|
tex.draw_texture(sachi["0_start"], { x = 640, y = lift and lift.y or 0 })
|
|
elseif t < RIDE_AT then
|
|
tex.draw_texture(ids.queen)
|
|
end
|
|
local flash = pose_at(FLASH, t)
|
|
if flash then tex.draw_texture(ids.flash, { fade = flash.alpha }) end
|
|
draw_crane(ids, pose_at(CRANE_IN, t), 0)
|
|
if t < RIDE_AT then return end
|
|
local u = t - RIDE_AT
|
|
local bob = pose_at(BOB, u % BOB.length).y
|
|
tex.draw_texture(ids.queen, { frame = pose_at(QUEEN_DANCE, u % QUEEN_DANCE.length).frame, y = bob })
|
|
local flap = pose_at(CRANE_DANCE, u % CRANE_DANCE.length).frame
|
|
tex.draw_texture(ids.crane_dance, { frame = flap, y = bob })
|
|
for i = 0, 1 do
|
|
tex.draw_texture(ids.wing, { index = i, frame = flap + 4, y = bob, mirror = i == 1 and "horizontal" or nil })
|
|
end
|
|
self:draw_sparkles(u)
|
|
end
|
|
|
|
function BGFever:destroy() tex.unload_folder(COLLAB_PATH, "bg_fever/" .. self.name) end
|
|
|
|
-- Chibis for good hits: さ or あ hops in from behind the lane and runs across, jumping every
|
|
-- beat: it ducks back and squashes, then springs up 59px and drops back
|
|
local CHIBI_MOTION = {
|
|
key = "1",
|
|
hop = { from = { -418, 21 }, peak = { -350, -15 }, up = 0.225 },
|
|
run = { y = 38 },
|
|
period = 1,
|
|
poses = {
|
|
{ 0, 0, 0, 0, sy = { 1, 0.9 }, to = { -31, 4, "out" } },
|
|
{ 0.125, 0, -31, 4, sy = { 0.9, 1.1 }, to = { -56, -4, "in" } },
|
|
{ 0.225, 0, -56, -4, sy = { 1.1, 0.9 }, to = { -27, -59, "out" } },
|
|
{ 0.625, 0, -27, -59, sy = { 0.9, 1 }, to = { 0, 0, "in" } },
|
|
},
|
|
}
|
|
local CHIBIS = {
|
|
setmetatable({ first = 0 }, { __index = CHIBI_MOTION }),
|
|
setmetatable({ first = 1 }, { __index = CHIBI_MOTION }),
|
|
}
|
|
|
|
-- Drumroll: the default flight up-right, one flight time per fan
|
|
local RENDA = { kinds = { { 0, 1733 }, { 1, 2000 } } }
|
|
|
|
-- Background
|
|
|
|
local KobayashiBackground = {}
|
|
KobayashiBackground.__index = KobayashiBackground
|
|
|
|
function KobayashiBackground.new(player_num, bpm)
|
|
local self = setmetatable({}, KobayashiBackground)
|
|
self.clock = new_clock(bpm)
|
|
self.donbg = DonBGBase.new(1, player_num, COLLAB_PATH, DONBG)
|
|
self.bg_normal = BGNormal.new(self.clock)
|
|
self.bg_fever = BGFever.new(self.clock)
|
|
self.fever = Fever.new(bpm, COLLAB_PATH)
|
|
local dancer_class = Dancer.define(SPECS)
|
|
dancer_class.draw = kobayashi_draw
|
|
self.dancer = Dancer.BaseDancerGroup.new(0, bpm, MAX_DANCERS, COLLAB_PATH, dancer_class)
|
|
table.sort(self.dancer.dancers, function(a, b) return a.index < b.index end)
|
|
self.dancer.needs_initial_dancer = false
|
|
self.dancer:add_dancer() -- Sachiko is always on stage
|
|
self.renda = RendaController.new(COLLAB_PATH, 0, RENDA)
|
|
self.chibi = ChibiController.new(bpm, COLLAB_PATH, 1)
|
|
self.is_clear = false
|
|
self.is_rainbow = false
|
|
return self
|
|
end
|
|
|
|
function KobayashiBackground:update(current_ms, bpm)
|
|
clock_update(self.clock, current_ms, bpm)
|
|
self.donbg:update(current_ms, bpm)
|
|
self.bg_fever:update()
|
|
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 KobayashiBackground:handle_good(player_num)
|
|
BaseChibi.add(self.chibi, CHIBIS[math.random(#CHIBIS)], COLLAB_PATH, player_num)
|
|
end
|
|
|
|
function KobayashiBackground:handle_ok(player_num) self:handle_good(player_num) end
|
|
|
|
function KobayashiBackground:handle_bad(player_num) self.chibi:add_chibi(player_num, true) end
|
|
|
|
function KobayashiBackground:handle_drumroll(player_num) self.renda:add_renda() end
|
|
|
|
function KobayashiBackground:handle_balloon(player_num) self.renda:add_renda() end
|
|
|
|
function KobayashiBackground: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 group = self.dancer
|
|
local want = is_clear and MAX_DANCERS or DANCERS[math.min(MAX_DANCERS - 1, math.floor(progress * MAX_DANCERS)) + 1]
|
|
while group.active_count < want do group:add_dancer() end
|
|
while group.active_count > want do group:leave_dancer() end
|
|
end
|
|
|
|
-- while clearing, the clear draws Sachiko and her lift; her floor light and beam stay
|
|
local SACHI_STAGE = { _floor = true, _beam = true }
|
|
|
|
function KobayashiBackground:draw_dancers(cam)
|
|
local fever = self.bg_fever:active()
|
|
for _, slot in ipairs(DRAW_ORDER) do
|
|
local d = self.dancer.active_dancers[slot]
|
|
if d then d:draw(SLOTS[slot], cam * RIG_EXTRA, fever and d.index == 0 and SACHI_STAGE or nil) end
|
|
end
|
|
end
|
|
|
|
function KobayashiBackground:draw_back()
|
|
local bg_fever = self.bg_fever
|
|
bg_fever:draw_back()
|
|
local cam = bg_fever:camera()
|
|
if cam < STAGE_DROP then
|
|
shifted(cam, function()
|
|
self.bg_normal:draw()
|
|
bg_fever:draw_stage(cam)
|
|
self:draw_dancers(cam)
|
|
bg_fever:draw_rig(cam)
|
|
end)
|
|
end
|
|
bg_fever:draw_front(cam)
|
|
self.donbg:draw()
|
|
self.renda:draw()
|
|
self.fever:draw()
|
|
self.chibi:draw()
|
|
end
|
|
|
|
function KobayashiBackground:draw_fore() end
|
|
|
|
function KobayashiBackground:destroy()
|
|
self.donbg:destroy()
|
|
self.bg_normal:destroy()
|
|
self.bg_fever:destroy()
|
|
self.fever:destroy()
|
|
self.dancer:destroy()
|
|
self.renda:destroy()
|
|
self.chibi:destroy()
|
|
end
|
|
|
|
return KobayashiBackground
|