624 lines
23 KiB
Lua
624 lines
23 KiB
Lua
local DonBGBase = require("background/bg_objects/donbg/base")
|
|
local Fever = require("background/bg_objects/fever")
|
|
local Dancer = require("background/bg_objects/dancer")
|
|
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/mario"
|
|
local MAX_DANCERS = 5
|
|
|
|
local pose_at, steps, concat = Timing.pose_at, Timing.steps, Timing.concat
|
|
|
|
-- Ambient clocks count Lumen's 60fps frames
|
|
local function frames(ms) return ms * 60 / 1000 end
|
|
|
|
-- ? blocks (and the clear band's coins) blink dark for three short steps every 50 frames;
|
|
-- `f` is one frame in the table's unit
|
|
local function blink(f)
|
|
return steps({ { 0, 35 * f, hide = true }, { 0, 5 * f }, { 1, 5 * f }, { 0, 5 * f } })
|
|
end
|
|
local BLINK = blink(1)
|
|
|
|
-- The course (drawn 3x, 3840 wide) and the don band's course strip scroll left 1px a frame
|
|
local COURSE_W = 3840
|
|
|
|
-- Don band: the course strip with its blinking ? blocks; the clear set is a strip of coins
|
|
-- and clouds (1536 wide) whose coins blink the same way. Ambient, in ms.
|
|
local BLINK_MS = blink(1000 / 60)
|
|
local COURSE_MS = COURSE_W * 1000 / 60
|
|
local DONBG_SPEC = { layers = {
|
|
{ key = "background", ms = COURSE_MS, dx = -COURSE_W, tile = COURSE_W },
|
|
{ key = "blocks", ms = COURSE_MS, dx = -COURSE_W, tile = COURSE_W, blink = BLINK_MS },
|
|
{ key = "coins", ms = 1536 * 1000 / 60, dx = -1536, tile = 1536, clear_only = true },
|
|
{ key = "coin_blink", ms = 1536 * 1000 / 60, dx = -1536, tile = 1536, clear_only = true, blink = BLINK_MS },
|
|
} }
|
|
|
|
-- Normal background: the course scrolling under the lane, ? blocks blinking
|
|
|
|
local MarioBGNormal = {}
|
|
MarioBGNormal.__index = MarioBGNormal
|
|
|
|
function MarioBGNormal.new()
|
|
local self = setmetatable({}, MarioBGNormal)
|
|
self.name = "bg_0"
|
|
tex.load_folder(COLLAB_PATH, "bg_normal/" .. self.name)
|
|
self._id_bg = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/background") or 0
|
|
self._id_blocks = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/blocks") or 0
|
|
self.now = 0
|
|
return self
|
|
end
|
|
|
|
function MarioBGNormal:update(current_time_ms) self.now = current_time_ms end
|
|
|
|
function MarioBGNormal:draw()
|
|
local scale = tex.get_screen_scale()
|
|
local x = -(frames(self.now) % COURSE_W)
|
|
local blink = pose_at(BLINK, frames(self.now) % BLINK.length)
|
|
for _, dx in ipairs({ x, x + COURSE_W }) do
|
|
tex.draw_texture(self._id_bg, { x = dx * scale })
|
|
if blink then tex.draw_texture(self._id_blocks, { frame = blink.frame, x = dx * scale }) end
|
|
end
|
|
end
|
|
|
|
function MarioBGNormal:destroy() tex.unload_folder(COLLAB_PATH, "bg_normal/" .. self.name) end
|
|
|
|
-- Clear background: fades in over the normal course and races to the goal, speeding up
|
|
-- until it stops on the flagpole and castle. The flag slides down the pole, the castle's flag
|
|
-- goes up, then four fireworks burst over the castle in turn, each every 3 beats. Losing the
|
|
-- clear fades it out; the next clear starts the race again. Timed in beats.
|
|
local FADE = 0.5
|
|
local RACE, GOAL_X = 2.25, -4089 -- beats to reach the goal, and the course's scroll there
|
|
local FLAG_DOWN = { at = 2.25, beats = 0.75, y = 203 }
|
|
local CASTLE_AT = 2.25
|
|
local CASTLE_FLAG = { at = 3.025, beats = 0.75, y = -70 }
|
|
local FIREWORK = steps({ { 0, 0.075 }, { 1, 0.075 }, { 2, 0.35 }, { 0, 2.5, hide = true } })
|
|
local FIREWORKS_AT = { 4.225, 5, 5.75, 6.525 }
|
|
|
|
local MarioBGFever = {}
|
|
MarioBGFever.__index = MarioBGFever
|
|
|
|
function MarioBGFever.new(bpm)
|
|
local self = setmetatable({}, MarioBGFever)
|
|
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.ids = {}
|
|
for _, key in ipairs({ "background", "goal", "block", "castle_flag", "castle", "flag", "firework" }) do
|
|
self.ids[key] = id(key)
|
|
end
|
|
self.beat = 60000 / bpm
|
|
self.now = 0
|
|
self.alpha = 0
|
|
self.state = nil -- nil (hidden), "on", "off"
|
|
return self
|
|
end
|
|
|
|
function MarioBGFever:start()
|
|
self.state, self.t0, self.alpha = "on", self.now, 0
|
|
end
|
|
|
|
-- fades out from wherever the fade is
|
|
function MarioBGFever:stop()
|
|
if self.state == "on" then self.state, self.t_off, self.alpha_off = "off", self.now, self.alpha end
|
|
end
|
|
|
|
function MarioBGFever:update(current_time_ms, bpm)
|
|
if bpm then self.beat = 60000 / bpm end
|
|
self.now = current_time_ms
|
|
if self.state == "on" then
|
|
self.alpha = math.min(1, (self.now - self.t0) / self.beat / FADE)
|
|
elseif self.state == "off" then
|
|
self.alpha = self.alpha_off * math.max(0, 1 - (self.now - self.t_off) / self.beat / FADE)
|
|
if self.alpha <= 0 then self.state = nil end
|
|
end
|
|
end
|
|
|
|
function MarioBGFever:draw()
|
|
if not self.state then return end
|
|
local scale = tex.get_screen_scale()
|
|
local t = (self.now - self.t0) / self.beat
|
|
local race = math.min(t / RACE, 1)
|
|
local x = GOAL_X * race * race * scale
|
|
local fade = self.alpha
|
|
local function draw(key, p)
|
|
p.x, p.fade = (p.x or 0) + x, fade
|
|
tex.draw_texture(self.ids[key], p)
|
|
end
|
|
local function slide(m)
|
|
return math.max(0, math.min((t - m.at) / m.beats, 1)) * m.y * scale
|
|
end
|
|
draw("background", {})
|
|
draw("goal", {})
|
|
local blink = pose_at(BLINK, frames(self.now - self.t0) % BLINK.length)
|
|
if blink then draw("block", { frame = blink.frame }) end
|
|
if t >= CASTLE_FLAG.at then draw("castle_flag", { y = slide(CASTLE_FLAG) }) end
|
|
if t >= CASTLE_AT then draw("castle", {}) end
|
|
draw("flag", { y = slide(FLAG_DOWN) })
|
|
for i, at in ipairs(FIREWORKS_AT) do
|
|
local burst = t >= at and pose_at(FIREWORK, (t - at) % FIREWORK.length)
|
|
if burst then draw("firework", { index = i - 1, frame = burst.frame }) end
|
|
end
|
|
end
|
|
|
|
function MarioBGFever:destroy() tex.unload_folder(COLLAB_PATH, "bg_fever/" .. self.name) end
|
|
|
|
-- Fever: the line-up shoots up from below the lane and settles; the group then holds while
|
|
-- each character keeps its own loop, timed in beats from the start. Offsets are screen px from
|
|
-- each one's texture.json rest position.
|
|
|
|
-- three hops a beat apart, then a higher fourth one; `f` = up and down frames (and the fourth
|
|
-- hop's), `lift` = how far the up frame starts above the down one (the koopas)
|
|
local function hops(h, last, f, lift)
|
|
lift = lift or 0
|
|
local out = {}
|
|
for i = 0, 3 do
|
|
local top = i == 3 and last or h
|
|
table.insert(out, { i, f[i == 3 and 3 or 1] or f[1], 0, -lift, to = { 0, -top, "out" } })
|
|
table.insert(out, { i + 0.5, f[i == 3 and 4 or 2] or f[2] or f[1], 0, lift - top, to = { 0, 0, "in" } })
|
|
end
|
|
out.length = 4
|
|
return out
|
|
end
|
|
|
|
-- the blooper squats, springs up and right, sinks back, and does it again ten times, drifting
|
|
-- across and off the screen before starting over from the left
|
|
local function blooper()
|
|
local out = {}
|
|
for k = 0, 9 do
|
|
local x = k * 192
|
|
table.insert(out, { 2 * k, 0, x, 0, to = { x, 48 } })
|
|
table.insert(out, { 2 * k + 0.25, 1, x, 0, to = { x + 192, -192, "out" } })
|
|
table.insert(out, { 2 * k + 1, 1, to = { x + 192, 0 } })
|
|
end
|
|
out.length = 20
|
|
return out
|
|
end
|
|
|
|
local FLAP = steps({ { 0, 0.5 }, { 1, 0.5 } })
|
|
|
|
local CAST = {
|
|
-- cheep cheep waits below the right edge, leaps up-left and falls back out of sight
|
|
{ key = "cheep", frames = FLAP, path = {
|
|
{ 0, 0, 0, 0 },
|
|
{ 4, 0, 0, 0, to = { -534, -344 } },
|
|
{ 5, 0, to = { -930, -437 } },
|
|
{ 6, 0, to = { -1174, -350, "in" } },
|
|
{ 7, 0, to = { -1406, 0 } },
|
|
length = 8,
|
|
} },
|
|
-- Lakitu bobs on his cloud, turning to the front one beat in four
|
|
{ key = "lakitu", frames = steps({ { 0, 3 }, { 1, 1 } }), path = {
|
|
{ 0, 0, 0, 0, to = { 0, -20, "out" } },
|
|
{ 0.5, 0, to = { 0, 0, "in" } },
|
|
length = 1,
|
|
} },
|
|
-- a Bullet Bill crosses from right to left every 11 beats
|
|
{ key = "bullet", path = {
|
|
{ 0, 0, 0, 0 },
|
|
{ 2, 0, 0, 0, to = { -1514, 0 } },
|
|
{ 5.2, 0, -1514, 0 },
|
|
length = 11.075,
|
|
} },
|
|
{ key = "blooper", path = blooper() },
|
|
{ key = "toad", path = hops(12, 16.8, { 0 }) },
|
|
{ key = "peach", path = hops(12, 16.8, { 0 }) },
|
|
{ key = "buzzy", path = hops(22, 32.8, { 0, 1 }) },
|
|
{ key = "koopa", path = hops(41, 57.5, { 0, 1 }, 8) },
|
|
-- Bowser's hammer: thrown in an arc down to the left, gone a beat, back in his hand as he
|
|
-- jumps, then swung up behind his head; spinning while it flies
|
|
{ key = "hammer", size = 153.6, spin = steps({ { 0, 0.125 }, { 1, 0.125, rot = 180 }, { 0, 0.125, rot = 180 }, { 1, 0.125 } }),
|
|
path = {
|
|
{ 0, 0, -203, -159, spin = true, to = { -320, -86, "in" } },
|
|
{ 1, 0, spin = true, to = { -499, 258 } },
|
|
{ 2, 0, hide = true },
|
|
{ 3, 0, 0, 59, to = { 0, 0, "out" } },
|
|
{ 3.5, 0, 0, 0, spin = true, to = { -86, -112 } },
|
|
{ 3.75, 0, spin = true, to = { -189, -160 } },
|
|
length = 4,
|
|
} },
|
|
{ key = "bowser", path = hops(19.5, 58.6, { 0, 1, 2, 0 }) },
|
|
{ key = "red_koopa", path = hops(35.5, 52, { 0, 1 }, 8) },
|
|
}
|
|
|
|
-- whether the path pose on at t is a spinning one
|
|
local function spinning(path, t)
|
|
local current
|
|
for _, p in ipairs(path) do
|
|
if t >= p[1] then current = p end
|
|
end
|
|
return current and current.spin
|
|
end
|
|
|
|
local MarioFever = setmetatable({}, { __index = Fever })
|
|
MarioFever.__index = MarioFever
|
|
|
|
function MarioFever.new(bpm)
|
|
local self = Fever.new(bpm, COLLAB_PATH, { rise = 530, peak = 38, bounce = 30, drop = 545, keys = {} })
|
|
setmetatable(self, MarioFever)
|
|
self.ids = {}
|
|
for _, c in ipairs(CAST) do
|
|
self.ids[c.key] = tex.get_texture(COLLAB_PATH .. "/" .. self.name .. "/" .. c.key) or 0
|
|
end
|
|
self.now = 0
|
|
return self
|
|
end
|
|
|
|
function MarioFever:start()
|
|
Fever.start(self)
|
|
self.t0 = self.now
|
|
end
|
|
|
|
-- the group holds once in; only leaving ends it
|
|
function MarioFever:update(current_time_ms, bpm)
|
|
if bpm then self.beat = 60000 / bpm end
|
|
self.now = current_time_ms
|
|
if not self.state then return end
|
|
for _, m in ipairs(self.moves) do m:update(current_time_ms) end
|
|
if self.state == "out" and self.moves[#self.moves].is_finished then self.state = nil end
|
|
end
|
|
|
|
function MarioFever:draw()
|
|
if not self.state then return end
|
|
local scale, y = tex.get_screen_scale(), self:y()
|
|
local t = (self.now - self.t0) / self.beat
|
|
for _, c in ipairs(CAST) do
|
|
local pose = pose_at(c.path, t % c.path.length)
|
|
if pose then
|
|
local frame, rot = pose.frame, 0
|
|
if c.frames then frame = pose_at(c.frames, t % c.frames.length).frame end
|
|
if c.spin and spinning(c.path, t % c.path.length) then
|
|
local spin = pose_at(c.spin, t % c.spin.length)
|
|
frame, rot = spin.frame, spin.rot
|
|
end
|
|
local p = { frame = frame, x = pose.x * scale, y = pose.y * scale + y }
|
|
if rot ~= 0 then -- turned about its centre
|
|
local o = c.size / 2 * scale
|
|
p.x, p.y, p.origin, p.rotation = p.x + o, p.y + o, { o, o }, rot
|
|
end
|
|
tex.draw_texture(self.ids[c.key], p)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Dancers
|
|
-- Timed in beats; y is the offset from Lumen's dancer line (texture.json y 690), images
|
|
-- centred on it. Mario and Luigi jump in, walk a step left and back, then jump twice turning
|
|
-- round; leaving, they jump and drop off.
|
|
local WALK = { { 1, 0.125 }, { 2, 0.125 }, { 1, 0.125 }, { 3, 0.125 } }
|
|
local function walk(mirror)
|
|
local out = {}
|
|
for i = 1, 15 do out[i] = { WALK[(i - 1) % 4 + 1][1], 0.125, mirror = mirror } end
|
|
return out
|
|
end
|
|
|
|
local function jump_in(from, top, land, frame)
|
|
return {
|
|
{ 0, frame, 0, from, mirror = true, to = { 0, top, "out" } },
|
|
{ 0.825, frame, mirror = true, to = { 0, land, "in" } },
|
|
{ 1.5, frame + 1, 0, land, mirror = true },
|
|
beats = 2,
|
|
}
|
|
end
|
|
|
|
local BROS = {
|
|
origin = { 64, 64 },
|
|
enter = jump_in(235, -175, -65, 0),
|
|
dance = {
|
|
steps({ { 0, 0.125, mirror = true } }, walk(false), { { 0, 0.125 } }, walk(true),
|
|
{ { 4, 1.75, mirror = true }, { 5, 0.125, mirror = true }, { 5, 0.125 } },
|
|
{ { 4, 1.75 }, { 5, 0.125 }, { 5, 0.125, mirror = true } }),
|
|
move = {
|
|
{ 0, 0, 0, -65, to = { -20, -65 } },
|
|
{ 2, 0, to = { 0, -65 } },
|
|
{ 4, 0, to = { 0, -125, "out" } },
|
|
{ 4.775, 0, to = { 0, -65, "in" } },
|
|
{ 5.75, 0, 0, -65 },
|
|
{ 6, 0, to = { 0, -125, "out" } },
|
|
{ 6.775, 0, to = { 0, -65, "in" } },
|
|
{ 7.75, 0, 0, -65 },
|
|
length = 8,
|
|
},
|
|
},
|
|
leave = {
|
|
{ 0, 0, 0, -65, key = "end", mirror = true, to = { 0, -175, "out" } },
|
|
{ 0.5, 0, key = "end", mirror = true, to = { 0, 165, "in" } },
|
|
{ 1.5, 0, hide = true },
|
|
beats = 2,
|
|
},
|
|
}
|
|
|
|
-- frames played in turn every `step` beats over `beats`
|
|
local function cycle(beats, step, list, fields)
|
|
local out = {}
|
|
for i = 0, math.ceil(beats / step) - 1 do
|
|
local s = { list[i % #list + 1], math.min(step, beats - i * step) }
|
|
for k, v in pairs(fields or {}) do s[k] = v end
|
|
out[#out + 1] = s
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- Goomba hops in, toddles left and back facing the other way, then hops twice; leaving, it
|
|
-- flips over and falls
|
|
local GOOMBA = {
|
|
origin = { 64, 64 },
|
|
enter = {
|
|
{ 0, 0, 0, 185, to = { 0, -185, "out" } },
|
|
{ 0.825, 0, to = { 0, -65, "in" } },
|
|
{ 1.5, 1, 0, -65, to = { 0, -75, "out" } },
|
|
{ 1.75, 1, to = { 0, -65, "in" } },
|
|
beats = 2,
|
|
},
|
|
dance = {
|
|
steps(cycle(8, 0.375, { 0, 1 }, { mirror = true })),
|
|
move = {
|
|
{ 0, 0, 0, -65, to = { -20, -65 } },
|
|
{ 2, 0, to = { 0, -65 } },
|
|
{ 4, 0, to = { 0, -81, "out" } },
|
|
{ 4.75, 0, to = { 0, -65, "in" } },
|
|
{ 5.5, 0, 0, -65 },
|
|
{ 6, 0, to = { 0, -81, "out" } },
|
|
{ 6.75, 0, to = { 0, -65, "in" } },
|
|
{ 7.5, 0, 0, -65 },
|
|
length = 8,
|
|
},
|
|
},
|
|
leave = {
|
|
steps(cycle(1.5, 0.375, { 0, 1 }, { key = "loop", rot = 180 })),
|
|
move = {
|
|
{ 0, 0, 0, -65, to = { 0, -155, "out" } },
|
|
{ 0.5, 0, to = { 0, 185, "in" } },
|
|
length = 1.5,
|
|
},
|
|
beats = 2,
|
|
},
|
|
}
|
|
|
|
-- Bowser stomps in, sways left and back, then jumps twice with a little hop after each;
|
|
-- leaving, he jumps, flips over and falls
|
|
local function bowser_jump(at)
|
|
return {
|
|
{ at, 0, to = { 0, -158, "out" } },
|
|
{ at + 1, 0, to = { 0, -128, "in" } },
|
|
{ at + 1.65, 0, to = { 0, -138, "out" } },
|
|
{ at + 1.825, 0, to = { 0, -128, "in" } },
|
|
}
|
|
end
|
|
|
|
local BOWSER = {
|
|
origin = { 128, 128 },
|
|
enter = jump_in(243, -172, -129, 0),
|
|
dance = {
|
|
steps(cycle(2, 0.5, { 2, 1 }, { mirror = true }), cycle(2, 0.5, { 2, 1 }),
|
|
cycle(2, 0.5, { 3, 1 }), cycle(2, 0.5, { 0, 2 }, { mirror = true })),
|
|
move = concat({
|
|
{ 0, 0, 0, -128, to = { -20, -128 } },
|
|
{ 2, 0, to = { 0, -128 } },
|
|
}, bowser_jump(4), bowser_jump(6)),
|
|
},
|
|
leave = {
|
|
{ 0, 2, 0, -128, key = "loop", mirror = true, to = { 0, -166, "out" } },
|
|
{ 0.475, 2, key = "loop", rot = 180, to = { 0, 194, "in" } },
|
|
{ 1.475, 2, hide = true },
|
|
beats = 2,
|
|
},
|
|
}
|
|
BOWSER.dance.move.length = 8
|
|
|
|
-- The paratroopa flaps in, hops in arcs left, back, right and back (turning round), then
|
|
-- flaps up and down twice; leaving, it drops into its shell, flips and falls
|
|
local function flap(beats, mirror) return cycle(beats, 0.25, { 0, 1 }, { mirror = mirror }) end
|
|
local PARATROOPA = {
|
|
origin = { 64, 99.5 },
|
|
enter = {
|
|
steps(flap(2, true)),
|
|
move = {
|
|
{ 0, 0, 0, 60, to = { 0, -128, "out" } },
|
|
{ 0.825, 0, to = { 0, -100, "in" } },
|
|
{ 1.5, 0, to = { 0, -116, "out" } },
|
|
{ 1.75, 0, to = { 0, -100, "in" } },
|
|
length = 2,
|
|
},
|
|
beats = 2,
|
|
},
|
|
dance = {
|
|
steps(flap(1, false), flap(2, true), flap(5, false)),
|
|
move = {
|
|
{ 0, 0, 0, -100, to = { -20, -150, "out" } },
|
|
{ 0.45, 0, to = { -45, -100, "in" } },
|
|
{ 1, 0, to = { -20, -150, "out" } },
|
|
{ 1.45, 0, to = { 0, -100, "in" } },
|
|
{ 2, 0, to = { 25, -159, "out" } },
|
|
{ 2.45, 0, to = { 47, -100, "in" } },
|
|
{ 3, 0, to = { 25, -159, "out" } },
|
|
{ 3.45, 0, to = { 0, -100, "in" } },
|
|
{ 4, 0, to = { 0, -150, "out" } },
|
|
{ 5, 0, to = { 0, -100, "in" } },
|
|
{ 6, 0, to = { 0, -150, "out" } },
|
|
{ 7, 0, to = { 0, -100, "in" } },
|
|
length = 8,
|
|
},
|
|
},
|
|
leave = {
|
|
{ 0, 0, 0, -90, key = "end", mirror = true, to = { 0, -150, "out" } },
|
|
{ 0.5, 0, key = "end", rot = 180, to = { 0, 10, "in" } },
|
|
{ 1.45, 0, hide = true },
|
|
beats = 2,
|
|
},
|
|
}
|
|
|
|
-- skin index = Lumen's dancerN - 1: Mario, Luigi, Goomba, Bowser, the paratroopa
|
|
local MARIO_DANCERS = Dancer.define({ [0] = BROS, [1] = BROS, [2] = GOOMBA, [3] = BOWSER, [4] = PARATROOPA })
|
|
|
|
-- 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). Dancer1 is drawn on top.
|
|
local DANCER_CENTERS = { 213, 427, 640, 853, 1067 }
|
|
local DANCER_DRAW_ORDER = { 5, 1, 4, 2, 3 }
|
|
|
|
-- Chibis for good hits: Mario, Luigi or a koopa hops in at full size and runs
|
|
local MARIO_RUN = { { 0, 1 }, { 0.125, 2 }, { 0.25, 1 }, { 0.375, 3 } }
|
|
local KOOPA_RUN = { { 0, 0 }, { 0.25, 1 } }
|
|
local MARIO_CHIBIS = {
|
|
{ key = "0", run = { y = 16 }, start_size = 1, period = 0.5, poses = MARIO_RUN },
|
|
{ key = "1", run = { y = 16 }, start_size = 1, period = 0.5, poses = MARIO_RUN },
|
|
{ key = "2", run = { y = 16 }, start_size = 1, period = 0.5, poses = KOOPA_RUN },
|
|
{ key = "3", run = { y = 16 }, start_size = 1, period = 0.5, poses = KOOPA_RUN },
|
|
}
|
|
|
|
-- Drumroll: every other hit, two ? blocks appear at random spots in the lower half; each
|
|
-- bumps up as a spinning coin pops out of it, and both vanish after 21 frames. In frames.
|
|
local RENDA_LIMIT = 25
|
|
local BUMP = {
|
|
{ 0, 0, 0, 0, to = { 0, -10, "out" } },
|
|
{ 6, 0, to = { 0, 0, "in" } },
|
|
{ 11, 0, 0, 0 },
|
|
length = 21,
|
|
}
|
|
local COIN = {
|
|
{ 0, 0, 0, -46, to = { 0, -116, "out" } },
|
|
{ 11, 0, to = { 0, -96, "in" } },
|
|
length = 21,
|
|
}
|
|
|
|
local MarioRenda = {}
|
|
MarioRenda.__index = MarioRenda
|
|
|
|
function MarioRenda.new()
|
|
local self = setmetatable({}, MarioRenda)
|
|
tex.load_folder(COLLAB_PATH, "renda")
|
|
self._id = tex.get_texture(COLLAB_PATH .. "/renda/renda_0") or 0
|
|
self.blocks = {}
|
|
self.calls = 0
|
|
self.now = 0
|
|
return self
|
|
end
|
|
|
|
function MarioRenda:add_renda()
|
|
self.calls = self.calls + 1
|
|
if self.calls % 2 == 0 then return end
|
|
for _ = 1, 2 do
|
|
if #self.blocks >= RENDA_LIMIT then return end
|
|
table.insert(self.blocks, { t0 = self.now, x = math.random(0, 1279), y = math.random(420, 719) })
|
|
end
|
|
end
|
|
|
|
function MarioRenda:update(current_time_ms)
|
|
self.now = current_time_ms
|
|
for i = #self.blocks, 1, -1 do
|
|
if frames(self.now - self.blocks[i].t0) >= BUMP.length then table.remove(self.blocks, i) end
|
|
end
|
|
end
|
|
|
|
function MarioRenda:draw()
|
|
local scale = tex.get_screen_scale()
|
|
for _, b in ipairs(self.blocks) do
|
|
local f = frames(self.now - b.t0)
|
|
local bump, coin = pose_at(BUMP, f), pose_at(COIN, f)
|
|
if bump then
|
|
tex.draw_texture(self._id, { frame = 0, x = b.x * scale, y = (b.y + bump.y) * scale })
|
|
end
|
|
if coin then
|
|
tex.draw_texture(self._id, { frame = 1 + math.floor(f / 2) % 4, x = b.x * scale, y = (b.y + coin.y) * scale })
|
|
end
|
|
end
|
|
end
|
|
|
|
function MarioRenda:destroy() tex.unload_folder(COLLAB_PATH, "renda") end
|
|
|
|
-- Background
|
|
-- Lumen's dodai is fully transparent and the course already paints its own ground, so there
|
|
-- is no footer.
|
|
|
|
local MarioBackground = {}
|
|
MarioBackground.__index = MarioBackground
|
|
|
|
function MarioBackground.new(player_num, bpm)
|
|
local self = setmetatable({}, MarioBackground)
|
|
self.donbg = DonBGBase.new(1, player_num, COLLAB_PATH, DONBG_SPEC)
|
|
self.bg_normal = MarioBGNormal.new()
|
|
self.bg_fever = MarioBGFever.new(bpm)
|
|
self.fever = MarioFever.new(bpm)
|
|
self.dancer = Dancer.BaseDancerGroup.new(0, bpm, MAX_DANCERS, COLLAB_PATH, MARIO_DANCERS)
|
|
table.sort(self.dancer.dancers, function(a, b) return a.index < b.index end)
|
|
self.renda = MarioRenda.new()
|
|
self.chibi = ChibiController.new(bpm, COLLAB_PATH, 1)
|
|
self.is_clear = false
|
|
self.is_rainbow = false
|
|
self.last_milestone = 1
|
|
return self
|
|
end
|
|
|
|
function MarioBackground: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 MarioBackground:add_chibi(player_num)
|
|
BaseChibi.add(self.chibi, MARIO_CHIBIS[math.random(#MARIO_CHIBIS)], COLLAB_PATH, player_num)
|
|
end
|
|
|
|
function MarioBackground:handle_good(player_num) self:add_chibi(player_num) end
|
|
|
|
function MarioBackground:handle_ok(player_num) self:add_chibi(player_num) end
|
|
|
|
function MarioBackground:handle_bad(player_num) self.chibi:add_chibi(player_num, true) end
|
|
|
|
function MarioBackground:handle_drumroll(player_num) self.renda:add_renda() end
|
|
|
|
function MarioBackground:handle_balloon(player_num) self.renda:add_renda() end
|
|
|
|
function MarioBackground: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 MarioBackground:draw_back()
|
|
self.bg_normal:draw()
|
|
self.bg_fever:draw()
|
|
self.donbg:draw()
|
|
self.renda:draw()
|
|
self.dancer:draw_slots(DANCER_CENTERS, DANCER_DRAW_ORDER)
|
|
self.fever:draw()
|
|
self.chibi:draw()
|
|
end
|
|
|
|
function MarioBackground:draw_fore() end
|
|
|
|
function MarioBackground: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 MarioBackground
|