diff --git a/CommonClient.py b/CommonClient.py index febe5c40cf0..3d4d7fedf2f 100755 --- a/CommonClient.py +++ b/CommonClient.py @@ -341,6 +341,10 @@ class CommonContext: """Name used in Connect packet""" seed_name: str | None """Seed name that will be validated on opening a socket if present""" + server_seed_name: str | None + """Actual seed_name reported by the server in RoomInfo""" + connected_identity: tuple[str, int, int] | None + """(server_seed_name, team, slot) of the last successful connection, used to detect a session switch""" # locations locations_checked: set[int] @@ -399,6 +403,10 @@ class CommonContext: self.slot = None self.auth = None self.seed_name = None + self.server_seed_name = None + self.connected_identity = None + # decouple from the class-level mutable set so tag changes (e.g. DeathLink) don't leak across instances + self.tags = set(type(self).tags) self.locations_checked = set() # local state self.locations_scouted = set() @@ -459,6 +467,7 @@ class CommonContext: self.team = None self.items_received = [] self.locations_info = {} + self.server_seed_name = None self.server_version = Version(0, 0, 0) self.generator_version = Version(0, 0, 0) self.server = None @@ -470,6 +479,21 @@ class CommonContext: "remaining": "disabled", } + def reset_session_state(self): + """Clear local progress and data-storage state tied to a single session.""" + self.locations_checked = set() + self.locations_scouted = set() + self.locations_info = {} + self.items_received = [] + self.missing_locations = set() + self.checked_locations = set() + self.server_locations = set() + self.finished_game = False + self.ready = False + self.stored_data = {} + self.stored_data_notification_keys = set() + self.current_energy_link_value = None + async def disconnect(self, allow_autoreconnect: bool = False): if not allow_autoreconnect: self.disconnected_intentionally = True @@ -924,6 +948,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict): logger.exception(f"Could not get command from {args}") raise if cmd == 'RoomInfo': + ctx.server_seed_name = args["seed_name"] if ctx.seed_name and ctx.seed_name != args["seed_name"]: msg = "The server is running a different multiworld than your client is. (invalid seed_name)" logger.info(msg, extra={'compact_gui': True}) @@ -1004,6 +1029,11 @@ async def process_server_cmd(ctx: CommonContext, args: dict): ctx.username = ctx.auth ctx.team = args["team"] ctx.slot = args["slot"] + # on a switch to a different session, clear session state before stale checks/goal are replayed below + identity = (ctx.server_seed_name, ctx.team, ctx.slot) + if ctx.connected_identity is not None and identity != ctx.connected_identity: + ctx.reset_session_state() + ctx.connected_identity = identity # int keys get lost in JSON transfer ctx.slot_info = {0: NetworkSlot("Archipelago", "Archipelago", SlotType.player)} ctx.slot_info.update({int(pid): data for pid, data in args["slot_info"].items()}) diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 142c2964009..9e2c55abed5 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -120,7 +120,6 @@ class BizHawkClientCommandProcessor(ClientCommandProcessor): class BizHawkClientContext(CommonContext): command_processor = BizHawkClientCommandProcessor text_passthrough_categories: set[str] - server_seed_name: str | None = None auth_status: AuthStatus password_requested: bool client_handler: BizHawkClient | None @@ -169,8 +168,6 @@ class BizHawkClientContext(CommonContext): if cmd == "Connected": self.slot_data = args.get("slot_data", None) self.auth_status = AuthStatus.AUTHENTICATED - elif cmd == "RoomInfo": - self.server_seed_name = args.get("seed_name", None) if self.client_handler is not None: self.client_handler.on_package(self, cmd, args) @@ -203,7 +200,6 @@ class BizHawkClientContext(CommonContext): async def disconnect(self, allow_autoreconnect: bool=False): self.auth_status = AuthStatus.NOT_AUTHENTICATED - self.server_seed_name = None await super().disconnect(allow_autoreconnect)