375 lines
14 KiB
Lua
375 lines
14 KiB
Lua
local DonBGBase = require("background/bg_objects/donbg/base")
|
|
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 CHIBI_SETS = require("background/bg_objects/chibi/sets")
|
|
local Timing = require("background/bg_objects/timing")
|
|
|
|
local COLLAB_PATH = "background/collab/gmt"
|
|
local MAX_DANCERS = 5
|
|
|
|
local chain_moves, sum, pose_at, steps = Timing.chain_moves, Timing.sum, Timing.pose_at, Timing.steps
|
|
|
|
-- Don band: honeycomb tiles under a strip of plum blossoms that bobs 23px down and back
|
|
-- every 2 s, with the tilted Game Music Triangle logo riding on the strip; everything
|
|
-- scrolls one tile left every 5.3 s. The clear set swaps in the bright tiles and blossoms.
|
|
-- Ambient, in ms.
|
|
local DONBG_SCROLL_MS = 5333
|
|
local DONBG_SPEC = { fade = 0.225, layers = {
|
|
{ key = "background", ms = DONBG_SCROLL_MS },
|
|
{ key = "overlay", ms = DONBG_SCROLL_MS, bob = { { 1000, 23 }, { 1000, -23 } } },
|
|
{ key = "logo", ms = DONBG_SCROLL_MS, x = 59, rotation = -30 },
|
|
} }
|
|
|
|
-- Normal background: a festival night street with lanterns flickering additively over it,
|
|
-- and its stall-goers each on their own loop. Ambient, in ms (Lumen's 60 fps).
|
|
|
|
local LIGHTS = {
|
|
{ 0, 0, 0, 0, alpha = 0.7, to = { 0, 0, alpha = 0.65 } },
|
|
{ 67, 0, alpha = 0.66, to = { 0, 0, alpha = 0.7 } },
|
|
length = 117,
|
|
}
|
|
|
|
-- The kappa stands still, squashes, rears up a little taller, rocks back and forth on its
|
|
-- feet and squashes again
|
|
local KAPPA = {
|
|
{ 0, 0, 0, 0 },
|
|
{ 1117, 0, to = { 0, 3, "out", sy = 0.95 } },
|
|
{ 1233, 0, 0, 3, sy = 0.95, to = { 0, 0, "in", sy = 1 } },
|
|
{ 1350, 1, 0, 0, to = { 0, -2, "out", sy = 1.03 } },
|
|
{ 2333, 1, 0, 0, to = { 0, 0, "out", rot = -3.3 } },
|
|
{ 2600, 1, rot = -3.3, to = { 0, 0, "in", rot = 0 } },
|
|
{ 2833, 1, to = { 0, 0, "out", rot = 4 } },
|
|
{ 3117, 1, rot = 4, to = { 0, 0, "in", rot = 0 } },
|
|
{ 3367, 0, to = { 0, 3, "out", sy = 0.95 } },
|
|
{ 3500, 0, 0, 3, sy = 0.95, to = { 0, 0, "in", sy = 1 } },
|
|
length = 3617,
|
|
}
|
|
local KAPPA_PIVOT = { 60, 115 } -- its feet
|
|
|
|
-- The lion mask crouches, stands, springs up and drops back into a crouch, turning round at
|
|
-- the top of each jump
|
|
local function shishi_jump(t, mirror)
|
|
return {
|
|
{ t, 0, 0, 4, sy = 0.95, mirror = mirror, to = { 0, 0, sy = 1 } },
|
|
{ t + 167, 0, 0, 0, mirror = mirror, to = { 0, -10, sy = 1.1 } },
|
|
}
|
|
end
|
|
local SHISHI = Timing.concat(
|
|
{ { 0, 0, 0, 0, to = { 0, 4, sy = 0.95 } } },
|
|
shishi_jump(150, false),
|
|
{ { 483, 0, 0, -10, sy = 1.1, mirror = true, to = { 0, 0, sy = 1 } },
|
|
{ 667, 0, 0, 0, mirror = true, to = { 0, 4, sy = 0.95 } } },
|
|
shishi_jump(850, true),
|
|
{ { 1150, 0, 0, -10, sy = 1.1, to = { 0, 0, sy = 1 } } }
|
|
)
|
|
SHISHI.length = 1333
|
|
|
|
-- The boy swings his hand (hanging from the wrist) back a little, then forward
|
|
local BOY_HAND = {
|
|
{ 0, 0, 0, 0, to = { 0, 0, "out", rot = -8.7 } },
|
|
{ 500, 0, rot = -8.7, to = { 0, 0, "in", rot = 0 } },
|
|
{ 1000, 0, to = { 0, 0, "out", rot = 19 } },
|
|
{ 1467, 0, rot = 19, to = { 0, 0, "in", rot = 0 } },
|
|
length = 2000,
|
|
}
|
|
local BOY_HAND_PIVOT = { 13, 1 }
|
|
|
|
-- The girl plays a water-balloon yo-yo: she throws, the balloon drops on its string and
|
|
-- comes back up. Lengths in Lumen frames; x, y in her own (unscaled) pixels from her centre.
|
|
local GIRL_SCALE = 0.794
|
|
local GIRL = steps({ { 0, 5 }, { 1, 5 }, { 2, 15 }, { 1, 5 } })
|
|
local STRING = steps({
|
|
{ 0, 2, x = 46, y = -1 }, { 1, 2, y = -16 },
|
|
{ 2, 2, y = -17, to = { 46, 6 } }, { 2, 1, y = 6 }, { 1, 3, y = 9 }, { 0, 2, y = 35 },
|
|
{ 3, 3, x = 47, y = 45, sy = 0.8 }, { 4, 10, sy = 0.8 }, -- pulled straight
|
|
{ 4, 3, y = 35, sy = 0.8 }, { 3, 2, sy = 0.8 },
|
|
})
|
|
local STRING_H = 106
|
|
local YOYO = steps({
|
|
{ 0, 2, x = 49, y = 37 }, { 1, 2, y = 13 }, { 2, 2, y = 9 }, { 3, 2, y = 7 },
|
|
{ 4, 7, y = 17, to = { 49, 87, "out" } }, -- drops
|
|
{ 4, 1, y = 87 }, { 2, 2 }, { 1, 2 },
|
|
{ 0, 10, to = { 49, 57, "in" } }, -- and comes back
|
|
})
|
|
|
|
local SHADOW_ALPHA = 0.5
|
|
|
|
local GmtBGNormal = {}
|
|
GmtBGNormal.__index = GmtBGNormal
|
|
|
|
function GmtBGNormal.new()
|
|
local self = setmetatable({}, GmtBGNormal)
|
|
self.name = "bg_0"
|
|
tex.load_folder(COLLAB_PATH, "bg_normal/" .. self.name)
|
|
local function id(key) return tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/" .. key) or 0 end
|
|
self._id_bg = id("background")
|
|
self._id_lights = id("lights")
|
|
self._id_shadow = id("shadow")
|
|
self._id_kappa = id("kappa")
|
|
self._id_shishi = id("shishi")
|
|
self._id_boy = id("boy")
|
|
self._id_boy_hand = id("boy_hand")
|
|
self._id_girl = id("girl")
|
|
self._id_string = id("string")
|
|
self._id_yoyo = id("yoyo")
|
|
self._id_deco = { id("deco1"), id("deco2"), id("deco3") }
|
|
self.kappa_h = tex.get_texture_info(self.name, "kappa").height
|
|
self.shishi_h = tex.get_texture_info(self.name, "shishi").height
|
|
self.now = 0
|
|
return self
|
|
end
|
|
|
|
function GmtBGNormal:update(current_time_ms)
|
|
self.now = current_time_ms
|
|
end
|
|
|
|
-- draws `id` at pose `p`: squashed about its centre by p.sy (h = its drawn height), turned
|
|
-- by p.rot about `pivot` (px from its top-left)
|
|
local function draw_pose(id, p, h, pivot, scale, extra)
|
|
local o = pivot and { pivot[1] * scale, pivot[2] * scale } or nil
|
|
local stretch = h * scale * (p.sy - 1)
|
|
local params = {
|
|
frame = p.frame,
|
|
x = (p.x or 0) * scale + (o and o[1] or 0),
|
|
y = (p.y or 0) * scale - stretch / 2 + (o and o[2] or 0),
|
|
y2 = stretch,
|
|
mirror = p.mirror and "horizontal" or nil,
|
|
rotation = pivot and p.rot or nil,
|
|
origin = o,
|
|
}
|
|
for k, v in pairs(extra or {}) do params[k] = v end
|
|
tex.draw_texture(id, params)
|
|
end
|
|
|
|
function GmtBGNormal:draw()
|
|
local scale = tex.get_screen_scale()
|
|
local function at(poses) return pose_at(poses, self.now % poses.length) end
|
|
local function shadow(i) tex.draw_texture(self._id_shadow, { index = i, fade = SHADOW_ALPHA }) end
|
|
|
|
tex.draw_texture(self._id_bg)
|
|
shadow(0)
|
|
tex.draw_texture(self._id_lights, { fade = at(LIGHTS).alpha })
|
|
draw_pose(self._id_kappa, at(KAPPA), self.kappa_h, KAPPA_PIVOT, scale)
|
|
shadow(1)
|
|
draw_pose(self._id_shishi, at(SHISHI), self.shishi_h, nil, scale)
|
|
shadow(2)
|
|
tex.draw_texture(self._id_boy)
|
|
draw_pose(self._id_boy_hand, at(BOY_HAND), 0, BOY_HAND_PIVOT, scale)
|
|
shadow(3)
|
|
local f = (self.now * 60 / 1000) % GIRL.length
|
|
local girl_scale = GIRL_SCALE * scale
|
|
tex.draw_texture(self._id_girl, { frame = pose_at(GIRL, f).frame })
|
|
draw_pose(self._id_string, pose_at(STRING, f), STRING_H, nil, girl_scale)
|
|
draw_pose(self._id_yoyo, pose_at(YOYO, f), 0, nil, girl_scale)
|
|
for i, deco in ipairs(self._id_deco) do
|
|
shadow(i + 3)
|
|
tex.draw_texture(deco)
|
|
end
|
|
end
|
|
|
|
function GmtBGNormal:destroy() tex.unload_folder(COLLAB_PATH, "bg_normal/" .. self.name) end
|
|
|
|
-- Clear background: the red pattern panel drops in from above over the street, bounces
|
|
-- once and settles; along it alternate logos and spinning tomoe crests scroll left. Losing
|
|
-- the clear drops the panel out the bottom. Drop and fall in beats; scroll and spin in ms.
|
|
local BG_FEVER_DROP = { from = -378, beats = 1.225, bounce = 40, bounce_beats = 0.275 }
|
|
local BG_FEVER_FALL = { to = 408, beats = 0.975 }
|
|
local BG_FEVER_TILE = 320
|
|
local BG_FEVER_PHASE = -180
|
|
local BG_FEVER_STRIP = { ms = 5333, spacing = 640 } -- one logo + one crest per spacing
|
|
local TOMOE_SPIN_MS = 7333
|
|
local TOMOE_HALF = 106.5
|
|
|
|
local GmtBGFever = {}
|
|
GmtBGFever.__index = GmtBGFever
|
|
|
|
function GmtBGFever.new(bpm)
|
|
local self = setmetatable({}, GmtBGFever)
|
|
self.name = "bg_fever_0"
|
|
tex.load_folder(COLLAB_PATH, "bg_fever/" .. self.name)
|
|
local function id(key) return tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/" .. key) or 0 end
|
|
self._id_bg = id("background")
|
|
self._id_tomoe = id("tomoe")
|
|
self._id_logo = id("logo")
|
|
self.beat = 60000 / bpm
|
|
self.offset = 0
|
|
self.moves = {}
|
|
self.shown = false
|
|
local scale = tex.get_screen_scale()
|
|
self.scroll = anim.move(BG_FEVER_STRIP.ms,
|
|
{ total_distance = -math.floor(BG_FEVER_STRIP.spacing * scale), loop = true })
|
|
self.spin = anim.move(TOMOE_SPIN_MS, { total_distance = 360, loop = true })
|
|
self.scroll:start()
|
|
self.spin:start()
|
|
return self
|
|
end
|
|
|
|
function GmtBGFever:y() return self.offset + sum(self.moves) end
|
|
|
|
-- drops in from wherever it is (from above when hidden)
|
|
function GmtBGFever:start()
|
|
local scale = tex.get_screen_scale()
|
|
self.offset = self.shown and self:y() or BG_FEVER_DROP.from * scale
|
|
self.shown = true
|
|
self.leaving = false
|
|
local d = BG_FEVER_DROP
|
|
self.moves = chain_moves({
|
|
{ d.beats, -self.offset / scale, "in" },
|
|
{ d.bounce_beats, -d.bounce, "out" },
|
|
{ d.bounce_beats, d.bounce, "in" },
|
|
}, self.beat, scale)
|
|
end
|
|
|
|
-- falls out the bottom from wherever it is
|
|
function GmtBGFever:stop()
|
|
if not self.shown then return end
|
|
local scale = tex.get_screen_scale()
|
|
self.offset = self:y()
|
|
self.leaving = true
|
|
self.moves = chain_moves({ { BG_FEVER_FALL.beats, BG_FEVER_FALL.to - self.offset / scale } }, self.beat, scale)
|
|
end
|
|
|
|
function GmtBGFever:update(current_time_ms, bpm)
|
|
if bpm then self.beat = 60000 / bpm end
|
|
for _, m in ipairs(self.moves) do m:update(current_time_ms) end
|
|
self.scroll:update(current_time_ms)
|
|
self.spin:update(current_time_ms)
|
|
if self.leaving and (not self.moves[1] or self.moves[1].is_finished) then
|
|
self.shown, self.leaving, self.moves, self.offset = false, false, {}, 0
|
|
end
|
|
end
|
|
|
|
function GmtBGFever:draw()
|
|
if not self.shown then return end
|
|
local scale = tex.get_screen_scale()
|
|
local y = self:y()
|
|
for k = 0, 4 do
|
|
tex.draw_texture(self._id_bg, { x = (BG_FEVER_PHASE + k * BG_FEVER_TILE) * scale, y = y })
|
|
end
|
|
local spacing = BG_FEVER_STRIP.spacing * scale
|
|
local half = TOMOE_HALF * scale
|
|
for k = 0, 2 do
|
|
tex.draw_texture(self._id_tomoe, {
|
|
x = self.scroll.attribute + k * spacing + half,
|
|
y = y + half,
|
|
rotation = self.spin.attribute,
|
|
origin = { half, half },
|
|
})
|
|
end
|
|
for k = 0, 3 do
|
|
tex.draw_texture(self._id_logo, { x = self.scroll.attribute + k * spacing, y = y })
|
|
end
|
|
end
|
|
|
|
function GmtBGFever:destroy() tex.unload_folder(COLLAB_PATH, "bg_fever/" .. self.name) end
|
|
|
|
-- Chibi for good hits: the default set 06 (chibi_5), except its first character runs 10px
|
|
-- lower in this collab
|
|
local CHIBI_SET = CHIBI_SETS[5]
|
|
local CHIBI_LOW = { key = "3", hop = { from = { -418, 31 }, peak = { -341, -14 }, up = 0.275 }, run = { y = 25 } }
|
|
|
|
local function add_chibi(controller, player_num)
|
|
local char = CHIBI_SET.chars[math.random(#CHIBI_SET.chars)]
|
|
if char.key == CHIBI_LOW.key then char = CHIBI_LOW end
|
|
local spec = setmetatable({}, { __index = function(_, k)
|
|
if char[k] ~= nil then return char[k] end
|
|
return CHIBI_SET[k]
|
|
end })
|
|
BaseChibi.add(controller, spec, "background", player_num, CHIBI_SET.class, "chibi_5")
|
|
end
|
|
|
|
-- Background
|
|
|
|
local GmtBackground = {}
|
|
GmtBackground.__index = GmtBackground
|
|
|
|
function GmtBackground.new(player_num, bpm)
|
|
local self = setmetatable({}, GmtBackground)
|
|
self.donbg = DonBGBase.new(1, player_num, COLLAB_PATH, DONBG_SPEC)
|
|
self.bg_normal = GmtBGNormal.new()
|
|
self.bg_fever = GmtBGFever.new(bpm)
|
|
self.footer = Footer.new(COLLAB_PATH, 0)
|
|
self.fever = Fever.create(2, bpm, "background")
|
|
self.dancer = Dancer.create(7, bpm, "background", MAX_DANCERS)
|
|
self.renda = RendaController.new("background", 1)
|
|
self.chibi = ChibiController.new(bpm, "background", 5)
|
|
self.is_clear = false
|
|
self.is_rainbow = false
|
|
self.last_milestone = 1
|
|
return self
|
|
end
|
|
|
|
function GmtBackground:update(current_ms, bpm)
|
|
self.donbg:update(current_ms, bpm)
|
|
self.bg_normal:update(current_ms)
|
|
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 GmtBackground:handle_good(player_num) add_chibi(self.chibi, player_num) end
|
|
|
|
function GmtBackground:handle_ok(player_num) add_chibi(self.chibi, player_num) end
|
|
|
|
function GmtBackground:handle_bad(player_num) self.chibi:add_chibi(player_num, true) end
|
|
|
|
function GmtBackground:handle_drumroll(player_num) self.renda:add_renda() end
|
|
|
|
function GmtBackground:handle_balloon(player_num) self.renda:add_renda() end
|
|
|
|
function GmtBackground: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 GmtBackground:draw_back()
|
|
self.bg_normal:draw()
|
|
self.bg_fever:draw()
|
|
self.footer:draw()
|
|
self.donbg:draw()
|
|
self.renda:draw()
|
|
self.dancer:draw()
|
|
self.fever:draw()
|
|
self.chibi:draw()
|
|
end
|
|
|
|
function GmtBackground:draw_fore() end
|
|
|
|
function GmtBackground:destroy()
|
|
self.donbg:destroy()
|
|
self.bg_normal:destroy()
|
|
self.bg_fever:destroy()
|
|
self.fever:destroy()
|
|
self.dancer:destroy()
|
|
self.footer:destroy()
|
|
self.renda:destroy()
|
|
self.chibi:destroy()
|
|
end
|
|
|
|
return GmtBackground
|