Files
PyTaikoGreen/Scripts/game/note_arc.lua
T
2026-11-04 18:45:35 -05:00

198 lines
7.8 KiB
Lua

local SKIN_S = tex.get_screen_scale()
local SKIN = {
note_arc_start_x_offset = { x = 350 * SKIN_S },
note_arc_curve_height = { height = 425 * SKIN_S },
note_arc_balloon_p2_y = { y = 435 * SKIN_S },
}
NoteArc = {}
NoteArc.__index = NoteArc
local ARC_POINTS = 100
local FRAME_MS = 16.67
local RAINBOW_ARC = { end_x = 812, end_y = -348, control_x = 323, control_y = -364 }
local RAINBOW_X, RAINBOW_Y, RAINBOW_Y_P2 = -119, -433, -77
local RAINBOW_SPARKS = 6
local SYNCHRO_CROUCH_FRAMES, SYNCHRO_FLIGHT_FRAMES = 9, 26
local SYNCHRO_CROUCH_DIP, SYNCHRO_CROUCH_SQUASH = 20, 0.16
local SYNCHRO_ARM_TILT = 45
local SYNCHRO_ARMS_X = { -17, 21 }
local function trunc(v) return v < 0 and math.ceil(v) or math.floor(v) end
function NoteArc.new(note_tex_type, big, is_balloon, player_num, start_x, start_y, rainbow_arc, synchro)
local self = setmetatable({}, NoteArc)
self.big = big
self.is_balloon = is_balloon
self.p2 = player_num == 2
local curve_height = SKIN.note_arc_curve_height.height
local sx = start_x + SKIN.note_arc_start_x_offset.x
local sy = start_y + tex.get_skin_config("game_notes").y
local hit = tex.get_skin_config("game_gauge_hit_effect_note")
local ex, ey = hit.x, hit.y
if self.p2 then ey = ey + hit.height end
self.sx, self.sy, self.ex, self.ey = sx, sy, ex, ey
self.cx = (sx + ex) / 2
if self.p2 then
self.cy = math.max(sy, ey) + curve_height -- arc downward
else
self.cy = math.min(sy, ey) - curve_height -- arc upward
end
self.t_note = tex.get_texture("game/notes/" .. note_tex_type)
self.note_width = tex.get_texture_info("notes", tostring(note_tex_type)).width
self.t_firework = big and tex.get_texture("game/hit_effect/firework") or nil
self.firework_frames = self.t_firework and tex.get_texture_info("hit_effect", "firework").frame_count or 0
self.rainbow_arc = rainbow_arc
local rainbow_key = rainbow_arc and "rainbow_angled" or "rainbow"
self.t_rainbow = tex.get_texture("game/balloon/" .. rainbow_key)
if self.t_rainbow then
self.rainbow_info = tex.get_texture_info("balloon", rainbow_key)
end
self.rainbow_x, self.rainbow_y = 0, self.p2 and SKIN.note_arc_balloon_p2_y.y or 0
if rainbow_arc then
local dir = self.p2 and -1 or 1
self.ex = sx + RAINBOW_ARC.end_x * SKIN_S
self.ey = sy + RAINBOW_ARC.end_y * dir * SKIN_S
self.cx = sx + RAINBOW_ARC.control_x * SKIN_S
self.cy = sy + RAINBOW_ARC.control_y * dir * SKIN_S
self.rainbow_x = sx + self.note_width / 2 + RAINBOW_X * SKIN_S
self.rainbow_y = sy + self.note_width / 2 + (self.p2 and RAINBOW_Y_P2 or RAINBOW_Y) * SKIN_S
self.t_firework = self.t_firework or tex.get_texture("game/hit_effect/firework")
self.firework_frames = tex.get_texture_info("hit_effect", "firework").frame_count
self.firework_size = tex.get_texture_info("hit_effect", "firework").width
if synchro then
self.t_arm = tex.get_texture("waiwai_game/effect/synchro_arm_flight")
self.arm_info = tex.get_texture_info("effect", "synchro_arm_flight")
end
end
return self
end
-- bezier sampled at the same 100 steps the C++ cache used, truncated to whole pixels
function NoteArc:point(progress)
local t = math.min(math.floor(progress * ARC_POINTS), ARC_POINTS) / ARC_POINTS
local ti = 1 - t
return trunc(ti * ti * self.sx + 2 * ti * t * self.cx + t * t * self.ex),
trunc(ti * ti * self.sy + 2 * ti * t * self.cy + t * t * self.ey)
end
function NoteArc:draw_rainbow(y, raw_progress)
local w, h = self.rainbow_info.width, self.rainbow_info.height
local trail_start = math.max(0, raw_progress - 0.5)
local trail_end = math.min(raw_progress, 1)
if trail_end <= trail_start then return end
local crop_start = math.floor(trail_start * w + 0.5)
local crop_w = math.floor(trail_end * w + 0.5) - crop_start
if crop_w <= 0 then return end
tex.draw_texture(self.t_rainbow, {
x = self.rainbow_x + crop_start, y = y + self.rainbow_y, x2 = -w + crop_w,
src = { x = crop_start, y = 0, width = crop_w, height = self.p2 and -h or h },
})
end
function NoteArc:draw_fireworks(y, elapsed_ms, duration_ms)
local half = self.note_width * 0.6 / 2
for i = 0, 8 do
local spawn_ms = (3 + 3 * i) * FRAME_MS
local age_ms = elapsed_ms - spawn_ms
local frame = math.floor(age_ms / FRAME_MS)
if age_ms >= 0 and frame < self.firework_frames then
local fx, fy = self:point(math.min(spawn_ms / duration_ms, 1))
tex.draw_texture(self.t_firework, { frame = frame, scale = 0.8, x = fx - half, y = y + fy - half })
end
end
end
function NoteArc:draw_rainbow_sparks(y, elapsed_ms, duration_ms)
local delay = self.t_arm and 6 or 0
local half_note, half = self.note_width / 2, self.firework_size / 2
for i = 0, RAINBOW_SPARKS - 1 do
local spawn_ms = (6 + 3 * i) * FRAME_MS
local age_ms = elapsed_ms - spawn_ms - delay * FRAME_MS
local frame = math.floor(age_ms / FRAME_MS)
if age_ms >= 0 and frame < self.firework_frames then
local fx, fy = self:point((spawn_ms - 3.5 * FRAME_MS) / duration_ms)
tex.draw_texture(self.t_firework, { frame = frame, x = fx + half_note - half, y = y + fy + half_note - half })
end
end
end
function NoteArc:draw_synchro(y, elapsed_ms)
local frames = elapsed_ms / FRAME_MS
local flight = (frames - SYNCHRO_CROUCH_FRAMES) / SYNCHRO_FLIGHT_FRAMES
if flight >= 1 then return end
local dir = self.p2 and -1 or 1
local crouch = 0
local tilt = 0
local x_i, y_i
if flight < 0 then
local out = math.min(frames / 4, 1)
crouch = frames < 4 and 1 - (1 - out) * (1 - out) or (SYNCHRO_CROUCH_FRAMES - frames) / 5
x_i, y_i = self.sx, self.sy
else
tilt = flight
local left = 1 - flight
x_i, y_i = self:point(1 - left * left)
end
local half = self.note_width / 2
local cx, cy = x_i + half, y + y_i + half
local arm_w, arm_h = self.arm_info.width, self.arm_info.height
local angle = math.rad(SYNCHRO_ARM_TILT * tilt)
local mid_x = cx - 23 * SKIN_S * tilt
local mid_y = cy + dir * (54 - 15 * tilt) * SKIN_S
for _, x in ipairs(SYNCHRO_ARMS_X) do
tex.draw_texture(self.t_arm, {
x = mid_x + x * math.cos(angle) * SKIN_S,
y = mid_y + dir * x * math.sin(angle) * SKIN_S,
origin = { arm_w / 2, arm_h / 2 },
rotation = dir * SYNCHRO_ARM_TILT * tilt,
mirror = self.p2 and "vertical" or nil,
})
end
local squash = SYNCHRO_CROUCH_SQUASH * crouch * self.note_width
tex.draw_texture(self.t_note, {
x = x_i,
y = y + y_i + squash / 2 + dir * SYNCHRO_CROUCH_DIP * crouch * SKIN_S,
y2 = -squash,
})
end
function NoteArc:draw(y, elapsed_ms, duration_ms)
local progress = math.min(1, elapsed_ms / duration_ms)
local x_i, y_i = self:point(progress)
if self.rainbow_arc then
if self.big then self:draw_rainbow_sparks(y, elapsed_ms, duration_ms) end
if self.t_arm then
self:draw_synchro(y, elapsed_ms)
return
end
if self.is_balloon and self.t_rainbow then
self:draw_rainbow(y, elapsed_ms / duration_ms)
return
end
elseif self.is_balloon then
if not self.t_rainbow then
if progress < 1 then tex.draw_texture(self.t_note, { x = x_i, y = y + y_i }) end
return
end
self:draw_rainbow(y, elapsed_ms / duration_ms)
elseif self.big and self.t_firework and self.firework_frames > 0 then
self:draw_fireworks(y, elapsed_ms, duration_ms)
end
if progress < 1 then
tex.draw_texture(self.t_note, { x = x_i, y = y + y_i })
end
end
return NoteArc