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