280 lines
11 KiB
Lua
280 lines
11 KiB
Lua
local DonBGBase = require("background/bg_objects/donbg/base")
|
|
local BGNormal = require("background/bg_objects/bg_normal")
|
|
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")
|
|
|
|
local COLLAB_PATH = "background/collab/funassyi"
|
|
local MAX_DANCERS = 5
|
|
|
|
local chain_moves, sum = Timing.chain_moves, Timing.sum
|
|
|
|
-- Don band: the fruit-pattern tiles scroll one tile left every 10.7 s; the clear set swaps
|
|
-- in the bright tiles and a strip of pears running twice as fast. Ambient, in ms.
|
|
local DONBG_SCROLL_MS = 10667
|
|
local DONBG_SPEC = { layers = {
|
|
{ key = "background", ms = DONBG_SCROLL_MS, x = -81 },
|
|
{ key = "overlay", ms = DONBG_SCROLL_MS, dx = -704, x = -81, clear_only = true },
|
|
} }
|
|
|
|
-- Clear background: the sunburst and the shouting strip fade in over the stage (which
|
|
-- stays underneath) in 1.75 beats and back out in 0.85 beat. The burst swaps between its
|
|
-- two panels every 0.75 beat; the strip (three copies, 1216 px apart) scrolls 1212 px left
|
|
-- every 14 beats. Timed in beats.
|
|
local BG_FEVER_IN = 1.75
|
|
local BG_FEVER_OUT = 0.85
|
|
local BG_FEVER_SWAP = 0.75
|
|
local BG_FEVER_TEXT = { beats = 14, distance = -1212, tile = 1216 }
|
|
|
|
local FunassyiBGFever = {}
|
|
FunassyiBGFever.__index = FunassyiBGFever
|
|
|
|
function FunassyiBGFever.new(bpm)
|
|
local self = setmetatable({}, FunassyiBGFever)
|
|
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_text = id("overlay")
|
|
self.beat = 60000 / bpm
|
|
self.offset = 0
|
|
self.fade = {}
|
|
return self
|
|
end
|
|
|
|
function FunassyiBGFever:opacity() return self.offset + sum(self.fade) end
|
|
|
|
-- fades in from wherever it is; the burst and strip start over
|
|
function FunassyiBGFever:start()
|
|
local scale = tex.get_screen_scale()
|
|
local swap = BG_FEVER_SWAP * self.beat
|
|
self.offset = self:opacity()
|
|
self.fade = chain_moves({ { BG_FEVER_IN, 1000 - self.offset } }, self.beat, 1)
|
|
self.panels = anim.texture_change(2 * swap, { { 0, swap, 0 }, { swap, 2 * swap, 1 } }, { loop = true })
|
|
self.scroll = anim.move(BG_FEVER_TEXT.beats * self.beat,
|
|
{ total_distance = math.floor(BG_FEVER_TEXT.distance * scale), loop = true })
|
|
self.panels:start()
|
|
self.scroll:start()
|
|
end
|
|
|
|
-- fades out from wherever it is
|
|
function FunassyiBGFever:stop()
|
|
self.offset = self:opacity()
|
|
self.fade = chain_moves({ { BG_FEVER_OUT, -self.offset } }, self.beat, 1)
|
|
end
|
|
|
|
function FunassyiBGFever:update(current_time_ms, bpm)
|
|
if bpm then self.beat = 60000 / bpm end
|
|
for _, m in ipairs(self.fade) do m:update(current_time_ms) end
|
|
if self.panels then
|
|
self.panels:update(current_time_ms)
|
|
self.scroll:update(current_time_ms)
|
|
end
|
|
end
|
|
|
|
function FunassyiBGFever:draw()
|
|
local fade = self:opacity() / 1000
|
|
if fade <= 0 then return end
|
|
tex.draw_texture(self._id_bg, { frame = self.panels.attribute, fade = fade })
|
|
local tile = math.floor(BG_FEVER_TEXT.tile * tex.get_screen_scale())
|
|
for k = 0, 2 do
|
|
tex.draw_texture(self._id_text, { x = self.scroll.attribute + k * tile, fade = fade })
|
|
end
|
|
end
|
|
|
|
function FunassyiBGFever:destroy() tex.unload_folder(COLLAB_PATH, "bg_fever/" .. self.name) end
|
|
|
|
-- Dancers
|
|
-- Timed in beats; poses are offsets from Lumen's dancer origin (texture.json y 670), which
|
|
-- sits in the middle of each image. Squashes move the origin down by half the height lost,
|
|
-- so the feet stay planted.
|
|
-- The four small dancers jump in from below and land with a squash, then loop an 8-beat
|
|
-- routine: two pose pairs swapped every half beat, a step back through their entry frames,
|
|
-- a hop and a squash. Leaving, they squash, spring up and drop off the bottom.
|
|
local SMALL_Y = -72
|
|
|
|
local SMALL_ENTER = Timing.steps({
|
|
{ 0, 0.625, x = 0, y = 267, to = { 0, -128, "out" } }, -- shoots up from below
|
|
{ 0, 0.275, to = { 0, SMALL_Y, "in" } },
|
|
{ 0, 0.225, to = { 0, SMALL_Y + 6, "out", sy = 0.94 } }, -- lands
|
|
{ 0, 0.25, sy = 0.94, to = { 0, SMALL_Y, "in", sy = 1 } },
|
|
{ 0, 0.35 },
|
|
{ 1, 0.275 },
|
|
})
|
|
SMALL_ENTER.beats = 2
|
|
|
|
local SMALL_DANCE = Timing.steps({
|
|
{ 0, 0.5, x = 0, y = SMALL_Y }, { 1, 0.5 }, { 0, 0.5 }, { 1, 0.5 },
|
|
{ 2, 0.5 }, { 3, 0.5 }, { 2, 0.5 }, { 3, 0.5 },
|
|
{ 1, 0.5, key = "start" }, { 0, 0.5, key = "start" }, { 1, 0.5, key = "start" }, { 0, 0.625, key = "start" },
|
|
{ 4, 0.475, to = { 0, SMALL_Y - 68, "out" } }, -- hop
|
|
{ 4, 0.425, to = { 0, SMALL_Y, "in" } },
|
|
{ 5, 0.25, to = { 0, SMALL_Y + 4, nil, sy = 0.96 } }, -- squash
|
|
{ 5, 0.225, sy = 0.96, to = { 0, SMALL_Y, nil, sy = 1 } },
|
|
{ 5, 0.5 },
|
|
})
|
|
|
|
local SMALL_LEAVE = Timing.steps({
|
|
{ 0, 0.15, x = 0, y = SMALL_Y, to = { 0, SMALL_Y + 7, "out", sy = 0.93 } },
|
|
{ 0, 0.125, sy = 0.93, to = { 0, SMALL_Y, "in", sy = 1 } },
|
|
{ 0, 0.225, to = { 0, -128, "out" } }, -- springs up
|
|
{ 0, 0.85, to = { 0, 267, "in" } }, -- and drops off
|
|
})
|
|
SMALL_LEAVE.beats = 1.35
|
|
|
|
-- Funassyi in the middle drops in through its own entry frames, loops three pose pairs a
|
|
-- quarter beat each, then the same hop and squash; it never leaves.
|
|
local BIG_Y = -162
|
|
|
|
local BIG_ENTER = Timing.steps({
|
|
{ 0, 0.5, x = 0, y = 194, to = { 0, -170, "out" } }, -- drops in from below
|
|
{ 1, 0.35, to = { 0, -174, "out" } },
|
|
{ 2, 0.45, to = { 0, -143, "in" } },
|
|
{ 3, 0.225, y = BIG_Y, to = { 0, BIG_Y + 20, "out", sy = 0.9 } }, -- lands
|
|
{ 4, 0.25, sy = 0.9, to = { 0, BIG_Y, "in", sy = 1 } },
|
|
{ 4, 0.225 },
|
|
})
|
|
BIG_ENTER.beats = 2
|
|
|
|
local function swap(a, b)
|
|
local out = {}
|
|
for _ = 1, 4 do
|
|
table.insert(out, { a, 0.25 })
|
|
table.insert(out, { b, 0.25 })
|
|
end
|
|
return out
|
|
end
|
|
local BIG_DANCE = Timing.steps({ { 0, 0, x = 0, y = BIG_Y } }, swap(0, 1), swap(2, 3), swap(4, 5), {
|
|
{ 6, 0.125 },
|
|
{ 7, 0.475, to = { 0, BIG_Y - 40, "out" } }, -- hop
|
|
{ 7, 0.425, to = { 0, BIG_Y + 10, "in" } },
|
|
{ 3, 0.225, key = "start", y = BIG_Y, to = { 0, BIG_Y + 20, "out", sy = 0.9 } },
|
|
{ 4, 0.25, key = "start", sy = 0.9, to = { 0, BIG_Y, "in", sy = 1 } },
|
|
{ 4, 0.5, key = "start" },
|
|
})
|
|
|
|
local SMALL = { origin = { 136, 100 }, enter = SMALL_ENTER, dance = SMALL_DANCE, leave = SMALL_LEAVE }
|
|
local FUNASSYI_DANCERS = Dancer.define({
|
|
[0] = { origin = { 148, 196 }, enter = BIG_ENTER, dance = BIG_DANCE },
|
|
default = SMALL,
|
|
})
|
|
|
|
-- Lumen's slots, left to right; the group fills centre, left, right, far-left, far-right,
|
|
-- which is Lumen's dancer1..5 (skin dancers 0, 4, 3, 2, 1)
|
|
local DANCER_CENTERS = { 183, 417, 640, 863, 1097 }
|
|
local DANCER_DRAW_ORDER = { 5, 1, 4, 2, 3 }
|
|
local DANCER_JOIN_ORDER = { 0, 4, 3, 2, 1 }
|
|
|
|
-- Chibi for good hits: Funassyi hops in growing from 70% size, then every beat as it runs
|
|
-- it crouches and slides back, springs up and lands
|
|
local FUNASSYI_CHIBI = {
|
|
key = "0",
|
|
run = { y = 0 },
|
|
period = 1,
|
|
poses = {
|
|
{ 0, 1, 0, 0, sy = { 1, 0.9 }, to = { -18, 7 } }, -- crouches, sliding back
|
|
{ 0.1, 1, -23, 7, sy = 0.9, to = { -64, 7 } },
|
|
{ 0.275, 1, -64, 5, sy = { 0.93, 1 }, to = { -76, 0 } },
|
|
{ 0.35, 0, -76, 8, to = { -76, -16, "out" } }, -- springs up
|
|
{ 0.55, 2, -76, -12, to = { -76, -6, "in" } },
|
|
{ 0.775, 3, -68, -14, to = { -68, 1 } }, -- lands
|
|
},
|
|
}
|
|
|
|
-- Background
|
|
|
|
local FunassyiBackground = {}
|
|
FunassyiBackground.__index = FunassyiBackground
|
|
|
|
function FunassyiBackground.new(player_num, bpm)
|
|
local self = setmetatable({}, FunassyiBackground)
|
|
self.donbg = DonBGBase.new(1, player_num, COLLAB_PATH, DONBG_SPEC)
|
|
self.bg_normal = BGNormal.Base.new(0, COLLAB_PATH)
|
|
self.bg_fever = FunassyiBGFever.new(bpm)
|
|
self.footer = Footer.new("background", 2)
|
|
self.fever = Fever.create(0, bpm, "background")
|
|
self.dancer = Dancer.BaseDancerGroup.new(0, bpm, MAX_DANCERS, COLLAB_PATH, FUNASSYI_DANCERS)
|
|
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("background", 0)
|
|
self.chibi = ChibiController.new(bpm, COLLAB_PATH, 1)
|
|
self.is_clear = false
|
|
self.is_rainbow = false
|
|
self.last_milestone = 1
|
|
return self
|
|
end
|
|
|
|
function FunassyiBackground: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 FunassyiBackground:handle_good(player_num) BaseChibi.add(self.chibi, FUNASSYI_CHIBI, COLLAB_PATH, player_num) end
|
|
|
|
function FunassyiBackground:handle_ok(player_num) BaseChibi.add(self.chibi, FUNASSYI_CHIBI, COLLAB_PATH, player_num) end
|
|
|
|
function FunassyiBackground:handle_bad(player_num) self.chibi:add_chibi(player_num, true) end
|
|
|
|
function FunassyiBackground:handle_drumroll(player_num) self.renda:add_renda() end
|
|
|
|
function FunassyiBackground:handle_balloon(player_num) self.renda:add_renda() end
|
|
|
|
function FunassyiBackground: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 FunassyiBackground: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 FunassyiBackground:draw_fore() end
|
|
|
|
function FunassyiBackground: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 FunassyiBackground
|