forked from Yonokid/PyTaikoGreen
gauge_state came from a cell count (68 of 87 for oni) while the game clears on soul >= norma (6000/7000/7000/8000); in the band just below the norma the result lit the gold cell, the clear caption and a crown for a failed run. Hard also used the oni layout although the HUD gives it normal's zone and norma. Derive the state from the soul value, pick the art tier like the HUD, and light the gold cell and caption only when cleared. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
401 lines
19 KiB
Lua
401 lines
19 KiB
Lua
ResultPlayer = {}
|
|
ResultPlayer.__index = ResultPlayer
|
|
|
|
function ResultPlayer.new(player_num, is_2p, has_2p,
|
|
gauge_length, difficulty, selected_difficulty,
|
|
crown_type, score_diff, score_method,
|
|
mod_display, mod_inverse, mod_random, mod_speed)
|
|
local self = setmetatable({}, ResultPlayer)
|
|
tex.load_animations("result")
|
|
|
|
self.player_num = player_num
|
|
self.is_2p = is_2p
|
|
self.has_2p = has_2p
|
|
self.gauge_length = gauge_length
|
|
self.difficulty = difficulty
|
|
self.selected_difficulty = selected_difficulty
|
|
self.crown_type = crown_type
|
|
self.score_diff = score_diff
|
|
self.score_method = score_method
|
|
self.mod_display = mod_display
|
|
self.mod_inverse = mod_inverse
|
|
self.mod_random = mod_random
|
|
self.mod_speed = mod_speed
|
|
|
|
-- Clear follows the engine's gauge (Gauge::get_is_clear: soul >= norma), not a cell
|
|
-- count: the norma is easy 6000 / normal 7000 / hard 7000 / oni+ura 8000 of 10000 soul,
|
|
-- and gauge_length arrives as soul / 10000 * 87. Hard shares normal's norma and so, like
|
|
-- the in-game HUD, normal's clear-zone art (zone at cell 60), not the oni layout.
|
|
local ART = { [0] = 0, [1] = 1, [2] = 1, [3] = 2 }
|
|
local NORMA = { [0] = 6000, [1] = 7000, [2] = 7000, [3] = 8000 }
|
|
local d = math.min(math.max(difficulty, 0), 3)
|
|
self.art_index = ART[d]
|
|
if gauge_length == 87 then
|
|
self.gauge_state = 2
|
|
elseif gauge_length / 87.0 * 10000.0 >= NORMA[d] - 0.5 then
|
|
self.gauge_state = 1
|
|
else
|
|
self.gauge_state = 0
|
|
end
|
|
|
|
if self.art_index >= 2 then
|
|
self.diff_str = "_hard"
|
|
elseif self.art_index >= 1 then
|
|
self.diff_str = "_normal"
|
|
else
|
|
self.diff_str = "_easy"
|
|
end
|
|
|
|
local p = tostring(player_num) .. "p"
|
|
self.player_str = p
|
|
|
|
self.score = ""
|
|
self.good = ""
|
|
self.ok = ""
|
|
self.bad = ""
|
|
self.max_combo = ""
|
|
self.total_drumroll = ""
|
|
self.fade_in_finished_flag = false
|
|
|
|
self.gauge_started = false
|
|
self.score_delay = nil
|
|
self.fade_in_started = false
|
|
self.rainbow_start_ms = -1.0
|
|
self.rainbow_frac = 0.0
|
|
|
|
-- Sub-objects
|
|
self.bottom_chars = BottomCharacters.new()
|
|
self.crown_obj = nil
|
|
self.crown_msg_obj = nil
|
|
self.high_score_obj = nil
|
|
|
|
-- Animations owned by ResultPlayer
|
|
self.fade_in_bottom = tex.get_animation(1, "result")
|
|
self.gauge_fade_in = tex.get_animation(17, "result")
|
|
self.tamashii_fire = tex.get_animation(20, "result")
|
|
|
|
self.t_grad_clear = tex.load_texture("result/background/gradient_clear")
|
|
self.t_grad_fail = tex.load_texture("result/background/gradient_fail")
|
|
|
|
self.t_overlay = tex.load_texture("result/score/overlay")
|
|
self.t_difficulty = tex.load_texture("result/score/difficulty")
|
|
self.t_judge_good = tex.load_texture("result/score/judge_good")
|
|
self.t_judge_ok = tex.load_texture("result/score/judge_ok")
|
|
self.t_judge_bad = tex.load_texture("result/score/judge_bad")
|
|
self.t_judge_num = tex.load_texture("result/score/judge_num")
|
|
self.t_max_combo = tex.load_texture("result/score/max_combo")
|
|
self.t_drumroll = tex.load_texture("result/score/drumroll")
|
|
self.t_score_lbl = tex.load_texture("result/score/score")
|
|
self.t_score_shinuchi_lbl = tex.load_texture("result/score/score_shinuchi")
|
|
self.t_score_num = tex.load_texture("result/score/score_num")
|
|
|
|
self.t_mod_doron = tex.load_texture("result/score/mod_doron")
|
|
self.t_mod_abekobe = tex.load_texture("result/score/mod_abekobe")
|
|
self.t_mod_kimagure = tex.load_texture("result/score/mod_kimagure")
|
|
self.t_mod_detarame = tex.load_texture("result/score/mod_detarame")
|
|
self.t_mod_yonbai = tex.load_texture("result/score/mod_yonbai")
|
|
self.t_mod_sanbai = tex.load_texture("result/score/mod_sanbai")
|
|
self.t_mod_baisaku = tex.load_texture("result/score/mod_baisaku")
|
|
|
|
self.t_gauge_unfilled = tex.load_texture("result/gauge/" .. p .. "_unfilled" .. self.diff_str)
|
|
self.t_gauge_bar = tex.load_texture("result/gauge/" .. p .. "_bar")
|
|
self.t_gauge_rainbow = tex.load_texture("result/gauge/rainbow" .. self.diff_str)
|
|
self.t_gauge_overlay = tex.load_texture("result/gauge/overlay" .. self.diff_str)
|
|
self.t_gauge_footer = tex.load_texture("result/gauge/footer")
|
|
self.t_gauge_clear = tex.load_texture("result/gauge/clear")
|
|
self.t_gauge_clear_dark = tex.load_texture("result/gauge/clear_dark")
|
|
self.t_gauge_bar_clear_transition = tex.load_texture("result/gauge/bar_clear_transition")
|
|
self.t_gauge_bar_clear_top = tex.load_texture("result/gauge/bar_clear_top")
|
|
self.t_gauge_bar_clear_bottom = tex.load_texture("result/gauge/bar_clear_bottom")
|
|
self.t_gauge_tamashii = tex.load_texture("result/gauge/tamashii")
|
|
self.t_gauge_tamashii_dark = tex.load_texture("result/gauge/tamashii_dark")
|
|
self.t_gauge_tamashii_fire = tex.load_texture("result/gauge/tamashii_fire")
|
|
self.t_gauge_tamashii_overlay = tex.load_texture("result/gauge/tamashii_overlay")
|
|
|
|
local bar_info = tex.get_texture_info("gauge", p .. "_bar")
|
|
self.gauge_bar_width = bar_info and bar_info.width or 0
|
|
|
|
local si = tex.get_skin_config("score_info_counter_margin")
|
|
self.sc_score_info_margin = si and si.x or 0
|
|
local rs = tex.get_skin_config("result_score_margin")
|
|
self.sc_result_score_margin = rs and rs.x or 0
|
|
local r2 = tex.get_skin_config("result_2p_offset")
|
|
self.sc_result_2p_offset = r2 and r2.y or 0
|
|
|
|
return self
|
|
end
|
|
|
|
function ResultPlayer:update(current_ms, fade_in_finished,
|
|
score, good, ok, bad, max_combo, total_drumroll)
|
|
self.fade_in_finished_flag = fade_in_finished
|
|
self.score = score
|
|
self.good = good
|
|
self.ok = ok
|
|
self.bad = bad
|
|
self.max_combo = max_combo
|
|
self.total_drumroll = total_drumroll
|
|
|
|
if fade_in_finished and not self.gauge_started then
|
|
self.gauge_started = true
|
|
self.gauge_fade_in:start()
|
|
self.tamashii_fire:start()
|
|
self.bottom_chars:start()
|
|
end
|
|
|
|
if self.gauge_started then
|
|
if self.gauge_state == 2 then
|
|
if self.rainbow_start_ms < 0 then self.rainbow_start_ms = current_ms end
|
|
self.rainbow_frac = math.fmod((current_ms - self.rainbow_start_ms) / 75.0, 8.0)
|
|
end
|
|
self.tamashii_fire:update(current_ms)
|
|
self.gauge_fade_in:update(current_ms)
|
|
if self.gauge_fade_in.is_finished and self.score_delay == nil then
|
|
self.score_delay = current_ms + 1883
|
|
end
|
|
end
|
|
|
|
if self.score_delay ~= nil and current_ms > self.score_delay and not self.fade_in_started then
|
|
self.fade_in_started = true
|
|
self.fade_in_bottom:start()
|
|
end
|
|
self.fade_in_bottom:update(current_ms)
|
|
|
|
if self.fade_in_started then
|
|
self.bottom_chars:update(current_ms, self.gauge_state)
|
|
end
|
|
|
|
if self.bottom_chars:is_finished() then
|
|
if self.gauge_state >= 1 and not self.crown_obj then
|
|
self.crown_obj = ResultCrown.new(self.crown_type, self.is_2p)
|
|
if self.crown_type == 1 or self.crown_type == 2 then
|
|
audio.play_sound("full_combo_voice_" .. tostring(self.player_num) .. "p", "voice")
|
|
end
|
|
elseif self.gauge_state == 0 and not self.crown_msg_obj then
|
|
local frame = (self.gauge_length < 87.0 / 2.0) and 0 or 1
|
|
self.crown_msg_obj = ResultCrownMessage.new(frame, self.is_2p)
|
|
if frame == 0 then
|
|
audio.play_sound("max_fail_voice_" .. tostring(self.player_num) .. "p", "voice")
|
|
else
|
|
audio.play_sound("fail_voice_" .. tostring(self.player_num) .. "p", "voice")
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.crown_obj then
|
|
self.crown_obj:update(current_ms)
|
|
if self.crown_obj:is_settled() and not self.crown_msg_obj then
|
|
local frame = (self.gauge_state == 2) and 3 or 2
|
|
self.crown_msg_obj = ResultCrownMessage.new(frame, self.is_2p)
|
|
if frame == 2 then
|
|
audio.play_sound("clear_voice_" .. tostring(self.player_num) .. "p", "voice")
|
|
else
|
|
audio.play_sound("max_clear_voice_" .. tostring(self.player_num) .. "p", "voice")
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.crown_msg_obj then
|
|
self.crown_msg_obj:update(current_ms)
|
|
end
|
|
|
|
if not self.high_score_obj and self.score_diff > 0 and self.good ~= "" then
|
|
self.high_score_obj = HighScoreIndicator.new(self.score_diff, self.is_2p)
|
|
end
|
|
if self.high_score_obj then
|
|
self.high_score_obj:update(current_ms)
|
|
end
|
|
end
|
|
|
|
function ResultPlayer:draw()
|
|
local p_idx = self.is_2p and 1 or 0
|
|
|
|
if self.fade_in_started then
|
|
local fade_val = math.min(0.4, self.fade_in_bottom.attribute)
|
|
if self.is_2p then
|
|
if self.gauge_state == 0 then
|
|
if self.t_grad_fail then tex.draw_texture(self.t_grad_fail, { fade = fade_val }) end
|
|
else
|
|
if self.t_grad_clear then tex.draw_texture(self.t_grad_clear, { fade = fade_val }) end
|
|
end
|
|
else
|
|
local y = (self.sc_result_2p_offset ~= 0) and (self.has_2p and 1.0 or 0.0) or 0.0
|
|
if self.gauge_state == 0 then
|
|
if self.t_grad_fail then tex.draw_texture(self.t_grad_fail, { y = y, fade = fade_val }) end
|
|
else
|
|
if self.t_grad_clear then tex.draw_texture(self.t_grad_clear, { y = y, fade = fade_val }) end
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.t_overlay then tex.draw_texture(self.t_overlay, { color = { 255, 255, 255, 192 }, index = p_idx }) end
|
|
if self.t_difficulty then tex.draw_texture(self.t_difficulty, { frame = self.selected_difficulty, index = p_idx }) end
|
|
|
|
if not self.has_2p then self.bottom_chars:draw() end
|
|
|
|
if self.t_judge_good then tex.draw_texture(self.t_judge_good, { index = p_idx }) end
|
|
if self.t_judge_ok then tex.draw_texture(self.t_judge_ok, { index = p_idx }) end
|
|
if self.t_judge_bad then tex.draw_texture(self.t_judge_bad, { index = p_idx }) end
|
|
if self.t_max_combo then tex.draw_texture(self.t_max_combo, { index = p_idx }) end
|
|
if self.t_drumroll then tex.draw_texture(self.t_drumroll, { index = p_idx }) end
|
|
|
|
self:draw_score_info()
|
|
self:draw_total_score()
|
|
|
|
if self.crown_obj then self.crown_obj:draw() end
|
|
if self.crown_msg_obj then self.crown_msg_obj:draw() end
|
|
|
|
self:draw_modifiers()
|
|
|
|
if self.high_score_obj then self.high_score_obj:draw() end
|
|
end
|
|
|
|
function ResultPlayer:draw_gauge()
|
|
if not self.gauge_started then return end
|
|
|
|
local p_idx = self.is_2p and 1 or 0
|
|
local scale = 10.0 / 11.0
|
|
local base_fade = self.gauge_fade_in.attribute
|
|
local gl_int = math.floor(self.gauge_length)
|
|
local clear_pts = { 52, 60, 69 } -- easy / normal+hard / oni norma cells (of 87)
|
|
local clear_pt = clear_pts[self.art_index + 1]
|
|
local bar_w = self.gauge_bar_width
|
|
|
|
if self.t_gauge_unfilled then
|
|
tex.draw_texture(self.t_gauge_unfilled, { scale = scale, fade = base_fade, index = p_idx })
|
|
end
|
|
|
|
if self.gauge_state == 2 then
|
|
local fa = math.floor(self.rainbow_frac) % 8
|
|
local fb = (fa + 1) % 8
|
|
local t = self.rainbow_frac - math.floor(self.rainbow_frac)
|
|
if self.t_gauge_rainbow then
|
|
tex.draw_texture(self.t_gauge_rainbow, { frame = fa, scale = scale, fade = base_fade, index = p_idx })
|
|
tex.draw_texture(self.t_gauge_rainbow, { frame = fb, scale = scale, fade = base_fade * t, index = p_idx })
|
|
end
|
|
else
|
|
if self.t_gauge_bar then
|
|
local x2 = math.min(gl_int * bar_w, (clear_pt - 1) * bar_w) - bar_w
|
|
tex.draw_texture(self.t_gauge_bar, { scale = scale, x2 = x2, fade = base_fade, index = p_idx })
|
|
end
|
|
-- The transition piece is the first gold cell (the rounded cap of the clear zone).
|
|
-- Light it only when the run is cleared, like the HUD gauge (Gauge::get_is_clear):
|
|
-- the norma falls mid-cell (oni 8000 = cell 69.6), so a cell-count test lit it for
|
|
-- a failed run at 7816..7999 soul.
|
|
if gl_int >= clear_pt - 1 and self.gauge_state >= 1 and self.t_gauge_bar_clear_transition then
|
|
tex.draw_texture(self.t_gauge_bar_clear_transition,
|
|
{ scale = scale, x = (clear_pt - 1) * bar_w, fade = base_fade, index = p_idx })
|
|
end
|
|
if gl_int > clear_pt and self.gauge_state >= 1 then
|
|
local extra = (gl_int - clear_pt) * bar_w
|
|
if self.t_gauge_bar_clear_top then
|
|
tex.draw_texture(self.t_gauge_bar_clear_top,
|
|
{ scale = scale, x = clear_pt * bar_w, x2 = extra, fade = base_fade, index = p_idx })
|
|
end
|
|
if self.t_gauge_bar_clear_bottom then
|
|
tex.draw_texture(self.t_gauge_bar_clear_bottom,
|
|
{ scale = scale, x = clear_pt * bar_w, x2 = extra, fade = base_fade, index = p_idx })
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.t_gauge_overlay then
|
|
tex.draw_texture(self.t_gauge_overlay,
|
|
{ scale = scale, fade = math.min(0.15, base_fade), index = p_idx })
|
|
end
|
|
if self.t_gauge_footer then
|
|
tex.draw_texture(self.t_gauge_footer, { scale = scale, fade = base_fade, index = p_idx })
|
|
end
|
|
|
|
local clear_idx = self.art_index + p_idx * 3
|
|
-- A failed run never lights the clear caption, even when the fill touches the zone.
|
|
if gl_int >= clear_pt - 1 and self.gauge_state >= 1 then
|
|
if self.t_gauge_clear then
|
|
tex.draw_texture(self.t_gauge_clear,
|
|
{ scale = scale, fade = base_fade, index = clear_idx })
|
|
end
|
|
if self.gauge_state == 2 and self.t_gauge_tamashii_fire then
|
|
local fire_f = math.floor(self.tamashii_fire.attribute)
|
|
tex.draw_texture(self.t_gauge_tamashii_fire,
|
|
{ frame = fire_f, scale = 0.75 * scale, center = true, fade = base_fade, index = p_idx })
|
|
end
|
|
if self.t_gauge_tamashii then
|
|
tex.draw_texture(self.t_gauge_tamashii, { scale = scale, fade = base_fade, index = p_idx })
|
|
end
|
|
if self.gauge_state == 2 and self.t_gauge_tamashii_overlay then
|
|
local f = math.floor(self.tamashii_fire.attribute)
|
|
if f == 0 or f == 1 or f == 4 or f == 5 then
|
|
tex.draw_texture(self.t_gauge_tamashii_overlay,
|
|
{ scale = scale, fade = math.min(0.5, base_fade), index = p_idx })
|
|
end
|
|
end
|
|
else
|
|
if self.t_gauge_clear_dark then
|
|
tex.draw_texture(self.t_gauge_clear_dark,
|
|
{ scale = scale, fade = base_fade, index = clear_idx })
|
|
end
|
|
if self.t_gauge_tamashii_dark then
|
|
tex.draw_texture(self.t_gauge_tamashii_dark,
|
|
{ scale = scale, fade = base_fade, index = p_idx })
|
|
end
|
|
end
|
|
end
|
|
|
|
function ResultPlayer:draw_score_info()
|
|
local scores = { self.good, self.ok, self.bad, self.max_combo, self.total_drumroll }
|
|
local p_idx = self.is_2p and 1 or 0
|
|
for j, s in ipairs(scores) do
|
|
if s ~= "" and self.t_judge_num then
|
|
local n = math.floor(tonumber(s) or 0)
|
|
local str = tostring(n)
|
|
for i = 1, #str do
|
|
local d = tonumber(string.sub(str, #str - i + 1, #str - i + 1)) or 0
|
|
tex.draw_texture(self.t_judge_num, {
|
|
frame = d,
|
|
x = -((i - 1) * self.sc_score_info_margin),
|
|
index = (j - 1) + p_idx * 5,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ResultPlayer:draw_total_score()
|
|
if not self.fade_in_finished_flag then return end
|
|
local p_idx = self.is_2p and 1 or 0
|
|
if self.score_method then
|
|
if self.t_score_shinuchi_lbl then tex.draw_texture(self.t_score_shinuchi_lbl, { index = p_idx }) end
|
|
else
|
|
if self.t_score_lbl then tex.draw_texture(self.t_score_lbl, { index = p_idx }) end
|
|
end
|
|
if self.score ~= "" and self.t_score_num then
|
|
local n = math.floor(tonumber(self.score) or 0)
|
|
local str = tostring(n)
|
|
for i = 1, #str do
|
|
local d = tonumber(string.sub(str, #str - i + 1, #str - i + 1)) or 0
|
|
tex.draw_texture(self.t_score_num, {
|
|
frame = d,
|
|
x = -((i - 1) * self.sc_result_score_margin),
|
|
index = p_idx,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
function ResultPlayer:draw_modifiers()
|
|
local p_idx = self.is_2p and 1 or 0
|
|
if self.mod_display and self.t_mod_doron then tex.draw_texture(self.t_mod_doron, { index = p_idx }) end
|
|
if self.mod_inverse and self.t_mod_abekobe then tex.draw_texture(self.t_mod_abekobe, { index = p_idx }) end
|
|
if self.mod_random == 1 and self.t_mod_kimagure then
|
|
tex.draw_texture(self.t_mod_kimagure, { index = p_idx })
|
|
elseif self.mod_random == 2 and self.t_mod_detarame then
|
|
tex.draw_texture(self.t_mod_detarame, { index = p_idx })
|
|
end
|
|
if self.mod_speed >= 40 and self.t_mod_yonbai then
|
|
tex.draw_texture(self.t_mod_yonbai, { index = p_idx })
|
|
elseif self.mod_speed >= 30 and self.t_mod_sanbai then
|
|
tex.draw_texture(self.t_mod_sanbai, { index = p_idx })
|
|
elseif self.mod_speed > 10 and self.t_mod_baisaku then
|
|
tex.draw_texture(self.t_mod_baisaku, { index = p_idx })
|
|
end
|
|
end
|