-- Ending banner (clear / full combo / donderful / fail), replayed from the cabinet -- movie data exported by scratchpad/lyric/clip_export.py: -- Scripts/game/ending_anim/ending_.lua -- frames[f+1] = {{shape, tx, ty, sx, sy, rot, a}, ...} -- Graphics/game/ending__anim/s.png -- one texture per shape -- The clip plays once from frame 0 and then loops from `loop_at`, at 60 fps, on the -- 1920x264 stage the original places at the lane (screen y 276 for 1P; the 2P -- lane adds the skin's game_2p_offset). The engine keeps its own ending -- animation running for the sounds and hands the drawing to this object through -- Background:handle_ending / draw_ending. local Ending = {} Ending.__index = Ending local FRAME_MS = 1000.0 / 60.0 local STAGE_Y = 276 -- the movie root places every ending clip at (1209, 109.5) on its 1920x264 stage; -- the exported frames are in clip-local coordinates, so add that placement local CLIP_X, CLIP_Y = 1209, 109.5 local CLIPS = { donderful = "ending_donderful", full_combo = "ending_fullcombo", clear = "ending_clear", fail = "ending_fail" } function Ending.available(kind) local name = CLIPS[kind] if not name then return false end local ok, data = pcall(require, "game/ending_anim/" .. name) return ok and type(data) == "table" and data.frames ~= nil end function Ending.new(kind, player_num, path) local name = CLIPS[kind] local ok, data = pcall(require, "game/ending_anim/" .. name) if not ok or type(data) ~= "table" or not data.frames then return nil end local self = setmetatable({}, Ending) self.kind = kind self.subset = name .. "_anim" self.path = path or "game" tex.load_folder(self.path, self.subset) self.data = data self.ids = {} self.t0 = nil self.y = STAGE_Y if player_num == 2 then local off = tex.get_skin_config("game_2p_offset") self.y = STAGE_Y + ((off and off.y) or 0) end return self end function Ending:id_of(shape) local id = self.ids[shape] if id == nil then id = tex.get_id(self.subset, "s" .. shape) or false self.ids[shape] = id end return id or nil end function Ending:frame(current_ms) if not self.t0 then self.t0 = current_ms end local f = math.floor((current_ms - self.t0) / FRAME_MS) local n, loop_at = self.data.nframes, self.data.loop_at if f >= n then local span = n - loop_at f = (span > 0) and (loop_at + (f - loop_at) % span) or (n - 1) end return f end function Ending:draw(current_ms) local rows = self.data.frames[self:frame(current_ms) + 1] if not rows then return end local shapes = self.data.shapes for i = 1, #rows do local r = rows[i] local s = shapes[r[1]] local id = s and self:id_of(r[1]) if id then local sx, sy = r[4], r[5] tex.draw_texture(id, { x = CLIP_X + r[2], y = self.y + CLIP_Y + r[3], x2 = s[1] * (sx - 1), y2 = s[2] * (sy - 1), origin = { s[3] * sx, s[4] * sy }, rotation = r[6], fade = r[7], }) end end end function Ending:destroy() tex.unload_folder(self.path, self.subset) end return Ending