Files
YataiDONNijiiro/Scripts/entry/player.lua
2026-09-02 14:25:04 -04:00

217 lines
10 KiB
Lua

-- YataiDON-HSS override of PyTaikoGreen/entry/player.lua: no big drum on the arcade entry
-- screen; Don stands on the street at the arcade spot (1P ~x220, feet ~y900; 2P mirrored).
EntryPlayer = {}
EntryPlayer.__index = EntryPlayer
function EntryPlayer.new(side)
local self = setmetatable({}, EntryPlayer)
-- entry.cpp passes the side-select cursor (0 = left/1P, 2 = right/2P); fold it to 0/1 so
-- every table below (DON_CX, COS_CX, __hss_costume_panel) keys by 1P=0 / 2P=1.
self.side = (side == 0) and 0 or 1
local drum_offset_cfg = tex.get_skin_config("entry_drum_offset")
local chara_offset_r = tex.get_skin_config("entry_chara_offset_r")
self.chara_y_base = chara_offset_r.y
if side == 0 then
self.drum = tex.load_texture("entry/side_select/red_drum")
self.drum_offset = drum_offset_cfg.x
local chara_l = tex.get_skin_config("entry_chara_offset_l")
self.chara_x_offset = chara_l.x
else
self.drum = tex.load_texture("entry/side_select/blue_drum")
self.drum_offset = drum_offset_cfg.y
self.chara_x_offset = chara_offset_r.x
end
self.cloud = tex.load_texture("entry/side_select/cloud")
self.drum_move_1 = tex.get_animation(2, "entry")
self.drum_move_2 = tex.get_animation(3, "entry")
self.drum_move_3 = tex.get_animation(4, "entry")
self.cloud_resize = tex.get_animation(5, "entry")
self.cloud_resize_loop = tex.get_animation(6, "entry")
self.cloud_texture_change = tex.get_animation(7, "entry")
self.cloud_fade = tex.get_animation(8, "entry")
self.nameplate_fadein = tex.get_animation(12, "entry")
return self
end
function EntryPlayer:start_animations()
self.drum_move_1:start()
self.drum_move_2:start()
self.drum_move_3:start()
self.cloud_resize:start()
self.cloud_resize_loop:start()
self.cloud_texture_change:start()
self.cloud_fade:start()
self.nameplate_fadein:start()
end
function EntryPlayer:update(current_ms)
self.drum_move_1:update(current_ms)
self.drum_move_2:update(current_ms)
self.drum_move_3:update(current_ms)
self.cloud_resize:update(current_ms)
self.cloud_texture_change:update(current_ms)
self.cloud_fade:update(current_ms)
self.cloud_resize_loop:update(current_ms)
self.nameplate_fadein:update(current_ms)
end
-- Arcade placement (1080p px). entry/entry.nulm: don_1p at (220,735) / smoke_instance_1 at (220,794)
-- (2P mirrored at x=1700); the user's arcade capture puts Don's centre at ~(220,775), feet ~900.
-- Chara3D::draw(x,y): the visible centre is ~130 px above the returned y (x centre = x+5); the
-- parent's entry bounce (drum_move_1+2) settles at move_y = -367.5, so on mode select the returned
-- y = centre + 130 + 367.5 (measured: 1255 gave a visible centre at 758). Costume select adds no bounce.
-- Costume select (entry/costume.nulm): costume_1p_instance/board_instance/don1p at (498,480)
-- scale 0.8, the Don stands INSIDE the red panel (~y 450..650). The panel is drawn here, before the
-- Don, when costume_menu.lua reports an open menu via __hss_costume_panel[side] (shared Lua state).
local DON_ANCHOR_DY, DON_ANCHOR_DX = 130, -5
local BOUNCE_END = -367.5 -- animation.json entry 2 (-295) + 3 (+50), x1.5
local DON_CX = { [0] = 220, [1] = 1920 - 220 }
local DON_CY = 780
local COS_CX = { [0] = 498, [1] = 498 + 924 }
local COS_CY = 548
-- arcade draws the Don at 0.8 inside the costume panel (MAPPING item 37).
-- Engine binding: EntryPlayer:draw_drum_back() may return a third element,
-- the Chara3D scale multiplier (see ENGINE_BINDINGS.md).
local COS_DON_SCALE = 0.8
__hss_costume_panel = __hss_costume_panel or {}
-- ROUND 11: costume_menu.lua publishes the arcade board fade-in (entry/costume.nulm
-- `costume_1p_instance` label fade_in, alpha 0 -> 1 over f5..f15) here so the panel — which
-- this script draws, because it has to go UNDER the Don — fades in with the rest of the
-- board. Missing entry = fully opaque (the menu may not have drawn its first frame yet).
__hss_costume_fade = __hss_costume_fade or {}
__hss_costume_smoke = __hss_costume_smoke or {}
-- ROUND 6 (item 36) — arcade costume-change smoke, lumen entry/costume `smoke_instance`
-- (MC 10, 33 frames, labels smoke_in 0 / smoke_loop 7 / smoke_out 16), placed inside
-- board_instance at (498,482) with instance scale 1.20; the shape is 400x400.
-- The parent's entry/side_select/cloud IS the same art (its 4 frames are pixel-for-pixel
-- shapes 2/4/6/8 of that MC, baked at 320x320 = 400 x 0.8), so the drawn scale is
-- timeline_scale x 1.20 / 0.8 = timeline_scale x 1.5.
-- Per-frame table straight off the lumen (60 fps):
-- f 0..6 frame 0, scale 0.10 +0.1667/f -> 1.10, alpha 0 -> 1
-- f 7..15 frame 0, scale wobble 1.10 1.13 1.15 1.18 1.21 1.18 1.15 1.13 1.10
-- f16..20 frame 1, scale 1.10, dy 0 -2.5 -5 -7.5 -10
-- f21..26 frame 2, scale 1.10, dy -10
-- f27..32 frame 3, scale 1.10, dy -20 -22 -24 -26 -28 -30, alpha 1 -> 0
local SMOKE_F = 1000 / 60
local SMOKE_LAST_F = 32
local SMOKE_CX = { [0] = 498, [1] = 498 + 924 }
local SMOKE_CY = 482 + (548 - 480) -- arcade smoke y + (our panel Don centre - arcade's)
local SMOKE_WOBBLE = { 1.10, 1.13, 1.15, 1.18, 1.21, 1.18, 1.15, 1.13, 1.10 }
-- ROUND 53 (r53-anim-lumen-sweep): the per-frame table above was hand-transcribed from the
-- lumen dump in ROUND 6; ROUND 7 exported the very same MC to `Scripts/anim/costume_smoke.lua`
-- but nothing ever consumed it (LUA_CHN_AUDIT.md item 68). Sample the export instead.
-- The MC is a 4-leaf flipbook (#2/#4/#6/#8 = cloud texture frames 0..3): each leaf's rows only
-- span the frames it is on the display list, so "which texture frame" = the leaf whose row
-- window contains floor(f), and scale/dy/alpha are that leaf's sx/ty/a channels.
-- ROUND 8 fail-soft: anim.safe hands back a NULL clip (no tracks) on any load problem, and
-- smoke_state() then falls through to the ROUND 6 hand table -- the screen never goes down.
local ok_anim, anim = pcall(require, "anim/sampler")
if not ok_anim then anim = nil end
local SMOKE_CLIP = anim and anim.safe("anim/costume_smoke") or nil
local SMOKE_LEAVES = { { "#2@0", 0 }, { "#4@0", 1 }, { "#6@0", 2 }, { "#8@0", 3 } }
-- returns frame index, scale, dy, alpha (or nil when no smoke is running)
local function smoke_state(f)
if f < 0 or f > SMOKE_LAST_F then return nil end
if SMOKE_CLIP and SMOKE_CLIP.tracks then
local fi = math.floor(f)
for i = 1, #SMOKE_LEAVES do
local name, tex_frame = SMOKE_LEAVES[i][1], SMOKE_LEAVES[i][2]
local rows = SMOKE_CLIP.tracks[name]
if rows and #rows > 0 and fi >= rows[1][1] and fi <= rows[#rows][1] then
-- fields = {ty, sx, a} (see the generated header)
local dy, sc, a = SMOKE_CLIP:at(name, f)
if dy ~= nil then return tex_frame, sc, dy, a end
end
end
-- fall through to the hand table only if no leaf claimed the frame
end
if f <= 6 then return 0, 0.10 + 0.1666667 * f, 0, f / 6 end
if f <= 15 then return 0, SMOKE_WOBBLE[f - 6] or 1.10, 0, 1 end
if f <= 20 then return 1, 1.10, -2.5 * (f - 16), 1 end
if f <= 26 then return 2, 1.10, -10, 1 end
return 3, 1.10, -20 - 2 * (f - 27), 1 - (f - 27) / 5
end
local function costume_open(side)
local t = __hss_costume_panel[side]
return t ~= nil and (tex.get_current_ms() - t) < 100
end
function EntryPlayer:draw_drum_back()
local move_y = self.drum_move_1.attribute + self.drum_move_2.attribute
local cx, cy = DON_CX[self.side] or DON_CX[0], DON_CY
if costume_open(self.side) then
cx, cy = COS_CX[self.side] or COS_CX[0], COS_CY
if not self.cos_panel then
self.cos_panel = tex.load_texture("entry/costume_select/box_center_" .. (self.side == 0 and "1p" or "2p"))
end
tex.draw_texture(self.cos_panel, { fade = __hss_costume_fade[self.side] or 1.0 })
move_y = 0
else
move_y = move_y - BOUNCE_END -- the bounce ends at BOUNCE_END; the centre lands on DON_CY
end
self.don_cx, self.don_cy = cx, cy + move_y
local don_scale = costume_open(self.side) and COS_DON_SCALE or 1.0
return { cx + DON_ANCHOR_DX, cy + move_y + DON_ANCHOR_DY, don_scale }
end
-- entry smoke (arcade smoke_instance_1/2 at (220,794)): the parent's 4-frame cloud, centred on Don
function EntryPlayer:draw_drum_front()
-- ROUND 6: costume-change smoke wins while it is running (the entry cloud is long finished)
local t0 = __hss_costume_smoke[self.side]
if t0 and costume_open(self.side) then
local f = (tex.get_current_ms() - t0) / SMOKE_F
local frame, sc, dy, a = smoke_state(f)
if frame then
if not self.cloud_info then self.cloud_info = tex.get_texture_info("side_select", "cloud") end
local ci = self.cloud_info
local cx = SMOKE_CX[self.side] or SMOKE_CX[0]
local cy = SMOKE_CY + dy
tex.draw_texture(self.cloud, {
frame = frame,
scale = sc * 1.5,
center = true,
x = cx - ci.x[1] - ci.x2[1] / 2,
y = cy - ci.y[1] - ci.y2[1] / 2,
fade = a,
})
return
end
end
local scale = self.cloud_resize.attribute
if self.cloud_resize.is_finished then
scale = math.max(1.0, self.cloud_resize_loop.attribute)
end
if not self.cloud_info then
self.cloud_info = tex.get_texture_info("side_select", "cloud")
end
local ci = self.cloud_info
local cx = (self.don_cx or DON_CX[0])
local cy = (self.don_cy or DON_CY) + 14
tex.draw_texture(self.cloud, {
frame = math.floor(self.cloud_texture_change.attribute),
scale = scale,
center = true,
x = cx - ci.x[1] - ci.x2[1] / 2,
y = cy - ci.y[1] - ci.y2[1] / 2,
fade = self.cloud_fade.attribute,
})
end
function EntryPlayer:is_cloud_finished()
return self.cloud_texture_change.is_finished
end
function EntryPlayer:get_nameplate_fade()
return self.nameplate_fadein.attribute
end