Gauge = {} Gauge.__index = Gauge local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end -- Port of Gauge::draw / Gauge::draw_dan. C++ keeps the points logic and hands the -- numbers over every frame; this only decides what gets drawn where. -- opts (all optional): screen ("game" default / "result"), player_str ("1p"/"2p"), scale, -- footer (draw gauge/footer), no_border. The result screen reuses the HUD draw through these. function Gauge.new(is_2p, dan, string_diff, opts) local self = setmetatable({}, Gauge) opts = opts or {} self.dan = dan self.scale = opts.scale self.no_border = opts.no_border self.base_fade = 1.0 local screen = opts.screen or "game" local cells = tex.get_skin_config("gauge_cells") self.bar_units = (cells and cells.x and cells.x > 0) and math.floor(cells.x + 0.5) or 87 local p = opts.player_str and (opts.player_str .. "_") or (is_2p and "2p_" or "1p_") local function t(name) return tex.get_texture(screen .. "/" .. name) end if dan then self.bar = t("gauge_dan/" .. p .. "bar") self.bar_fade = t("gauge_dan/" .. p .. "bar_fade") self.border = t("gauge_dan/border") self.unfilled = t("gauge_dan/" .. p .. "unfilled") self.rainbow = t("gauge_dan/rainbow") self.overlay = t("gauge_dan/overlay") self.fire = t("gauge_dan/tamashii_fire") self.tamashii = t("gauge_dan/tamashii") self.tamashii_overlay = t("gauge_dan/tamashii_overlay") self.tamashii_dark = t("gauge_dan/tamashii_dark") self.bar_width = tex.get_texture_info("gauge_dan", p .. "bar").width else self.border = t("gauge/border" .. string_diff) self.unfilled = t("gauge/" .. p .. "unfilled" .. string_diff) self.bar = t("gauge/" .. p .. "bar") self.clear_transition = t("gauge/bar_clear_transition") self.clear_top = t("gauge/bar_clear_top") self.clear_bottom = t("gauge/bar_clear_bottom") self.rainbow = t("gauge/rainbow" .. string_diff) self.clear_transition_fade = t("gauge/bar_clear_transition_fade") self.clear_fade = t("gauge/bar_clear_fade") self.bar_fade = t("gauge/" .. p .. "bar_fade") self.overlay = t("gauge/overlay" .. string_diff) self.fire = t("gauge/tamashii_fire") self.tamashii = t("gauge/tamashii") self.tamashii_overlay = t("gauge/tamashii_overlay") self.tamashii_dark = t("gauge/tamashii_dark") -- language variants (clear_ja ...) are resolved by tex.get_texture self.clear = t("gauge/clear") self.clear_dark = t("gauge/clear_dark") self.bar_width = (tex.get_texture_info("gauge", p .. "bar") or {}).width or 0 if opts.footer then self.footer = t("gauge/footer") end self.art_tier = (string_diff == "_easy") and 0 or (string_diff == "_normal") and 1 or 2 end return self end -- cell bookkeeping shared by both gauges: how many cells are solid, and the alpha of the -- sprite on the cell that was just filled (nil when there is none to show). function Gauge:cells(points, previous_points, max_points, anim_active, anim_alpha) local units = self.bar_units local length = math.floor(points * units / max_points) local previous = math.floor(previous_points * units / max_points) local fade_in = tex.get_option("gauge_cell_fade_in") local pending = length <= units and length > previous and anim_active local solid = (fade_in and pending) and length - 1 or length local alpha = fade_in and 1.0 - anim_alpha or anim_alpha local show_up if fade_in then show_up = pending else show_up = length <= units and length > previous end return length, solid, alpha, show_up, pending, fade_in end -- every draw goes through here so the result screen's scale / fade-in apply uniformly function Gauge:blit(t, p) if self.scale then -- draw_texture scales the texture but not x / x2, and cell offsets are in bar-texture -- pixels, so they have to follow the scale or the fill outgrows the scaled groove p.scale = (p.scale or 1.0) * self.scale if p.x then p.x = p.x * self.scale end if p.x2 then p.x2 = p.x2 * self.scale end end if p.cap then p.cap = nil; p.fade = math.min(p.fade, self.base_fade) -- overlays: cap, not multiply elseif self.base_fade ~= 1.0 then p.fade = (p.fade or 1.0) * self.base_fade end tex.draw_texture(t, p) end function Gauge:draw(y, points, previous_points, max_points, clear_points, is_clear, is_rainbow, anim_active, anim_alpha, rainbow_fade, rainbow_frac, fire_frame) if self.dan then return self:draw_dan(points, previous_points, max_points, is_rainbow, anim_active, anim_alpha, rainbow_fade, rainbow_frac, fire_frame) end local mirrored = y > tex.get_screen_height() / 2 self:render(y, mirrored and "vertical" or nil, mirrored and 1 or 0, points, previous_points, max_points, clear_points, is_clear, is_rainbow, anim_active, anim_alpha, rainbow_fade, rainbow_frac, fire_frame) end -- result screen: no lane offset, no mirroring, no gauge-up cell animation. base_fade is the -- screen's gauge fade-in; idx picks the 1p/2p layout. function Gauge:draw_result(idx, base_fade, points, max_points, clear_points, is_clear, is_rainbow, rainbow_frac, fire_frame) self.base_fade = base_fade self:render(0, nil, idx, points, points, max_points, clear_points, is_clear, is_rainbow, false, 0.0, 1.0, rainbow_frac, fire_frame) end function Gauge:render(y, mirror, idx, points, previous_points, max_points, clear_points, is_clear, is_rainbow, anim_active, anim_alpha, rainbow_fade, rainbow_frac, fire_frame) if not self.no_border then self:blit(self.border, { mirror = mirror, y = y, index = idx }) end self:blit(self.unfilled, { mirror = mirror, y = y, index = idx }) local units, bw = self.bar_units, self.bar_width local length, solid, alpha, show_up, pending, fade_in = self:cells(points, previous_points, max_points, anim_active, anim_alpha) local clear_point = clamp(clear_points * units // max_points, 1, units) if solid > 0 then self:blit(self.bar, { y = y, index = idx, x2 = math.min(solid * bw, (clear_point - 1) * bw) - bw }) end -- first gold cell lights exactly when the gauge is cleared (not by a cell-count test: -- on 87 cells 8000 -> 69.6 would light it a cell early/late vs. the clear state) if is_clear and not (fade_in and pending and length == clear_point) then self:blit(self.clear_transition, { mirror = mirror, x = (clear_point - 1) * bw, y = y, index = idx }) end -- gold zone: cells clear_point .. solid-1, one cell wide piece so stretch is (n-1)*bw if solid > clear_point then local gold_x2 = (solid - clear_point) * bw - bw self:blit(self.clear_top, { mirror = mirror, x = clear_point * bw, y = y, x2 = gold_x2, index = idx }) self:blit(self.clear_bottom, { x = clear_point * bw, y = y, x2 = gold_x2, index = idx }) end if is_rainbow and rainbow_fade >= 0 then local frame_a = math.floor(rainbow_frac) % 8 local frame_b = (frame_a + 1) % 8 local f = rainbow_frac - math.floor(rainbow_frac) self:blit(self.rainbow, { frame = frame_a, mirror = mirror, y = y, fade = rainbow_fade, index = idx }) self:blit(self.rainbow, { frame = frame_b, mirror = mirror, y = y, fade = rainbow_fade * f, index = idx }) end if show_up then -- sprite sits on the cell that was just filled (length-1) local fx = (length - 1) * bw if length == clear_point then self:blit(self.clear_transition_fade, { mirror = mirror, x = fx, y = y, fade = alpha, index = idx }) elseif length > clear_point then self:blit(self.clear_fade, { x = fx, y = y, fade = alpha, index = idx }) else self:blit(self.bar_fade, { x = fx, y = y, fade = alpha, index = idx }) end end self:blit(self.overlay, { mirror = mirror, y = y, fade = 0.15, cap = true, index = idx }) if self.footer then self:blit(self.footer, { y = y, index = idx }) end local label = self.art_tier + idx * 3 if is_clear then self:blit(self.clear, { y = y, index = label }) if is_rainbow then self:blit(self.fire, { frame = fire_frame, scale = 0.75, center = true, y = y, index = idx }) end self:blit(self.tamashii, { y = y, index = idx }) if is_rainbow and (fire_frame == 0 or fire_frame == 1 or fire_frame == 4 or fire_frame == 5) then self:blit(self.tamashii_overlay, { y = y, fade = 0.5, cap = true, index = idx }) end else self:blit(self.clear_dark, { y = y, index = label }) self:blit(self.tamashii_dark, { y = y, index = idx }) end end -- dan gauge sits at absolute positions from its texture.json: no lane offset, no mirroring function Gauge:draw_dan(points, previous_points, max_points, is_rainbow, anim_active, anim_alpha, rainbow_fade, rainbow_frac, fire_frame) tex.draw_texture(self.border, {}) tex.draw_texture(self.unfilled, {}) local bw = self.bar_width local length, solid, alpha, show_up = self:cells(points, previous_points, max_points, anim_active, anim_alpha) if solid > 0 then tex.draw_texture(self.bar, { x2 = solid * bw - bw }) end if is_rainbow and rainbow_fade >= 0 then local frame_a = math.floor(rainbow_frac) % 8 local frame_b = (frame_a + 1) % 8 local f = rainbow_frac - math.floor(rainbow_frac) tex.draw_texture(self.rainbow, { frame = frame_a, fade = rainbow_fade }) tex.draw_texture(self.rainbow, { frame = frame_b, fade = rainbow_fade * f }) end if show_up and length > 0 then tex.draw_texture(self.bar_fade, { x = (length - 1) * bw, fade = alpha }) end tex.draw_texture(self.overlay, { fade = 0.15 }) if is_rainbow then tex.draw_texture(self.fire, { frame = fire_frame, scale = 0.75, center = true }) tex.draw_texture(self.tamashii, {}) if fire_frame == 0 or fire_frame == 1 or fire_frame == 4 or fire_frame == 5 then tex.draw_texture(self.tamashii_overlay, { fade = 0.5 }) end else tex.draw_texture(self.tamashii_dark, {}) end end