@@ -65,37 +65,134 @@ function MP.MATCH_RECORD.finalize(won)
6565 MP .MATCH_RECORD .final_ante = G .GAME .round_resets and G .GAME .round_resets .ante or 1
6666end
6767
68- -- Ghost Replay playback state
69- -- Loads a stored match record and provides enemy scores for PvP blinds
70- -- so practice mode can simulate playing against a past opponent.
68+ MP .GHOST = { active = false , replay = nil , flipped = false , gamemode = nil }
7169
72- MP .GHOST = { active = false , replay = nil , flipped = false }
70+ -- Per-ante playback state
71+ MP .GHOST ._hands = {}
72+ MP .GHOST ._hand_idx = 0
73+ MP .GHOST ._advancing = false
7374
7475function MP .GHOST .load (replay )
7576 MP .GHOST .active = true
7677 MP .GHOST .replay = replay
7778 MP .GHOST .flipped = false
79+ MP .GHOST .gamemode = replay and replay .gamemode or nil
80+ MP .GHOST ._hands = {}
81+ MP .GHOST ._hand_idx = 0
82+ MP .GHOST ._advancing = false
7883end
7984
8085function MP .GHOST .clear ()
8186 MP .GHOST .active = false
8287 MP .GHOST .replay = nil
8388 MP .GHOST .flipped = false
89+ MP .GHOST .gamemode = nil
90+ MP .GHOST ._hands = {}
91+ MP .GHOST ._hand_idx = 0
92+ MP .GHOST ._advancing = false
8493end
8594
8695function MP .GHOST .flip ()
8796 MP .GHOST .flipped = not MP .GHOST .flipped
8897end
8998
99+ function MP .GHOST .get_enemy_hands (ante )
100+ if not MP .GHOST .replay or not MP .GHOST .replay .ante_snapshots then return {} end
101+ local snapshot = MP .GHOST .replay .ante_snapshots [ante ] or MP .GHOST .replay .ante_snapshots [tostring (ante )]
102+ if not snapshot or not snapshot .hands then return {} end
103+ local enemy_side = MP .GHOST .flipped and " player" or " enemy"
104+ local out = {}
105+ for _ , h in ipairs (snapshot .hands ) do
106+ if h .side == enemy_side then
107+ out [# out + 1 ] = h
108+ end
109+ end
110+ return out
111+ end
112+
113+ -- Fallback for replays without hand-level data
90114function MP .GHOST .get_enemy_score (ante )
91115 if not MP .GHOST .replay or not MP .GHOST .replay .ante_snapshots then return nil end
92116 local snapshot = MP .GHOST .replay .ante_snapshots [ante ] or MP .GHOST .replay .ante_snapshots [tostring (ante )]
93117 if not snapshot then return nil end
94- -- When flipped, the original player's score becomes the ghost target
95118 local key = MP .GHOST .flipped and " player_score" or " enemy_score"
96119 return snapshot [key ]
97120end
98121
122+ function MP .GHOST .init_playback (ante )
123+ local hands = MP .GHOST .get_enemy_hands (ante )
124+ MP .GHOST ._hands = hands
125+ MP .GHOST ._hand_idx = 0
126+ MP .GHOST ._advancing = false
127+ if # hands > 0 then
128+ MP .GHOST ._hand_idx = 1
129+ local score = MP .INSANE_INT .from_string (hands [1 ].score )
130+ MP .GAME .enemy .score = score
131+ MP .GAME .enemy .score_text = MP .INSANE_INT .to_string (score )
132+ MP .GAME .enemy .hands = hands [1 ].hands_left or 0
133+ return true
134+ else
135+ local score_str = MP .GHOST .get_enemy_score (ante )
136+ if score_str then
137+ MP .GAME .enemy .score = MP .INSANE_INT .from_string (score_str )
138+ MP .GAME .enemy .score_text = MP .INSANE_INT .to_string (MP .GAME .enemy .score )
139+ end
140+ return false
141+ end
142+ end
143+
144+ function MP .GHOST .advance_hand ()
145+ if MP .GHOST ._hand_idx >= # MP .GHOST ._hands then return false end
146+ MP .GHOST ._hand_idx = MP .GHOST ._hand_idx + 1
147+ local entry = MP .GHOST ._hands [MP .GHOST ._hand_idx ]
148+ local score = MP .INSANE_INT .from_string (entry .score )
149+
150+ G .E_MANAGER :add_event (Event ({
151+ blockable = false , blocking = false ,
152+ trigger = " ease" , delay = 0.5 ,
153+ ref_table = MP .GAME .enemy .score ,
154+ ref_value = " e_count" ,
155+ ease_to = score .e_count ,
156+ func = function (t ) return math.floor (t ) end ,
157+ }))
158+ G .E_MANAGER :add_event (Event ({
159+ blockable = false , blocking = false ,
160+ trigger = " ease" , delay = 0.5 ,
161+ ref_table = MP .GAME .enemy .score ,
162+ ref_value = " coeffiocient" ,
163+ ease_to = score .coeffiocient ,
164+ func = function (t ) return math.floor (t ) end ,
165+ }))
166+ G .E_MANAGER :add_event (Event ({
167+ blockable = false , blocking = false ,
168+ trigger = " ease" , delay = 0.5 ,
169+ ref_table = MP .GAME .enemy .score ,
170+ ref_value = " exponent" ,
171+ ease_to = score .exponent ,
172+ func = function (t ) return math.floor (t ) end ,
173+ }))
174+
175+ MP .GAME .enemy .hands = entry .hands_left or 0
176+ if MP .UI .juice_up_pvp_hud then MP .UI .juice_up_pvp_hud () end
177+ return true
178+ end
179+
180+ function MP .GHOST .playback_exhausted ()
181+ return # MP .GHOST ._hands == 0 or MP .GHOST ._hand_idx >= # MP .GHOST ._hands
182+ end
183+
184+ function MP .GHOST .has_hand_data ()
185+ return # MP .GHOST ._hands > 0
186+ end
187+
188+ -- Reads target from hands array directly, bypassing the eased score table.
189+ function MP .GHOST .current_target_big ()
190+ if MP .GHOST ._hand_idx < 1 or MP .GHOST ._hand_idx > # MP .GHOST ._hands then return to_big (0 ) end
191+ local entry = MP .GHOST ._hands [MP .GHOST ._hand_idx ]
192+ local score = MP .INSANE_INT .from_string (entry .score )
193+ return to_big (score .coeffiocient * (10 ^ score .exponent ))
194+ end
195+
99196function MP .GHOST .get_nemesis_name ()
100197 if not MP .GHOST .replay then return nil end
101198 if MP .GHOST .flipped then
0 commit comments