From 5579fbf2a0ce2e186a34db5b5a4ef11c95f2e134 Mon Sep 17 00:00:00 2001 From: Brian0255 Date: Wed, 24 Jun 2026 23:57:35 -0400 Subject: [PATCH 1/5] put current map and entrance into data storage --- worlds/gstla/BizClient.py | 40 ++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/worlds/gstla/BizClient.py b/worlds/gstla/BizClient.py index e14ec823582b..20cec22f2b6d 100644 --- a/worlds/gstla/BizClient.py +++ b/worlds/gstla/BizClient.py @@ -31,7 +31,8 @@ class _MemDomain(str, Enum): ROM = 'ROM' class _DataLocations(IntEnum): - IN_GAME = (0x428, 0x2, 0x0, _MemDomain.EWRAM) + MAP_ID = (0x428, 0x2, 0x0, _MemDomain.EWRAM) + ENTRANCE_ID = (0x42A, 0x2, 0x0, _MemDomain.EWRAM) DJINN_FLAGS = (FLAG_START + (0x30 >> 3), 0x0A, 0x30, _MemDomain.EWRAM) AP_ITEM_SLOT = (0xA96, 0x2, 0x0, _MemDomain.EWRAM) # two unused bytes in save data @@ -293,6 +294,8 @@ def __init__(self): self.coop: int = 0 self.remote_blacklist: Set[int] = remote_blacklist self.goals = GoalManager() + self.last_map: int = 0 + self.last_entrance: int = 0 async def validate_rom(self, ctx: 'BizHawkClientContext'): from worlds._bizhawk.context import TextCategory @@ -338,10 +341,9 @@ async def _load_djinn(self, ctx: 'BizHawkClientContext') -> None: self.djinn_ram_to_rom[rom_flag] = djinn_flag def _is_in_game(self, data: List[bytes]) -> bool: - # What the emo tracker pack does; seems like it also verifies the player - # has opened a save file - flag = int.from_bytes(data[_DataLocations.IN_GAME], 'little') - return flag > 1 + # Map ID 0x00 and 0x01 are Title Screen and Clear Data respectively, everything greater is safe + current_map = int.from_bytes(data[_DataLocations.MAP_ID], 'little') + return current_map > 1 def get_goal_flags_key(self, ctx: 'BizHawkClientContext') -> str: return f"gstla_goal_flags_status_{ctx.slot}_{ctx.team}" @@ -355,6 +357,12 @@ def get_djinn_location_key(self, ctx: 'BizHawkClientContext') -> str: def get_djinn_possessed_key(self, ctx: 'BizHawkClientContext') -> str: return f"gstla_djinn_held_{ctx.slot}_{ctx.team}" + def get_current_map_key(self, ctx: 'BizHawkClientContext') -> str: + return f"gstla_current_map_{ctx.slot}_{ctx.team}" + + def get_current_entrance_key(self, ctx: 'BizHawkClientContext') -> str: + return f"gstla_current_entrance_{ctx.slot}_{ctx.team}" + def check_summon_count(self, ctx: "BizHawkClientContext") -> None: if self.summon_item_index >= len(ctx.items_received): return @@ -606,6 +614,28 @@ async def game_watcher(self, ctx: 'BizHawkClientContext') -> None: await self.goals.check_goals(self, ctx) + current_map = int.from_bytes(result[_DataLocations.MAP_ID], 'little') + if current_map != self.last_map: + self.last_map = current_map + await ctx.send_msgs([{ + "cmd": "Set", + "key": self.get_current_map_key(ctx), + "default": 0, + "want_reply": False, + "operations": [{"operation": "replace", "value": current_map}] + }]) + + current_entrance = int.from_bytes(result[_DataLocations.ENTRANCE_ID], 'little') + if current_entrance != self.last_entrance: + self.last_entrance = current_entrance + await ctx.send_msgs([{ + "cmd": "Set", + "key": self.get_current_entrance_key(ctx), + "default": 0, + "want_reply": False, + "operations": [{"operation": "replace", "value": current_entrance}] + }]) + if not ctx.finished_game and self.goals.is_done(self, ctx): await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) ctx.finished_game = True From 23224e62a344d025d3e954cfceca33446841cb35 Mon Sep 17 00:00:00 2001 From: Brian0255 Date: Fri, 26 Jun 2026 04:18:53 -0400 Subject: [PATCH 2/5] send both together as a dict to avoid desync issues --- worlds/gstla/BizClient.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/worlds/gstla/BizClient.py b/worlds/gstla/BizClient.py index 20cec22f2b6d..61a90debf422 100644 --- a/worlds/gstla/BizClient.py +++ b/worlds/gstla/BizClient.py @@ -357,11 +357,8 @@ def get_djinn_location_key(self, ctx: 'BizHawkClientContext') -> str: def get_djinn_possessed_key(self, ctx: 'BizHawkClientContext') -> str: return f"gstla_djinn_held_{ctx.slot}_{ctx.team}" - def get_current_map_key(self, ctx: 'BizHawkClientContext') -> str: - return f"gstla_current_map_{ctx.slot}_{ctx.team}" - - def get_current_entrance_key(self, ctx: 'BizHawkClientContext') -> str: - return f"gstla_current_entrance_{ctx.slot}_{ctx.team}" + def get_map_info_key(self, ctx: 'BizHawkClientContext') -> str: + return f"gstla_map_info_{ctx.slot}_{ctx.team}" def check_summon_count(self, ctx: "BizHawkClientContext") -> None: if self.summon_item_index >= len(ctx.items_received): @@ -599,13 +596,12 @@ async def game_watcher(self, ctx: 'BizHawkClientContext') -> None: if self.temp_events: await ctx.send_msgs([{ "cmd": "Set", - "operation": "update", "want_reply": True, "key": self.get_event_key(ctx), "default": [], "operations": [ { - "operation": "update", + "operation": "replace", "value": [x for x in self.temp_events], } ] @@ -615,25 +611,22 @@ async def game_watcher(self, ctx: 'BizHawkClientContext') -> None: await self.goals.check_goals(self, ctx) current_map = int.from_bytes(result[_DataLocations.MAP_ID], 'little') - if current_map != self.last_map: - self.last_map = current_map - await ctx.send_msgs([{ - "cmd": "Set", - "key": self.get_current_map_key(ctx), - "default": 0, - "want_reply": False, - "operations": [{"operation": "replace", "value": current_map}] - }]) - current_entrance = int.from_bytes(result[_DataLocations.ENTRANCE_ID], 'little') - if current_entrance != self.last_entrance: + if current_map != self.last_map or current_entrance != self.last_entrance: + self.last_map = current_map self.last_entrance = current_entrance await ctx.send_msgs([{ "cmd": "Set", - "key": self.get_current_entrance_key(ctx), - "default": 0, + "key": self.get_map_info_key(ctx), + "default": {}, "want_reply": False, - "operations": [{"operation": "replace", "value": current_entrance}] + "operations": [{ + "operation": "update", + "value": { + "map": current_map, + "entrance": current_entrance + } + }] }]) if not ctx.finished_game and self.goals.is_done(self, ctx): From 71e9b59a904d46c7e5a0496da498a75a55f047ee Mon Sep 17 00:00:00 2001 From: Brian0255 Date: Fri, 26 Jun 2026 04:21:07 -0400 Subject: [PATCH 3/5] this probably should be an explicit replace --- worlds/gstla/BizClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/gstla/BizClient.py b/worlds/gstla/BizClient.py index 61a90debf422..4da14b73b671 100644 --- a/worlds/gstla/BizClient.py +++ b/worlds/gstla/BizClient.py @@ -621,7 +621,7 @@ async def game_watcher(self, ctx: 'BizHawkClientContext') -> None: "default": {}, "want_reply": False, "operations": [{ - "operation": "update", + "operation": "replace", "value": { "map": current_map, "entrance": current_entrance From cbdbddf727502c25f68ee21fb6ac50e544e3b779 Mon Sep 17 00:00:00 2001 From: Brian0255 Date: Fri, 26 Jun 2026 06:27:16 -0400 Subject: [PATCH 4/5] oops --- worlds/gstla/BizClient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/gstla/BizClient.py b/worlds/gstla/BizClient.py index 4da14b73b671..c36bb7964da7 100644 --- a/worlds/gstla/BizClient.py +++ b/worlds/gstla/BizClient.py @@ -596,12 +596,13 @@ async def game_watcher(self, ctx: 'BizHawkClientContext') -> None: if self.temp_events: await ctx.send_msgs([{ "cmd": "Set", + "operation": "update", "want_reply": True, "key": self.get_event_key(ctx), "default": [], "operations": [ { - "operation": "replace", + "operation": "update", "value": [x for x in self.temp_events], } ] From bd3b6d05087d63e5c156998db0d8f354bd2a1ebf Mon Sep 17 00:00:00 2001 From: Brian0255 Date: Sat, 4 Jul 2026 09:20:56 -0400 Subject: [PATCH 5/5] add extra clarification on coop option --- worlds/gstla/Options.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/worlds/gstla/Options.py b/worlds/gstla/Options.py index 263915c39a39..0ffe40eac596 100644 --- a/worlds/gstla/Options.py +++ b/worlds/gstla/Options.py @@ -617,9 +617,13 @@ class AutoRun(Toggle): class Coop(Choice): """Enables cooping a seed by making local items the same as remote items, enabling - your friends to receive items you pick up. Also helpful if trying to play a world from multiple devices. + your friends to receive items you pick up. Also helpful if trying to play a world from multiple devices. You will still need to pickup djinn the other person picks up. - off - vanilla + + Note: Due to current limitations, if you are enabling this to coop a seed and you want to use this game's PopTracker pack, + you should turn automatic tabbing off in the pack's settings to avoid constant map changes. + + off - Vanilla progression - Only progression items are treated as remote items prog_useful - Progression and useful items are treated as remote items all - All items are treated as remote items