198 lines
7.8 KiB
Lua
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
|