From d0f7b34cb3e12236a6008762a27c81cd4fe1273b Mon Sep 17 00:00:00 2001 From: Wuamp-dev Date: Tue, 5 May 2026 15:32:31 -0400 Subject: [PATCH 1/3] Softlock force out stuck position --- core.lua | 3 +++ networking/action_handlers.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/core.lua b/core.lua index 8391e4a9..16786643 100644 --- a/core.lua +++ b/core.lua @@ -19,6 +19,7 @@ MP.LOBBY = { type = "", config = {}, -- Now set in MP.reset_lobby_config deck = { + back_key = "b_red", back = "Red Deck", sleeve = "sleeve_casl_none", stake = 1, @@ -171,6 +172,7 @@ function MP.reset_lobby_config(persist_ruleset_and_gamemode) weekly = nil, custom_seed = "random", different_decks = false, + back_key = "b_red", back = "Red Deck", sleeve = "sleeve_casl_none", stake = 1, @@ -227,6 +229,7 @@ function MP.reset_game_states() real_money = 0, ce_cache = false, furthest_blind = 0, + pvp_blind_started = false, pincher_index = -3, pincher_unlock = false, asteroids = 0, diff --git a/networking/action_handlers.lua b/networking/action_handlers.lua index 3295a544..02efb86b 100644 --- a/networking/action_handlers.lua +++ b/networking/action_handlers.lua @@ -112,6 +112,26 @@ function Game:update(dt) handle_reconnect_timeout("Reconnection failed.\nReturning to main menu.") end end + + if MP.LOBBY.code and MP.is_pvp_boss() and not MP.GAME.pvp_blind_started then + + -- Check if opponent is already in PvP blind (has "playing" in their location) + if MP.GAME.enemy and MP.GAME.enemy.location and string.find(MP.GAME.enemy.location, "playing") then + sendDebugMessage("Auto-detected stuck outside PvP blind, recovering...", "MULTIPLAYER") + + -- Trigger the same unstuck logic that the button uses + if G.FUNCS.mp_unstuck_blind then + G.FUNCS.mp_unstuck_blind() + end + end + end + + -- Also reset the flag when leaving PvP state + if MP.LOBBY.code and not MP.is_pvp_boss() and MP.GAME.pvp_blind_started then + MP.GAME.pvp_blind_started = false + end + + return _disconnect_gupdate(self, dt) end @@ -250,6 +270,12 @@ local function action_start_blind() MP.GAME.ready_blind = false MP.GAME.timer_started = false MP.GAME.timer = MP.LOBBY.config.timer_base_seconds + MP.GAME.pvp_blind_started = true + + if MP.GAME.stuck_outside_pvp then + MP.GAME.stuck_outside_pvp = nil + end + MP.UI.start_pvp_countdown(begin_pvp_blind) end From fc9936b753494f045a5efc2343313535cba57f97 Mon Sep 17 00:00:00 2001 From: 26tscole <123657398+26tscole@users.noreply.github.com> Date: Tue, 5 May 2026 16:27:34 -0400 Subject: [PATCH 2/3] Changed up the way we check for being stuck. We only check for unsticking if one player enters a pvp blind without the other player. When this happens unstuck will be called and force the player into the pvp blind. --- core.lua | 1 + networking/action_handlers.lua | 39 ++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/core.lua b/core.lua index 16786643..2336d22a 100644 --- a/core.lua +++ b/core.lua @@ -230,6 +230,7 @@ function MP.reset_game_states() ce_cache = false, furthest_blind = 0, pvp_blind_started = false, + pvp_unstuck_attempted = false, pincher_index = -3, pincher_unlock = false, asteroids = 0, diff --git a/networking/action_handlers.lua b/networking/action_handlers.lua index 02efb86b..0fcc54c7 100644 --- a/networking/action_handlers.lua +++ b/networking/action_handlers.lua @@ -113,25 +113,35 @@ function Game:update(dt) end end - if MP.LOBBY.code and MP.is_pvp_boss() and not MP.GAME.pvp_blind_started then + -- Auto-unstuck: if the enemy is already inside the PvP blind but we aren't, + -- force the same transition that the settings button does. + -- Uses actual game state (not a message-received flag) so it covers every + -- scenario that leaves the player stranded outside the blind. + if MP.LOBBY.code + and MP.GAME.next_blind_context -- we know which blind to enter + and not MP.is_pvp_boss() -- we are NOT inside it yet + and not MP.GAME.pvp_unstuck_attempted -- only try once per round + and MP.GAME.enemy + and MP.GAME.enemy.location + and string.find(MP.GAME.enemy.location, "playing") + then + sendDebugMessage("Auto-detected stuck outside PvP blind, recovering...", "MULTIPLAYER") + MP.GAME.pvp_unstuck_attempted = true + if G.FUNCS.mp_unstuck_blind then + G.FUNCS.mp_unstuck_blind() + end + end - -- Check if opponent is already in PvP blind (has "playing" in their location) - if MP.GAME.enemy and MP.GAME.enemy.location and string.find(MP.GAME.enemy.location, "playing") then - sendDebugMessage("Auto-detected stuck outside PvP blind, recovering...", "MULTIPLAYER") + -- Reset the attempt flag once we are safely inside the PvP blind + if MP.GAME.pvp_unstuck_attempted and MP.is_pvp_boss() then + MP.GAME.pvp_unstuck_attempted = false + end - -- Trigger the same unstuck logic that the button uses - if G.FUNCS.mp_unstuck_blind then - G.FUNCS.mp_unstuck_blind() - end - end - end - - -- Also reset the flag when leaving PvP state + -- Reset pvp_blind_started when leaving PvP state if MP.LOBBY.code and not MP.is_pvp_boss() and MP.GAME.pvp_blind_started then MP.GAME.pvp_blind_started = false end - return _disconnect_gupdate(self, dt) end @@ -270,7 +280,8 @@ local function action_start_blind() MP.GAME.ready_blind = false MP.GAME.timer_started = false MP.GAME.timer = MP.LOBBY.config.timer_base_seconds - MP.GAME.pvp_blind_started = true + MP.GAME.pvp_blind_started = true + MP.GAME.pvp_unstuck_attempted = false if MP.GAME.stuck_outside_pvp then MP.GAME.stuck_outside_pvp = nil From 6efce00d21c6d907a15f3af803bf7bc52cd327ea Mon Sep 17 00:00:00 2001 From: 26tscole <123657398+26tscole@users.noreply.github.com> Date: Tue, 5 May 2026 16:40:53 -0400 Subject: [PATCH 3/3] Created a a test that checks all the boxes of the new conditions. Checks all states including both stuck error and no stuck error and returns whether or not the player gets put into pvp or not. It will also check whether or not the in pvp status will get updated or not. Co-authored-by: Copilot --- tests/pvp_auto_unstuck_test.lua | 123 ++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/pvp_auto_unstuck_test.lua diff --git a/tests/pvp_auto_unstuck_test.lua b/tests/pvp_auto_unstuck_test.lua new file mode 100644 index 00000000..78c7571e --- /dev/null +++ b/tests/pvp_auto_unstuck_test.lua @@ -0,0 +1,123 @@ +-- Tests for the auto-unstuck logic in Game:update that detects when a player +-- is stranded outside the PvP blind while their opponent is already inside. + +local function load_game_update(env) + local f = assert(io.open("networking/action_handlers.lua", "r")) + local src = f:read("*a") + f:close() + + local start_idx = assert(src:find("function Game:update%(dt%)")) + local end_idx = assert(src:find("\nlocal function action_enemyDisconnected", start_idx)) + local snippet = src:sub(start_idx, end_idx - 1) + + local chunk = assert(load(snippet, "game_update_snippet", "t", env)) + chunk() + -- Game:update is now defined on env.Game +end + +--- Build a default environment with all mocks in a valid "stuck" state: +--- in a lobby, has next_blind_context, not in PvP blind yet, enemy is playing. +local function make_env() + local env = { + string = string, + math = math, + Game = {}, + MP = { + LOBBY = { code = "ABCD" }, + GAME = { + next_blind_context = { blind = "bl_mp_nemesis" }, + pvp_unstuck_attempted = false, + pvp_blind_started = false, + enemy = { location = "playing_blind" }, + }, + is_pvp_boss = function() return false end, + enemy_disconnect_countdown = nil, + self_reconnect_countdown = nil, + }, + G = { + FUNCS = {}, + }, + love = { + timer = { getTime = function() return 100 end }, + }, + sendDebugMessage = function() end, + handle_reconnect_timeout = function() end, + _disconnect_gupdate = function() return true end, + } + return env +end + +describe("pvp auto-unstuck (Game:update)", function() + + it("calls mp_unstuck_blind when stuck outside PvP blind", function() + local env = make_env() + local unstuck_called = 0 + env.G.FUNCS.mp_unstuck_blind = function() unstuck_called = unstuck_called + 1 end + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(unstuck_called == 1, "mp_unstuck_blind should have been called once") + assert(env.MP.GAME.pvp_unstuck_attempted == true, "pvp_unstuck_attempted should be set") + end) + + it("does not fire a second time after pvp_unstuck_attempted is set", function() + local env = make_env() + local unstuck_called = 0 + env.G.FUNCS.mp_unstuck_blind = function() unstuck_called = unstuck_called + 1 end + env.MP.GAME.pvp_unstuck_attempted = true -- already tried + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(unstuck_called == 0, "mp_unstuck_blind should NOT fire when already attempted") + end) + + it("does not fire when player is already inside the PvP blind", function() + local env = make_env() + local unstuck_called = 0 + env.G.FUNCS.mp_unstuck_blind = function() unstuck_called = unstuck_called + 1 end + env.MP.is_pvp_boss = function() return true end -- already inside + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(unstuck_called == 0, "mp_unstuck_blind should NOT fire when already in PvP blind") + end) + + it("does not fire when there is no next_blind_context", function() + local env = make_env() + local unstuck_called = 0 + env.G.FUNCS.mp_unstuck_blind = function() unstuck_called = unstuck_called + 1 end + env.MP.GAME.next_blind_context = nil + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(unstuck_called == 0, "mp_unstuck_blind should NOT fire without next_blind_context") + end) + + it("does not fire when enemy location does not contain 'playing'", function() + local env = make_env() + local unstuck_called = 0 + env.G.FUNCS.mp_unstuck_blind = function() unstuck_called = unstuck_called + 1 end + env.MP.GAME.enemy.location = "loc_selecting" + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(unstuck_called == 0, "mp_unstuck_blind should NOT fire when enemy is not playing") + end) + + it("resets pvp_unstuck_attempted once player enters the PvP blind", function() + local env = make_env() + env.MP.GAME.pvp_unstuck_attempted = true + env.MP.is_pvp_boss = function() return true end -- now inside the blind + + load_game_update(env) + env.Game.update(env.Game, 0) + + assert(env.MP.GAME.pvp_unstuck_attempted == false, "pvp_unstuck_attempted should reset after entering PvP blind") + end) + +end)