Entry = {} Entry.__index = Entry function Entry.new() local self = setmetatable({}, Entry) __hss_entry_reset = true self.bg_flicker = tex.get_animation(1, "entry") self.side_select_fade = tex.get_animation(0, "entry") self.bg_flicker:start() self.text_cancel = text.create_text("entry_cancel", { 255, 255, 255, 255 }, { 40, 24, 12, 255 }, false, 3, 0) self.text_credit_p = { text.create_text("entry_credit_1p", { 0, 0, 0, 255 }, { 255, 255, 255, 255 }, false, 7.0 / tex.get_screen_scale(), 0), text.create_text("entry_credit_2p", { 0, 0, 0, 255 }, { 255, 255, 255, 255 }, false, 7.0 / tex.get_screen_scale(), 0), } local CREDIT_BORDER = 7.0 / tex.get_screen_scale() self.text_credit_msg = text.create_text("entry_credit_start", { 255, 255, 255, 255 }, { 0, 0, 0, 255 }, false, CREDIT_BORDER, 0) self.text_credit_msg_hl = text.create_text("entry_credit_start", { 255, 255, 255, 255 }, { 255, 236, 67, 255 }, false, CREDIT_BORDER, 0) self.bg_bg = tex.get_texture("entry/background/bg") self.bg_tower = tex.get_texture("entry/background/tower") self.bg_shops_center = tex.get_texture("entry/background/shops_center") self.bg_people = tex.get_texture("entry/background/people") self.bg_shops_left = tex.get_texture("entry/background/shops_left") self.bg_shops_right = tex.get_texture("entry/background/shops_right") self.bg_lights = tex.get_texture("entry/background/lights") self.bg_street_lit = tex.get_texture("entry/background/street_lit") self.bg_glow = tex.get_texture("entry/background/glow") self.bg_twinkle = tex.get_texture("entry/background/twinkle") self.bg_t0 = tex.get_current_ms() self.cr_pill = tex.get_texture("entry/side_select/credit_pill") self.cr_flash = tex.get_texture("entry/side_select/credit_flash") self.cr_t0 = tex.get_current_ms() self.cr_blink_t0 = self.cr_t0 self.cr_side = 1 self.cr_frame = 0 self.cr_seen_frame = nil self.cr_decide_t0 = nil self.cr_decide_row = nil local arcade_cfg = tex.get_skin_config("entry_credit_arcade") self.cr_arcade = (arcade_cfg ~= nil and (arcade_cfg.x or 0) >= 1) self.cr_pending_decide = nil self.guide = tex.get_texture("global/indicator/background") self.guide_t0 = self.cr_t0 self.any_joined = false return self end local GUIDE_STEP = 2 local GUIDE_SPLIT = math.floor(420 / GUIDE_STEP) local GUIDE_END = math.floor(650 / GUIDE_STEP) function Entry:draw_control_guide() if not self.guide then return end if self.any_joined then return end local n = GUIDE_END - GUIDE_SPLIT local f = GUIDE_SPLIT + math.floor((tex.get_current_ms() - self.guide_t0) * 0.06 / GUIDE_STEP) % n tex.draw_texture(self.guide, { frame = f, x = 0, y = 0 }) end function Entry:update(current_ms) self.bg_flicker:update(current_ms) self.side_select_fade:update(current_ms) self.cr_frame = (self.cr_frame or 0) + 1 end function Entry:start_side_select() self.side_select_fade:start() self:restart_credit() end function Entry:restart_side_select() self.side_select_fade:restart() self:restart_credit() end function Entry:restart_credit() local now = tex.get_current_ms() self.cr_t0 = now self.cr_blink_t0 = now self.cr_side = 1 self.cr_decide_t0 = nil self.cr_decide_row = nil self.cr_seen_frame = nil self.cr_pending_decide = nil end function Entry:get_side_select_fade() return self.side_select_fade.attribute end local ea_ok, entry_anim = pcall(require, "anim/sampler") if not ea_ok then entry_anim = { safe = function() local n = {} n.at = function() return nil end; n.get = function() return nil end n.label = function() return nil end; n.has = function() return false end n.first = function() return 0 end; n.last = function() return 0 end return n end } end local entry_bg = entry_anim.safe("anim/entry_bg") local credit_row = entry_anim.safe("anim/credit_row") local credit_fade = entry_anim.safe("anim/credit_fade") local CREDIT_CX = 960 local ROW_CY = { 432 + 72, 432 + 248 } local LABEL_INK_X = CREDIT_CX - 443 local MSG_INK_X = CREDIT_CX + 190 local TEXT_INK_DY = 0.5 local PAD5 = 9 local PAD_LABEL = 2 local CANCEL_CX, CANCEL_CY = 960, 838 local ROW_LOOP_F0, ROW_LOOP_LEN = 5, 120 local DIM_UNSELECTED = 0.72 local DECIDE_FLASH_F = 24 local entry_bg_len = math.max(1, entry_bg:last() - entry_bg:first() + 1) local BG_LIT_END = 19 local ENTRY_TWINKLES = { { 0, 0, "#11@1/#5@0/#3@0" }, { 1, 0, "#11@1/#9@2/#3@0" }, { 2, 1, "#11@1/#8@1/#6@0" }, { 3, 1, "#11@1/#10@3/#6@0" }, } function Entry:draw_background() local f = ((tex.get_current_ms() - self.bg_t0) * 0.06) % entry_bg_len -- arcade frames @ 60 fps tex.draw_texture(self.bg_bg) if self.bg_twinkle then for i = 1, #ENTRY_TWINKLES do local tw = ENTRY_TWINKLES[i] local s, a = entry_bg:at(tw[3], f) if a and a > 0.002 then tex.draw_texture(self.bg_twinkle, { index = tw[1], frame = tw[2], center = true, scale = s, fade = a }) end end end if self.bg_glow then local ga = entry_bg:get("#16@3/#14@0", f, "a") or 1.0 for i = 0, 1 do tex.draw_texture(self.bg_glow, { index = i, frame = i, fade = ga }) end end if self.bg_street_lit then local lf = (tex.get_current_ms() - self.bg_t0) * 0.06 -- monotonic, NOT wrapped if lf < BG_LIT_END then local la = entry_bg:get("#17@7", lf, "a") or 0.0 if la > 0.002 then tex.draw_texture(self.bg_street_lit, { fade = la }) end end end end function Entry:draw_side_select() end -- One player row. `row` is 1 (upper, 1P) or 2 (lower, 2P); `f` is the row timeline frame. function Entry:draw_credit_row(row, f, a) if a <= 0.002 then return end tex.draw_texture(self.cr_pill, { index = row - 1, fade = a }) local cy = ROW_CY[row] local lbl = self.text_credit_p[row] if lbl then lbl:draw({ x = LABEL_INK_X - PAD_LABEL, y = cy + TEXT_INK_DY - lbl.height / 2, fade = a }) end -- fill alpha and the outline copy colour multiply, both read from the export local ma = credit_row:get("text_message_instance", f, "a") or 1 local _, cr = credit_row:at("text_message_instance_2", f) local yellow = (cr and cr / 256.0) or 0.0 -- 0 = black outline, 1 = (256,236,67) local msg = self.text_credit_msg if msg and ma > 0.002 then local x = MSG_INK_X - msg.width / 2 local y = cy + TEXT_INK_DY - msg.height / 2 msg:draw({ x = x, y = y, fade = a * ma }) if yellow > 0.001 and self.text_credit_msg_hl then self.text_credit_msg_hl:draw({ x = x, y = y, fade = a * ma * yellow }) end end end function Entry:draw_credit(a, sel, blink_f, flash_row, flash_a, loop_rows) for row = 1, 2 do local armed = (row == sel) local ra = (sel == nil or sel == 0 or armed) and a or (a * DIM_UNSELECTED) local rf = (armed or loop_rows) and blink_f or ROW_LOOP_F0 self:draw_credit_row(row, rf, ra) end local now = tex.get_current_ms() if sel and sel > 0 then local pulse = 0.08 + 0.10 * math.sin((now - self.cr_blink_t0) * 0.006) tex.draw_texture(self.cr_flash, { index = sel - 1, fade = a * pulse }) end if flash_row and flash_a and flash_a > 0.002 then tex.draw_texture(self.cr_flash, { index = flash_row - 1, fade = a * flash_a }) end end function Entry:draw_side_select_buttons(side) local now = tex.get_current_ms() __hss_entry_guide = { ms = now, phase = "side" } if side ~= self.cr_side then self.cr_side = side self.cr_blink_t0 = now -- a newly armed row restarts its attract cycle end self.cr_seen_frame = self.cr_frame self.cr_decide_t0 = nil local fi = math.min(15, 5 + (now - self.cr_t0) * 0.06) local a = credit_fade:get("player1_text_instance", fi, "a") or 1.0 local sel = (not self.cr_arcade) and ((side == 0 and 1) or (side == 2 and 2) or 0) or nil local blink_f = ROW_LOOP_F0 + ((now - self.cr_blink_t0) * 0.06) % ROW_LOOP_LEN self:draw_credit(a, sel, blink_f, nil, nil, self.cr_arcade) -- depth 28 > depth 23: the guide sits OVER the credit rows. self:draw_control_guide() YD_ENTRY_CREDIT_MS = now end function Entry:draw_footer(p1_joined, p2_joined) YD_ENTRY_BOTH_JOINED = (p1_joined == true and p2_joined == true) self.any_joined = (p1_joined == true or p2_joined == true) end function Entry:draw_player_entry() self:draw_credit_decide() end function Entry:decide_side_select(side) self.cr_pending_decide = (side == 2) and 2 or 1 end function Entry:draw_credit_decide() local now = tex.get_current_ms() if self.cr_seen_frame ~= nil and self.cr_decide_t0 == nil then if self.cr_frame > self.cr_seen_frame then self.cr_seen_frame = nil if self.cr_pending_decide then -- arcade model: the engine told us which row entered self.cr_decide_t0 = now self.cr_decide_row = self.cr_pending_decide self.cr_pending_decide = nil elseif self.cr_side == 0 or self.cr_side == 2 then -- cursor model: side 1 is 「つかわない」 -- cancelled, not decided. self.cr_decide_t0 = now self.cr_decide_row = (self.cr_side == 0) and 1 or 2 end end end if self.cr_decide_t0 == nil then return end local t = (now - self.cr_decide_t0) * 0.06 -- frames since decide local flash_a = credit_row:get("#8@4", 125 + math.min(t, DECIDE_FLASH_F), "a") or 0 local fade_f = 16 + math.max(0, t - (DECIDE_FLASH_F + 1)) -- fade_out starts after local a = credit_fade:get("player1_text_instance", math.min(fade_f, 35), "a") or 0 if a <= 0.002 then self.cr_decide_t0 = nil return end self:draw_credit(a, nil, ROW_LOOP_F0, self.cr_decide_row, flash_a) YD_ENTRY_CREDIT_MS = now end