local M = {} M.__index = M local SCR = "background" local DATA = {} local OP_DEFAULTS = { nil, nil, nil, nil, nil, 0, 0, 1, 1, 1, 1, 0, 0, 0 } local function expand(d) local ops = d.ops for _, op in ipairs(ops) do for j = #op + 1, 14 do op[j] = OP_DEFAULTS[j] end end for _, st in pairs(d.states) do for _, part in ipairs({ "intro", "loop" }) do local frames = st[part] for f = 1, #frames do local ids, flat, k = frames[f], {}, 0 for i = 1, #ids do local op = ops[ids[i]] for j = 1, 14 do flat[k + j] = op[j] end k = k + 14 end frames[f] = flat end end end d.ops, d.packed = nil, nil end -- id -> clip; loads its texture folder the first time function M.get(id) local c = DATA[id] if c == nil then local ok, d = pcall(require, "background.t12." .. id) if ok and type(d) == "table" then if d.packed then expand(d) end tex.load_folder(SCR, d.tex) c = setmetatable({ d = d, id = id, cache = {} }, M) else c = false end DATA[id] = c end return c or nil end function M.unload_all() for _, c in pairs(DATA) do if c then tex.unload_folder(SCR, c.d.tex) end end DATA = {} end function M:texture(i) local t = self.cache[i] if t == nil then t = tex.get_texture(string.format("%s/%s/t%02d", SCR, self.d.tex, i)) or false self.cache[i] = t end return t or nil end -- the white copy (same alpha) for the colour offset pass; the texture itself if there is none function M:texture_w(i) self.cache_w = self.cache_w or {} local t = self.cache_w[i] if t == nil then t = tex.get_texture(string.format("%s/%s/t%02d_w", SCR, self.d.tex, i)) or false self.cache_w[i] = t end return t or self:texture(i) end function M:state(name) return self.d.states[name] end -- frame f (0-based) of a state: the intro once, then the loop; hold = keep the last intro frame -- instead of looping (one-shots). Returns the op list or nil. function M:frame(name, f, hold) local st = self.d.states[name] if not st then return nil end f = math.max(0, math.floor(f)) local ni, nl = #st.intro, #st.loop if f < ni then return st.intro[f + 1] end if nl == 0 then return st.intro[ni] end if hold then return st.loop[math.min(f - ni, nl - 1) + 1] end return st.loop[(f - ni) % nl + 1] end -- loop phase from an absolute frame counter (keeps a looping state on the beat grid) function M:loop_frame(name, fabs) local st = self.d.states[name] if not st or #st.loop == 0 then return nil end return st.loop[math.floor(fabs) % #st.loop + 1] end function M:length(name) local st = self.d.states[name] return st and (#st.intro + #st.loop) or 0 end local function set(...) local t = {} for _, i in ipairs({ ... }) do t[i] = true end return t end local ADDITIVE = { t12_dbg_dance_bg_02 = set(0, 2), -- light pillars t12_dbg_dance_bg_03 = set(1), -- harbour bokeh t12_dbg_dance_bg_04 = set(9, 10), -- will-o'-wisps t12_dbg_dance_bg_05 = set(1), -- lantern glow t12_dbg_dance_bg_07 = set(4), -- soft star glow t12_dbg_dance_bg_08 = set(0, 5, 12), t12_dbg_dance_bg_09 = set(0, 1, 5), t12_dbg_dance_bg_11 = set(0, 2, 4, 11), -- candle / kamakura glow t12_dbg_dance_bg_13 = set(0, 1, 2, 3, 4), -- bokeh, lantern-string glow (black fringe: dark red blotch) t12_dbg_dance_bg_idol = set(1, 2, 3), -- spotlight beams t12_fever_fever_01 = set(0), -- glow orb (brown edge: brown halo) t12_fever_fever_02 = set(0), t12_chara_m_don = set(0, 1), -- eye glow, lightning t12_chara_m_katsu = set(0, 1), t12_geki_m_don_geki = set(0), -- lightning t12_geki_m_katsu_geki = set(0), } -- one params / colour table reused for every op (tex.draw_texture reads it on the spot): the -- background draws a few hundred ops a frame, and two new tables per op were most of the garbage local floor = math.floor local P, COLOR, ADD_COLOR = {}, { 0, 0, 0, 255 }, { 0, 0, 0, 255 } function M:draw(ops, ox, oy, K, fade, flipx) if not ops then return end fade = fade or 1 local size = self.d.size local add = ADDITIVE[self.d.tex] local p = P for n = 1, #ops, 14 do local t = self:texture(ops[n]) if t then local sz = size[ops[n] + 1] local w, h = ops[n + 3] * K, ops[n + 4] * K local x, y = (ox + ops[n + 1]) * K, (oy + ops[n + 2]) * K local mirror = (ops[n + 6] == 1) and "vertical" or nil if flipx then x = (ox + self.d.w - ops[n + 1]) * K - w mirror = "horizontal" -- (no combined mirror in the engine) end p.x, p.y, p.x2, p.y2 = x, y, w - sz[1], h - sz[2] p.rotation, p.mirror, p.fade = ops[n + 5], mirror, ops[n + 7] * fade COLOR[1], COLOR[2], COLOR[3] = floor(ops[n + 8] * 255 + 0.5), floor(ops[n + 9] * 255 + 0.5), floor(ops[n + 10] * 255 + 0.5) p.color = COLOR p.blend = (add and add[ops[n]]) and "additive" or nil tex.draw_texture(t, p) local ar, ag, ab = ops[n + 11], ops[n + 12], ops[n + 13] if ar + ag + ab > 0.01 then p.blend = "additive" ADD_COLOR[1], ADD_COLOR[2], ADD_COLOR[3] = floor(ar * 255 + 0.5), floor(ag * 255 + 0.5), floor(ab * 255 + 0.5) p.color = ADD_COLOR tex.draw_texture(self:texture_w(ops[n]), p) end end end end return M