-- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/renda.lua: Renda1 origin from x2[1]/y2[1]; frame from frame_count -- The three roll effects, replayed the way the original places them: a piece is -- spawned somewhere over the LOWER backdrop (x 6..1100, y 540..1020 -- the region -- the original's spawn box covers below the lane) and flies for 120 frames at -- 60 Hz along its movie's own path: -- renda_0 birds (chidori) +17.3 px/frame right, -10.9 px/frame up -- renda_1 shuriken (shuriken) +17.3 px/frame right, level, spinning -6 deg/frame -- renda_2 arrow (hamaya) +13.1 px/frame right, -13.8 px/frame up -- They are drawn before the dancers (see background.lua), as the original does. local FRAME_MS = 1000.0 / 60.0 local LIFE = 120 local SPAWN_X0, SPAWN_X1 = 6, 1100 local SPAWN_Y0, SPAWN_Y1 = 540, 1020 local BaseRenda = {} BaseRenda.__index = BaseRenda function BaseRenda.new(index, vx, vy, spin) local self = setmetatable({}, BaseRenda) self.name = "renda_" .. index self.vx, self.vy, self.spin = vx, vy, spin or 0 self.x0 = math.random(SPAWN_X0, SPAWN_X1) self.y0 = math.random(SPAWN_Y0, SPAWN_Y1) self.frames = 0 self.last_ms = nil local info = tex.get_texture_info("renda", self.name) self.frame = math.random(0, ((info and info.frame_count) or 1) - 1) self.w = (info and info.x2 and info.x2[1]) or 0 self.h = (info and info.y2 and info.y2[1]) or 0 self._id = tex.get_id("renda", self.name) -- nil = missing (ROUND 60: id 0 is valid) return self end function BaseRenda:update(current_time_ms) if self.last_ms then local dt = current_time_ms - self.last_ms if dt > 0 and dt < 500 then self.frames = self.frames + dt / FRAME_MS end end self.last_ms = current_time_ms end function BaseRenda:finished() return self.frames >= LIFE end function BaseRenda:draw(y_offset) if not self._id or self:finished() then return end local f = self.frames local cx = self.x0 + self.vx * f local cy = self.y0 + self.vy * f + (y_offset or 0) if self.spin ~= 0 then tex.draw_texture(self._id, { frame = self.frame, x = cx, y = cy, origin = { self.w * 0.5, self.h * 0.5 }, rotation = self.spin * f, }) else tex.draw_texture(self._id, { frame = self.frame, x = cx - self.w * 0.5, y = cy - self.h * 0.5 }) end end local Renda0 = setmetatable({}, {__index = BaseRenda}) Renda0.__index = Renda0 function Renda0.new(index) return setmetatable(BaseRenda.new(index, 17.3, -10.9), Renda0) end local Renda1 = setmetatable({}, {__index = BaseRenda}) Renda1.__index = Renda1 function Renda1.new(index) return setmetatable(BaseRenda.new(index, 17.3, 0, -6), Renda1) end local Renda2 = setmetatable({}, {__index = BaseRenda}) Renda2.__index = Renda2 function Renda2.new(index) return setmetatable(BaseRenda.new(index, 13.1, -13.8), Renda2) end -- ROUND 52 (r52-lua-divergence-fixes): the cabinet's renda effect is a FIXED -- round-robin pool, not an unbounded per-hit spawner. the reference build -- `EnsoGraphicRendaEffect` (EnsoGraphicRendaEffect.obj.c): -- * the pool is 25 movie clips -- `EffectPlayerBase::Initialize(a1, 25, ...)` -- (:2379); -- * `Process` (0x14012BDA0, :2270-2295) arms AT MOST ONE -- `EffectPlayer::Start` per frame (a single flag at +88, set while an -- active kind-6/9 roll note is in flight, consumed once); -- * `EffectPlayer::Start` (0x14012C640, :2848-2860) takes slot -- `counter % pool_size` and REPLACES whatever is there (a still-playing -- clip is swapped out via DoSwaping, not skipped), then counter++. -- The skin used to push one object per hit with no cap (~40 live at -- 20 hit/s over the 1500 ms crossing), so the shower read "how fast you hit" -- instead of "a roll is happening". Ported: pool of 25 slots, at most one -- spawn per rendered frame, round-robin slot replacement. (The engine hook -- is still per-hit -- there is no per-frame "roll active" Lua signal -- so a -- slow roll spawns fewer than the cabinet's one-per-frame; the cap and the -- steady-state density now match.) -- Observed on the original: about ten pieces are ever live at once -- the oldest -- is dropped as soon as an eleventh starts, before it leaves the screen. local POOL_SIZE = 10 local RendaController = {} RendaController.__index = RendaController -- ROUND 58 (r58-2p-background-polish): optional y_offset -- the cabinet's 2P -- enso_post set (target 1) places TWO renda_effect instances, playerNo 0 at -- y=0 and playerNo 1 at y=804 (the 2P donbg band), pure translate. A second -- controller with y_offset = game_2p_offset.y is that pn1 instance. function RendaController.new(path, index, y_offset) local self = setmetatable({}, RendaController) self.rendas = {} -- slot -> live effect (holes allowed) self.rr = 0 -- round-robin counter (EffectPlayer +76) self.spawned_this_frame = false self.path = path or "background" self.index = (index ~= nil) and index or math.random(0, 2) self.y_offset = y_offset or 0 tex.load_folder(self.path, "renda") return self end function RendaController:add_renda() -- <=1 Start per frame, exactly like Process's single +88 flag. if self.spawned_this_frame then return end self.spawned_this_frame = true local classes = {Renda0, Renda1, Renda2} local cls = classes[self.index + 1] local slot = (self.rr % POOL_SIZE) + 1 self.rr = self.rr + 1 self.rendas[slot] = cls.new(self.index) -- replace-in-slot (DoSwaping) end function RendaController:update(current_time_ms) self.spawned_this_frame = false for slot = 1, POOL_SIZE do local r = self.rendas[slot] if r then r:update(current_time_ms) if r:finished() then self.rendas[slot] = nil end end end end function RendaController:draw() for slot = 1, POOL_SIZE do local r = self.rendas[slot] if r then r:draw(self.y_offset) end end end function RendaController:destroy() tex.unload_folder(self.path, "renda") end return RendaController