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