425 lines
15 KiB
Lua
425 lines
15 KiB
Lua
local RendaController = require("background/bg_objects/renda")
|
|
|
|
local COLLAB = "background/collab/"
|
|
local S = tex.get_screen_scale()
|
|
|
|
local RISE_MS, FALL_MS = 233, 150
|
|
|
|
local A, B = "hop_late", "hop_early"
|
|
|
|
-- x comes from texture.json: the dancer's hop and whether the second prop is a mirror image
|
|
local function row(...)
|
|
local out = {}
|
|
for i, d in ipairs({ ... }) do
|
|
out[i] = { key = "dancer_" .. i, prop = "dancer_" .. i .. "_prop", hop = d[1], props = d[2], mirror_prop = d[3] }
|
|
end
|
|
return out
|
|
end
|
|
|
|
local PACKS = {
|
|
taiko = { renda = { dx = 915, dy = -1006 } },
|
|
i7id7 = {
|
|
slides = 7,
|
|
renda = { dx = 1560, dy = -1648 },
|
|
dancers = row({ A, 2 }, { B, 2 }, { B, 2, true }, { B, 1 }, { A, 2 }, { B, 1 }, { A, 1 }),
|
|
},
|
|
i7tri = {
|
|
slides = 3,
|
|
renda = { dx = 1560, dy = -1648 },
|
|
dancers = row({ A, 1 }, { B, 1 }, { B, 2, true }),
|
|
},
|
|
i7rev = {
|
|
slides = 2,
|
|
renda = { dx = 1560, dy = -1648 },
|
|
dancers = row({ B, 1 }, { A, 1 }),
|
|
},
|
|
i7natsu = {
|
|
slides = 10,
|
|
renda = { dx = 1560, dy = -1648 },
|
|
dancers = row({ A, 2 }, { B, 2 }, { B, 2, true }, { B, 1 }, { A, 2 }, { B, 1 }, { A, 1 }, { A, 1 }, { B, 1 },
|
|
{ A, 2, true }),
|
|
},
|
|
a3 = { street = true, renda = { dx = 915, dy = -1006 } },
|
|
}
|
|
|
|
local function clamp01(t) return math.max(0, math.min(1, t)) end
|
|
local function lerp(a, b, t) return a + (b - a) * t end
|
|
|
|
-- 0 -> 1 -> 0 over one cycle
|
|
local function arc(t)
|
|
t = t % 1
|
|
return t < 0.5 and t * 2 or (1 - t) * 2
|
|
end
|
|
|
|
local Waiwai = {}
|
|
Waiwai.__index = Waiwai
|
|
|
|
local function pack_path(name)
|
|
return COLLAB .. (name == "taiko" and "waiwai" or ("waiwai_" .. name))
|
|
end
|
|
|
|
function Waiwai.create(name, bpm)
|
|
local self = setmetatable({}, Waiwai)
|
|
self.spec = PACKS[name]
|
|
self.path = pack_path(name)
|
|
|
|
self.base_path = self.spec.street and self.path or pack_path("taiko")
|
|
self.bpm = bpm
|
|
self.beat = 0
|
|
self.last_ms = nil
|
|
self.progress = { 0, 0 }
|
|
self.level = 0
|
|
self.second_a = 0
|
|
self.fever_a = 0
|
|
self.ids = {}
|
|
tex.load_folder(self.base_path, "stage")
|
|
if self.path ~= self.base_path then tex.load_folder(self.path, "stage") end
|
|
self.renda = RendaController.new(self.path, 0, {
|
|
ms = { 2000 }, dx = self.spec.renda.dx, dy = self.spec.renda.dy, x_range = 500, y_range = 20,
|
|
})
|
|
return self
|
|
end
|
|
|
|
function Waiwai.new(_, bpm) return Waiwai.create("taiko", bpm) end
|
|
|
|
-- A collab constructor for Background's table
|
|
function Waiwai.pack(name)
|
|
return function(_, bpm) return Waiwai.create(name, bpm) end
|
|
end
|
|
|
|
function Waiwai:id(key, own)
|
|
local t = self.ids[key]
|
|
if not t then
|
|
t = tex.get_texture((own and self.path or self.base_path) .. "/stage/" .. key)
|
|
self.ids[key] = t
|
|
end
|
|
return t
|
|
end
|
|
|
|
function Waiwai:draw(key, fade, frame)
|
|
if fade <= 0 then return end
|
|
tex.draw_texture(self:id(key), { fade = fade, frame = frame })
|
|
end
|
|
|
|
-- both sides of a mirrored pair
|
|
function Waiwai:draw_pair(key, fade, frame)
|
|
if fade <= 0 then return end
|
|
tex.draw_texture(self:id(key), { fade = fade, frame = frame })
|
|
tex.draw_texture(self:id(key), { fade = fade, frame = frame, index = 1, mirror = "horizontal" })
|
|
end
|
|
|
|
function Waiwai:handle_gauge(player_num, progress)
|
|
self.progress[player_num == 2 and 2 or 1] = progress
|
|
local mean = (self.progress[1] + self.progress[2]) / 2
|
|
self.level = mean >= 0.7 and 2 or (mean >= 0.3 and 1 or 0)
|
|
end
|
|
|
|
function Waiwai:handle_good() end
|
|
function Waiwai:handle_ok() end
|
|
function Waiwai:handle_bad() end
|
|
function Waiwai:handle_drumroll() self.renda:add_renda() end
|
|
function Waiwai:handle_balloon() self.renda:add_renda() end
|
|
|
|
function Waiwai:update(current_ms, bpm)
|
|
local dt = self.last_ms and current_ms - self.last_ms or 0
|
|
self.last_ms = current_ms
|
|
self.bpm = bpm
|
|
self.now = current_ms
|
|
self.beat = self.beat + dt * bpm / 60000
|
|
self.renda:update(current_ms)
|
|
|
|
-- a level only starts once the one below it is finished, in both directions
|
|
local up, down = dt / RISE_MS, dt / FALL_MS
|
|
if self.level >= 1 then
|
|
self.second_a = math.min(1, self.second_a + up)
|
|
elseif self.fever_a <= 0 then
|
|
self.second_a = math.max(0, self.second_a - down)
|
|
end
|
|
if self.level >= 2 and self.second_a >= 1 then
|
|
self.fever_a = math.min(1, self.fever_a + up)
|
|
elseif self.level < 2 then
|
|
self.fever_a = math.max(0, self.fever_a - down)
|
|
end
|
|
end
|
|
|
|
-- Pops up past its rest while rising, then falls back out accelerating
|
|
local function pop_offset(a, rising, depth)
|
|
if rising then
|
|
if a < 0.7 then return lerp(depth, -18, a / 0.7) end
|
|
return lerp(-18, 0, (a - 0.7) / 0.3)
|
|
end
|
|
return depth * (1 - a) * (1 - a)
|
|
end
|
|
|
|
-- Two poses a beat, bobbing 8 px
|
|
function Waiwai:draw_mascots(key, y)
|
|
tex.draw_texture(self:id(key), {
|
|
frame = math.floor(self.beat) % 2,
|
|
y = (y + 8 * arc(self.beat)) * S,
|
|
})
|
|
end
|
|
|
|
-- A folder of pictures, each up for `slot` beats with the next fading in over its last 0.2
|
|
function Waiwai:draw_slides(id, count, slot, fade)
|
|
local i = math.floor(self.beat / slot)
|
|
local into = self.beat / slot - i
|
|
tex.draw_texture(id, { frame = i % count, fade = fade })
|
|
local next_in = (into * slot - (slot - 0.2)) / 0.2
|
|
if next_in > 0 then tex.draw_texture(id, { frame = (i + 1) % count, fade = fade * next_in }) end
|
|
end
|
|
|
|
-- Falling sparkles, each stream starting a third of its flight after the last
|
|
local SPARKLE_STREAMS = {
|
|
{ x = { -160, 320, 800 }, y = 340, from = { 327, -449 }, to = { -43, 520 }, ms = 10000, stagger = 3333, scale = 0.75, fade = 0.7 },
|
|
{ x = { -160, 320, 800 }, y = 340, from = { 573, -449 }, to = { 203, 520 }, ms = 10000, stagger = 3333, scale = 0.75, fade = 0.7, mirror = true },
|
|
{ x = { 95, 545, 995 }, y = 150, from = { 260, -310 }, to = { -60, 730 }, ms = 8000, stagger = 2667, scale = 1, fade = 1 },
|
|
}
|
|
local SPARKLE_SIZE = 328
|
|
|
|
function Waiwai:draw_confetti(fade)
|
|
if fade <= 0 then return end
|
|
local id = self:id("confetti")
|
|
for _, s in ipairs(SPARKLE_STREAMS) do
|
|
local half = SPARKLE_SIZE * s.scale / 2
|
|
for phase = 0, 2 do
|
|
local t = ((self.now + phase * s.stagger) % s.ms) / s.ms
|
|
for _, x in ipairs(s.x) do
|
|
tex.draw_texture(id, {
|
|
x = (x + lerp(s.from[1], s.to[1], t) - half) * S,
|
|
y = (s.y + lerp(s.from[2], s.to[2], t) - half) * S,
|
|
scale = s.scale,
|
|
fade = fade * s.fade,
|
|
mirror = s.mirror and "horizontal" or nil,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The stage up to the screen's floor; the fever layers fade over what they replace
|
|
function Waiwai:draw_stage()
|
|
local second, fever = self.second_a, self.fever_a
|
|
local beat = self.beat
|
|
local not_full = fever < 1
|
|
local flip = math.floor(beat) % 2
|
|
local band = self.spec.slides and self:id("fever_band", true) or self:id("fever_band")
|
|
local slides = self.spec.slides and self:id("screen", true) or self:id("screen")
|
|
|
|
if not_full then self:draw("eq_band", 1, self.level >= 1 and flip or 0) end
|
|
if fever > 0 then tex.draw_texture(band, { frame = flip, fade = fever }) end
|
|
self:draw("dome", 1)
|
|
self:draw("fever_dome", fever)
|
|
self:draw("fever_dome_glow", fever * lerp(0.4, 1, 1 - arc(beat / 2)))
|
|
self:draw("stage", 1)
|
|
if fever > 0 then
|
|
self:draw("fever_dots", 1, flip)
|
|
else
|
|
self:draw("lights_dots", second)
|
|
end
|
|
-- the bulbs change colour once fever has fully arrived, and only change back when it has gone
|
|
local fever_lit = fever >= 1 or (self.level < 2 and fever > 0)
|
|
self:draw_pair(fever_lit and "fever_corner_lights" or "corner_lights", 1)
|
|
if fever > 0 then
|
|
tex.draw_texture(self:id("fever_crowd_back"), { y = 3 * arc(beat) * S, fade = fever })
|
|
end
|
|
if not_full then self:draw("rim_top", 1) end
|
|
self:draw("fever_rim_top", fever)
|
|
if not_full then self:draw_slides(slides, self.spec.slides or 2, 4, 1) end
|
|
if fever > 0 then self:draw_slides(self:id("fever_screen"), 3, 3, fever) end
|
|
local glow = lerp(0.6, 1, 1 - arc(beat))
|
|
if not_full then self:draw("screen_glow", glow) end
|
|
self:draw("fever_screen_glow", fever * glow)
|
|
self:draw("stage_front", 1)
|
|
self:draw_pair("spot_beams", second)
|
|
self:draw_pair("fever_bulbs", fever)
|
|
if not_full then
|
|
-- two sets of floor lights, swapping every two beats
|
|
local into = beat / 2 % 1
|
|
self:draw_pair("floor_lights", clamp01(into * 8), math.floor(beat / 2) % 2)
|
|
end
|
|
self:draw_pair("fever_floor_lights", fever)
|
|
local half = beat % 1
|
|
self:draw_pair("fever_floor_flash", fever * (half < 0.5 and lerp(0.6, 1, 1 - arc(half * 2)) or 1))
|
|
self:draw("stage_floor", 1)
|
|
self:draw("balcony", 1)
|
|
self:draw("fever_stage_floor", fever)
|
|
if not_full then self:draw("rim_floor", 1) end
|
|
self:draw("fever_rim_floor", fever)
|
|
end
|
|
|
|
local LANTERN_MS = 333
|
|
|
|
-- A3: the festival street stays as it is at every level, only the balcony warms up
|
|
function Waiwai:draw_street()
|
|
local fever = self.fever_a
|
|
self:draw("street", 1)
|
|
self:draw("top_band", 1)
|
|
if fever >= 1 or (self.level < 2 and fever > 0) then self:draw_pair("fever_corner_lights", 1) end
|
|
if fever > 0 then
|
|
tex.draw_texture(self:id("fever_crowd_back"), { y = 3 * arc(self.beat) * S, fade = fever })
|
|
end
|
|
local flicker = lerp(0.55, 0.7, arc(self.now / LANTERN_MS))
|
|
tex.draw_texture(self:id("lantern_glow"), { fade = flicker, blend = "additive" })
|
|
self:draw_a3_dancers()
|
|
self:draw("curtain", 1)
|
|
end
|
|
|
|
-- Sixteen beats, shared by all four; each mirrors for the middle eight
|
|
local A3_POSES = {
|
|
{ 0, 0 }, { 0.35, 1 }, { 0.6, 2 }, { 2, 1 }, { 2.1, 0 }, { 2.55, 1 }, { 2.6, 2 }, { 3.45, 3 },
|
|
{ 4, 0 }, { 4.35, 1 }, { 4.6, 2 }, { 6, 1 }, { 6.1, 0 }, { 6.35, 1 }, { 6.6, 2 }, { 7.45, 3 },
|
|
{ 8, "rest_8" }, { 8.6, 4 }, { 8.8, 5 }, { 9, 6 }, { 9.85, 7 }, { 10.45, 8 },
|
|
{ 12, "rest_12" }, { 12.6, 4 }, { 12.8, 5 }, { 13, 6 }, { 13.85, 7 }, { 14.45, 8 },
|
|
}
|
|
local A3_HOPS = { 0.6, 2.6, 4.6, 6.6 }
|
|
local A3_DANCERS = {
|
|
{ key = "dancer_4", x = 422, rest_8 = 0, rest_12 = 0 },
|
|
{ key = "dancer_3", x = 857, rest_8 = 0, rest_12 = 0 },
|
|
{ key = "dancer_2", x = 562, rest_8 = 9, rest_12 = 9 },
|
|
{ key = "dancer_1", x = 717, rest_8 = 0, rest_12 = 1, notes = { { 3.45, 46, -26 }, { 7.45, -41.5, -24.5 } } },
|
|
}
|
|
local A3_SCALE, A3_FLOOR = 0.5, 633
|
|
|
|
function Waiwai:draw_a3_dancers()
|
|
local beat = self.beat % 16
|
|
local pose = 0
|
|
for _, p in ipairs(A3_POSES) do
|
|
if beat >= p[1] then pose = p[2] end
|
|
end
|
|
local hop = 0
|
|
for _, h in ipairs(A3_HOPS) do
|
|
local t = beat - h
|
|
if t >= 0 and t < 1 then hop = 8 * (t < 0.35 and t / 0.35 or (1 - t) / 0.65) end
|
|
end
|
|
local mirror = beat >= 4 and beat < 12 and "horizontal" or nil
|
|
local shadow_scale = A3_SCALE * lerp(1, 0.9, arc(clamp01((beat % 2 - 0.6) / 0.7) / 2))
|
|
for _, d in ipairs(A3_DANCERS) do
|
|
tex.draw_texture(self:id("shadow"), {
|
|
x = (d.x + 2.5 - 60 * shadow_scale) * S,
|
|
y = (A3_FLOOR + 73.5 - 25 * shadow_scale) * S,
|
|
scale = shadow_scale,
|
|
fade = 0.449,
|
|
})
|
|
local frame = type(pose) == "string" and d[pose] or pose
|
|
tex.draw_texture(self:id(d.key), { frame = frame, y = -hop * S, scale = A3_SCALE, mirror = mirror })
|
|
for _, n in ipairs(d.notes or {}) do
|
|
local t = beat - n[1]
|
|
if t >= 0 and t < 0.65 then
|
|
tex.draw_texture(self:id("note"), {
|
|
x = (d.x + n[2] - 21.5) * S,
|
|
y = (A3_FLOOR + n[3] - 21.5) * S,
|
|
fade = t < 0.3 and t / 0.3 or (0.65 - t) / 0.35,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Four-beat routine: first pose, a hop into the second, a hop back. Half hop in beat 1, half in beat 3.
|
|
local HOP_START = { hop_early = 0.75, hop_late = 2.75 }
|
|
local PROP_START = { hop_early = 1.45, hop_late = 3.33 }
|
|
local SWAP_TO, SWAP_BACK = 1.325, 3.325
|
|
local HOP_PX = 16
|
|
local DANCER_H = 184
|
|
|
|
function Waiwai:draw_i7_dancers()
|
|
local dancers = self.spec.dancers
|
|
if not dancers then return end
|
|
local beat = self.beat % 4
|
|
for _, d in ipairs(dancers) do
|
|
local t = beat - HOP_START[d.hop]
|
|
local y, squash = 0, 0
|
|
if t >= 0 and t < 0.575 then
|
|
y = -HOP_PX * (t < 0.25 and t / 0.25 or (0.575 - t) / 0.325)
|
|
else
|
|
-- crouch in the second half of every beat
|
|
local half = beat % 1
|
|
if half >= 0.5 then squash = 0.019 * arc(half * 2 - 1) end
|
|
end
|
|
local id = self:id(d.key, true)
|
|
local h = DANCER_H
|
|
tex.draw_texture(id, {
|
|
frame = (beat >= SWAP_TO and beat < SWAP_BACK) and 1 or 0,
|
|
y = (y + h * squash) * S,
|
|
y2 = -h * squash * S,
|
|
})
|
|
|
|
local age = beat - PROP_START[d.hop]
|
|
if age >= 0 and age < 0.65 then
|
|
local id_prop = self:id(d.prop, true)
|
|
local size = 56 * clamp01(age / 0.2)
|
|
local fade = age < 0.35 and 1 or (0.65 - age) / 0.3
|
|
for i = 0, d.props - 1 do
|
|
local inset = (56 - size) / 2
|
|
tex.draw_texture(id_prop, {
|
|
index = i,
|
|
x = inset * S,
|
|
y = (inset - 12 * age) * S,
|
|
x2 = (size - 56) * S,
|
|
y2 = (size - 56) * S,
|
|
fade = fade,
|
|
mirror = (d.mirror_prop and i == 1) and "horizontal" or nil,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The balcony's crowd and mascots, common to every stage
|
|
function Waiwai:draw_balcony()
|
|
local second, fever = self.second_a, self.fever_a
|
|
local crowd_bob = fever > 0 and 5 * arc(self.beat) * S or 0
|
|
if second > 0 then tex.draw_texture(self:id("side_crowd"), { y = crowd_bob, fade = second }) end
|
|
tex.draw_texture(self:id("crowd"), { y = crowd_bob })
|
|
if not self.spec.street and fever > 0 then
|
|
-- the speakers swing in from below the sides, thumping on the beat
|
|
local slide = 1 - fever
|
|
local thump = 0.1 * clamp01(1 - (self.beat % 1) / 0.25)
|
|
local grow = 160 * thump
|
|
local grow_h = 320 * thump
|
|
local dy = -120 * slide
|
|
tex.draw_texture(self:id("speaker"), {
|
|
x = (-106 * slide - grow / 2) * S,
|
|
y = (dy - grow_h / 2) * S,
|
|
x2 = grow * S,
|
|
y2 = grow_h * S,
|
|
})
|
|
tex.draw_texture(self:id("speaker"), {
|
|
index = 1,
|
|
mirror = "horizontal",
|
|
x = (106 * slide - grow / 2) * S,
|
|
y = (dy - grow_h / 2) * S,
|
|
x2 = grow * S,
|
|
y2 = grow_h * S,
|
|
})
|
|
end
|
|
if fever > 0 then self:draw_mascots("mascots_fever", pop_offset(fever, self.level >= 2, 90)) end
|
|
if second > 0 then self:draw_mascots("mascots_second", pop_offset(second, self.level >= 1, 128)) end
|
|
self:draw_mascots("mascots_first", 0)
|
|
self:draw("rail", 1)
|
|
if not self.spec.street and fever > 0 then self:draw("fever_rail_glow", lerp(0.1, 1, fever)) end
|
|
self:draw_confetti(fever)
|
|
end
|
|
|
|
function Waiwai:draw_back()
|
|
if not self.now then return end
|
|
if self.spec.street then
|
|
self:draw_street()
|
|
else
|
|
self:draw_stage()
|
|
self:draw_i7_dancers()
|
|
end
|
|
self:draw_balcony()
|
|
self.renda:draw()
|
|
end
|
|
|
|
function Waiwai:draw_fore() end
|
|
|
|
function Waiwai:destroy()
|
|
self.renda:destroy()
|
|
tex.unload_folder(self.base_path, "stage")
|
|
if self.path ~= self.base_path then tex.unload_folder(self.path, "stage") end
|
|
end
|
|
|
|
return Waiwai
|