| 
									
										
										
										
											2025-09-04 23:48:53 -04:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | from typing import TYPE_CHECKING, Sequence | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  | import asyncio | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  | import NetUtils | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  | import copy | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  | import uuid | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | import Utils | 
					
						
							| 
									
										
										
										
											2025-08-17 21:22:12 -04:00
										 |  |  | from .Locations import grinch_locations, GrinchLocation | 
					
						
							| 
									
										
										
										
											2025-08-31 13:59:20 -04:00
										 |  |  | from .Items import ALL_ITEMS_TABLE, MISSION_ITEMS_TABLE, GADGETS_TABLE, KEYS_TABLE, GrinchItemData #, SLEIGH_PARTS_TABLE | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | import worlds._bizhawk as bizhawk | 
					
						
							|  |  |  | from worlds._bizhawk.client import BizHawkClient | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from worlds._bizhawk.context import BizHawkClientContext | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |     from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  | # Stores received index of last item received in PS1 memory card save data | 
					
						
							|  |  |  | # By storing this index, it will remember the last item received and prevent item duplication loops | 
					
						
							| 
									
										
										
										
											2025-08-30 09:09:35 -04:00
										 |  |  | RECV_ITEM_ADDR = 0x010068 | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  | RECV_ITEM_BITSIZE = 4 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  | # Maximum number of times we check if we are in demo mode or not | 
					
						
							|  |  |  | MAX_DEMO_MODE_CHECK = 30 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  | # List of Menu Map IDs | 
					
						
							|  |  |  | MENU_MAP_IDS: list[int] = [0x00, 0x02, 0x35, 0x36, 0x37] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | MAX_EGGS: int = 200 | 
					
						
							|  |  |  | EGG_COUNT_ADDR: int = 0x010058 | 
					
						
							|  |  |  | EGG_ADDR_BYTESIZE: int = 2 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | class GrinchClient(BizHawkClient): | 
					
						
							|  |  |  |     game = "The Grinch" | 
					
						
							|  |  |  |     system = "PSX" | 
					
						
							|  |  |  |     patch_suffix = ".apgrinch" | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |     items_handling = 0b111 | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |     demo_mode_buffer: int = 0 | 
					
						
							|  |  |  |     last_map_location: int = -1 | 
					
						
							|  |  |  |     ingame_log: bool = False | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  |     previous_egg_count: int = 0 | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |     send_ring_link: bool = False | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |     unique_client_id: int = 0 | 
					
						
							| 
									
										
										
										
											2025-10-03 22:23:16 -04:00
										 |  |  |     ring_link_enabled = False | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self): | 
					
						
							|  |  |  |         super().__init__() | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         self.last_received_index = 0 | 
					
						
							|  |  |  |         self.loading_bios_msg = False | 
					
						
							|  |  |  |         self.loc_unlimited_eggs = False | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |         self.unique_client_id = 0 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							|  |  |  |         # TODO Check the ROM data to see if it matches against bytes expected | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  |         grinch_identifier_ram_address: int = 0x00928C | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         bios_identifier_ram_address: int = 0x097F30 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             bytes_actual: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |                 grinch_identifier_ram_address, 11, "MainRAM")]))[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             psx_rom_name = bytes_actual.decode("ascii") | 
					
						
							|  |  |  |             if psx_rom_name != "SLUS_011.97": | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |                 bios_bytes_check: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |                     bios_identifier_ram_address, 24, "MainRAM")]))[0] | 
					
						
							|  |  |  |                 if "System ROM Version" in bios_bytes_check.decode("ascii"): | 
					
						
							|  |  |  |                     if not self.loading_bios_msg: | 
					
						
							|  |  |  |                         self.loading_bios_msg = True | 
					
						
							|  |  |  |                         logger.error("BIOS is currently loading. Will wait up to 5 seconds before retrying.") | 
					
						
							|  |  |  |                     return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |                 logger.error("Invalid rom detected. You are not playing Grinch USA Version.") | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |                 raise Exception("Invalid rom detected. You are not playing Grinch USA Version.") | 
					
						
							| 
									
										
										
										
											2025-09-22 21:38:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |             ctx.command_processor.commands["ringlink"] = _cmd_ringlink | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         except Exception: | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ctx.game = self.game | 
					
						
							|  |  |  |         ctx.items_handling = self.items_handling | 
					
						
							|  |  |  |         ctx.want_slot_data = True | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  |         ctx.watcher_timeout = 0.125 | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         self.loading_bios_msg = False | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-09 01:29:32 -04:00
										 |  |  |     def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: | 
					
						
							| 
									
										
										
										
											2025-08-31 13:45:51 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  |         super().on_package(ctx, cmd, args) | 
					
						
							|  |  |  |         match cmd: | 
					
						
							|  |  |  |             case "Connected":  # On Connect | 
					
						
							|  |  |  |                 self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"]) | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |                 self.unique_client_id = self._get_uuid() | 
					
						
							| 
									
										
										
										
											2025-08-31 13:47:30 -04:00
										 |  |  |                 logger.info("You are now connected to the client. "+ | 
					
						
							|  |  |  |                     "There may be a slight delay to check you are not in demo mode before locations start to send.") | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-03 22:23:16 -04:00
										 |  |  |                 self.ring_link_enabled = bool(ctx.slot_data["ring_link"]) | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 tags = copy.deepcopy(ctx.tags) | 
					
						
							| 
									
										
										
										
											2025-10-03 22:23:16 -04:00
										 |  |  |                 if self.ring_link_enabled: | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  |                     ctx.tags.add("RingLink") | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     ctx.tags -= { "RingLink" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if tags != ctx.tags: | 
					
						
							| 
									
										
										
										
											2025-09-09 01:29:32 -04:00
										 |  |  |                     Utils.async_start(ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]), "Update RingLink Tags") | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |             case "Bounced": | 
					
						
							|  |  |  |                 if "tags" not in args: | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |                 if "RingLink" in ctx.tags and "RingLink" in args["tags"] and args["data"]["source"] != self.unique_client_id: | 
					
						
							| 
									
										
										
										
											2025-09-09 01:29:32 -04:00
										 |  |  |                     Utils.async_start(self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs") | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |     async def set_auth(self, ctx: "BizHawkClientContext") -> None: | 
					
						
							|  |  |  |         await ctx.get_username() | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async def game_watcher(self, ctx: "BizHawkClientContext") -> None: | 
					
						
							| 
									
										
										
										
											2025-09-26 21:40:52 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |         #If the player is not connected to an AP Server, or their connection was disconnected. | 
					
						
							| 
									
										
										
										
											2025-09-06 20:05:36 -04:00
										 |  |  |         if not ctx.slot: | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  |             if not await self.ingame_checker(ctx): | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-03 22:23:16 -04:00
										 |  |  |             if not any(task.get_name() == "Grinch EggLink" for task in asyncio.all_tasks()): | 
					
						
							|  |  |  |                 print("EggLink") | 
					
						
							|  |  |  |                 self.send_ring_link = True | 
					
						
							|  |  |  |                 Utils.async_start(self.ring_link_output(ctx), name="Grinch EggLink") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             await self.location_checker(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |             await self.receiving_items_handler(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  |             await self.goal_checker(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  |             await self.option_handler(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |             await self.constant_address_update(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |         except bizhawk.RequestFailedError as ex: | 
					
						
							| 
									
										
										
										
											2025-08-04 23:03:31 -04:00
										 |  |  |             # The connector didn't respond. Exit handler and return to main loop to reconnect | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |             logger.error("Failure to connect / authenticate the grinch. Error details: " + str(ex)) | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             pass | 
					
						
							| 
									
										
										
										
											2025-09-06 19:37:46 -04:00
										 |  |  |         except Exception as genericEx: | 
					
						
							| 
									
										
										
										
											2025-09-06 19:43:57 -04:00
										 |  |  |             # For all other errors, catch this and let the client gracefully disconnect | 
					
						
							| 
									
										
										
										
											2025-09-06 19:37:46 -04:00
										 |  |  |             logger.error("Unknown error occurred while playing the grinch. Error details: " + str(genericEx)) | 
					
						
							|  |  |  |             await ctx.disconnect(False) | 
					
						
							|  |  |  |             pass | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async def location_checker(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |         # Update the AP Server to know what locations are not checked yet. | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         local_locations_checked: list[int] = [] | 
					
						
							| 
									
										
										
										
											2025-09-10 23:19:36 -04:00
										 |  |  |         addr_list_to_read: list[tuple[int, int, str]] = [] | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  |         local_ap_locations: set[int] = copy.deepcopy(ctx.missing_locations) | 
					
						
							| 
									
										
										
										
											2025-09-10 23:19:36 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Loop through the first time of everything left to create the list of RAM addresses to read / monitor. | 
					
						
							|  |  |  |         for missing_location in local_ap_locations: | 
					
						
							|  |  |  |             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | 
					
						
							|  |  |  |             grinch_loc_ram_data = grinch_locations[grinch_loc_name] | 
					
						
							|  |  |  |             missing_addr_list: list[tuple[int, int, str]] = [(read_addr.ram_address, read_addr.bit_size, "MainRAM") for | 
					
						
							|  |  |  |                                                              read_addr in grinch_loc_ram_data.update_ram_addr] | 
					
						
							|  |  |  |             addr_list_to_read = [*addr_list_to_read, *missing_addr_list] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         returned_bytes: list[bytes] = await bizhawk.read(ctx.bizhawk_ctx, addr_list_to_read) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Now loop through everything again and this time get the byte value from the above read, convert to int, | 
					
						
							|  |  |  |         # and check to see if that ram address has our expected value. | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  |         for missing_location in local_ap_locations: | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             # Missing location is the AP ID & we need to convert it back to a location name within our game. | 
					
						
							|  |  |  |             # Using the location name, we can then get the Grinch ram data from there. | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  |             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | 
					
						
							|  |  |  |             grinch_loc_ram_data = grinch_locations[grinch_loc_name] | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location | 
					
						
							| 
									
										
										
										
											2025-08-25 17:03:48 -04:00
										 |  |  |             # We use a list here to keep track of all our checks. If they are all true, then and only then do we mark that location as checked. | 
					
						
							|  |  |  |             ram_checked_list: list[bool] = [] | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             for addr_to_update in grinch_loc_ram_data.update_ram_addr: | 
					
						
							|  |  |  |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | 
					
						
							| 
									
										
										
										
											2025-09-10 23:19:36 -04:00
										 |  |  |                 orig_index: int = addr_list_to_read.index((addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")) | 
					
						
							|  |  |  |                 value_read_from_bizhawk: int = int.from_bytes(returned_bytes[orig_index], "little") | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |                 if is_binary: | 
					
						
							| 
									
										
										
										
											2025-09-10 23:19:36 -04:00
										 |  |  |                     ram_checked_list.append((value_read_from_bizhawk & (1 << addr_to_update.binary_bit_pos)) > 0) | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     expected_int_value = addr_to_update.value | 
					
						
							| 
									
										
										
										
											2025-09-10 23:19:36 -04:00
										 |  |  |                     ram_checked_list.append(expected_int_value == value_read_from_bizhawk) | 
					
						
							| 
									
										
										
										
											2025-09-01 15:49:09 -04:00
										 |  |  |             if all(ram_checked_list): | 
					
						
							|  |  |  |                 local_locations_checked.append(GrinchLocation.get_apid(grinch_loc_ram_data.id)) | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch) | 
					
						
							| 
									
										
										
										
											2025-08-30 17:26:06 -04:00
										 |  |  |         locations_sent_to_ap: set[int] = await ctx.check_locations(local_locations_checked) | 
					
						
							|  |  |  |         if len(locations_sent_to_ap) > 0: | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |             await self.remove_physical_items(ctx) | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         ctx.locations_checked = set(local_locations_checked) | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async def receiving_items_handler(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |         # Len will give us the size of the items received list & we will track that against how many items we received already | 
					
						
							|  |  |  |         # If the list says that we have 3 items and we already received items, we will ignore and continue. | 
					
						
							|  |  |  |         # Otherwise, we will get the new items and give them to the player. | 
					
						
							| 
									
										
										
										
											2025-08-19 01:59:18 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.last_received_index = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |             RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |         if len(ctx.items_received) == self.last_received_index: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         # Ensures we only get the new items that we want to give the player | 
					
						
							|  |  |  |         new_items_only = ctx.items_received[self.last_received_index:] | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         ram_addr_dict: dict[int, list[int]] = {} | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for item_received in new_items_only: | 
					
						
							|  |  |  |             local_item = ctx.item_names.lookup_in_game(item_received.item) | 
					
						
							| 
									
										
										
										
											2025-08-06 23:34:52 -04:00
										 |  |  |             grinch_item_ram_data = ALL_ITEMS_TABLE[local_item] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for addr_to_update in grinch_item_ram_data.update_ram_addr: | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                 if addr_to_update.ram_address in ram_addr_dict.keys(): | 
					
						
							|  |  |  |                     current_ram_address_value = ram_addr_dict[addr_to_update.ram_address][0] | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |                         addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |                 if is_binary: | 
					
						
							|  |  |  |                     current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos)) | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |                 elif addr_to_update.update_existing_value: | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  |                     # Grabs minimum value of a list of numbers and makes sure it does not go above max count possible | 
					
						
							| 
									
										
										
										
											2025-08-17 21:22:12 -04:00
										 |  |  |                     current_ram_address_value += addr_to_update.value | 
					
						
							|  |  |  |                     current_ram_address_value = min(current_ram_address_value, addr_to_update.max_count) | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  |                 else: | 
					
						
							|  |  |  |                     current_ram_address_value = addr_to_update.value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Write the updated value back into RAM | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                 ram_addr_dict[addr_to_update.ram_address] = [current_ram_address_value, addr_to_update.bit_size] | 
					
						
							| 
									
										
										
										
											2025-08-07 00:38:42 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |             self.last_received_index += 1 | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         # Update the latest received item index to ram as well. | 
					
						
							|  |  |  |         ram_addr_dict[RECV_ITEM_ADDR] = [self.last_received_index, RECV_ITEM_BITSIZE] | 
					
						
							|  |  |  |         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  |     async def goal_checker(self, ctx: "BizHawkClientContext"): | 
					
						
							|  |  |  |         if not ctx.finished_game: | 
					
						
							| 
									
										
										
										
											2025-09-20 21:41:40 -04:00
										 |  |  |             goal_loc = grinch_locations["MC - Sleigh Ride - Neutralizing Santa"] | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  |             goal_ram_address = goal_loc.update_ram_addr[0] | 
					
						
							|  |  |  |             current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |                 goal_ram_address.ram_address, goal_ram_address.bit_size, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-09-26 21:40:52 -04:00
										 |  |  |             # if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0: | 
					
						
							|  |  |  |             if current_ram_address_value == goal_ram_address.value: | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  |                 ctx.finished_game = True | 
					
						
							|  |  |  |                 await ctx.send_msgs([{ | 
					
						
							|  |  |  |                     "cmd": "StatusUpdate", | 
					
						
							|  |  |  |                     "status": NetUtils.ClientStatus.CLIENT_GOAL, | 
					
						
							|  |  |  |                 }]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |     # This function's entire purpose is to take away items we physically received ingame, but have not received from AP | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |     async def remove_physical_items(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         ram_addr_dict: dict[int, list[int]] = {} | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:27:19 -04:00
										 |  |  |         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | 
					
						
							| 
									
										
										
										
											2025-08-31 13:21:54 -04:00
										 |  |  |         items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE | 
					
						
							| 
									
										
										
										
											2025-08-25 18:49:44 -04:00
										 |  |  |         heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570)) | 
					
						
							|  |  |  |         heart_item_data = ALL_ITEMS_TABLE["Heart of Stone"] | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         ram_addr_dict[heart_item_data.update_ram_addr[0].ram_address] = [min(heart_count, 4), 1] | 
					
						
							| 
									
										
										
										
											2025-08-25 18:49:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  |         # Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         ram_addr_dict[0x0100F0] = [0, 4] | 
					
						
							| 
									
										
										
										
											2025-08-31 13:21:54 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:27:19 -04:00
										 |  |  |         for (item_name, item_data) in items_to_check.items(): | 
					
						
							|  |  |  |             # If item is an event or already been received, ignore. | 
					
						
							|  |  |  |             if item_data.id is None or GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # This assumes we don't have the item so we must set all the data to 0 | 
					
						
							|  |  |  |             for addr_to_update in item_data.update_ram_addr: | 
					
						
							|  |  |  |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | 
					
						
							|  |  |  |                 if is_binary: | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | 
					
						
							|  |  |  |                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |                     current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                     ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def convert_dict_to_ram_list(self, addr_dict: dict[int, list[int]]) -> list[tuple[int, Sequence[int], str]]: | 
					
						
							|  |  |  |         addr_list_to_update: list[tuple[int, Sequence[int], str]] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (key, val) in addr_dict.items(): | 
					
						
							|  |  |  |             addr_list_to_update.append((key, val[0].to_bytes(val[1], "little"), "MainRAM")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return addr_list_to_update | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Removes the regional access until you actually received it from AP. | 
					
						
							|  |  |  |     async def constant_address_update(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         ram_addr_dict: dict[int, list[int]] = {} | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | 
					
						
							| 
									
										
										
										
											2025-08-31 13:21:54 -04:00
										 |  |  |         items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE} | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for (item_name, item_data) in items_to_check.items(): | 
					
						
							|  |  |  |             # If item is an event or already been received, ignore. | 
					
						
							| 
									
										
										
										
											2025-09-06 19:22:34 -04:00
										 |  |  |             if item_data.id is None: # or GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-06 19:22:34 -04:00
										 |  |  |             # This will either constantly update the item to ensure you still have it or take it away if you don't deserve it | 
					
						
							| 
									
										
										
										
											2025-08-30 19:59:56 -04:00
										 |  |  |             for addr_to_update in item_data.update_ram_addr: | 
					
						
							|  |  |  |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | 
					
						
							|  |  |  |                 if is_binary: | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | 
					
						
							|  |  |  |                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-09-06 19:22:34 -04:00
										 |  |  |                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | 
					
						
							|  |  |  |                         current_bin_value |= (1 << addr_to_update.binary_bit_pos) | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | 
					
						
							| 
									
										
										
										
											2025-08-25 18:27:19 -04:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2025-09-06 19:22:34 -04:00
										 |  |  |                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                         ram_addr_dict[addr_to_update.ram_address] = [addr_to_update.value, addr_to_update.bit_size] | 
					
						
							| 
									
										
										
										
											2025-09-06 19:22:34 -04:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |                         ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size] | 
					
						
							| 
									
										
										
										
											2025-09-09 22:06:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 23:10:19 -04:00
										 |  |  |         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) | 
					
						
							| 
									
										
										
										
											2025-08-16 02:24:19 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |     async def ingame_checker(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  |         ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |             0x010000, 1, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-10-02 20:12:21 -04:00
										 |  |  |         initial_cutscene_checker = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							| 
									
										
										
										
											2025-10-02 16:27:45 -04:00
										 |  |  |             0x010094, 1, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |         #If not in game or at a menu, or loading the publisher logos | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  |         if ingame_map_id <= 0x04 or ingame_map_id >= 0x35: | 
					
						
							| 
									
										
										
										
											2025-10-02 20:47:32 -04:00
										 |  |  |             self.ingame_log = False | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |             return False | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |         #If grinch has changed maps | 
					
						
							|  |  |  |         if not ingame_map_id == self.last_map_location: | 
					
						
							|  |  |  |             # If the last "map" we were on was a menu or a publisher logo | 
					
						
							|  |  |  |             if self.last_map_location in MENU_MAP_IDS: | 
					
						
							|  |  |  |                 # Reset our demo mode checker just in case the game is in demo mode. | 
					
						
							|  |  |  |                 self.demo_mode_buffer = 0 | 
					
						
							|  |  |  |                 self.ingame_log = False | 
					
						
							| 
									
										
										
										
											2025-10-02 16:27:45 -04:00
										 |  |  |             if initial_cutscene_checker != 1: | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |                 return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-31 13:45:51 -04:00
										 |  |  |             # Update the previous map we were on to be the current map. | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |             self.last_map_location = ingame_map_id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Use this as a delayed check to make sure we are in game | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  |         if not self.demo_mode_buffer == MAX_DEMO_MODE_CHECK: | 
					
						
							|  |  |  |             await asyncio.sleep(0.1) | 
					
						
							|  |  |  |             self.demo_mode_buffer += 1 | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |         demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | 
					
						
							|  |  |  |             0x01008A, 1, "MainRAM")]))[0], "little") | 
					
						
							|  |  |  |         if demo_mode == 1: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  |         if not self.ingame_log: | 
					
						
							| 
									
										
										
										
											2025-08-30 16:33:21 -04:00
										 |  |  |             logger.info("You can now start sending locations from the Grinch!") | 
					
						
							| 
									
										
										
										
											2025-08-26 21:28:02 -04:00
										 |  |  |             self.ingame_log = True | 
					
						
							| 
									
										
										
										
											2025-08-14 00:23:40 -04:00
										 |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async def option_handler(self, ctx: "BizHawkClientContext"): | 
					
						
							|  |  |  |         if self.loc_unlimited_eggs: | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  |             await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2,"little"), "MainRAM")]) | 
					
						
							| 
									
										
										
										
											2025-08-17 21:22:12 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  |     async def ring_link_output(self, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-09-09 01:29:32 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |         while self.send_ring_link and ctx.slot: | 
					
						
							| 
									
										
										
										
											2025-09-09 21:01:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 current_egg_count = int.from_bytes( | 
					
						
							|  |  |  |                     (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (current_egg_count - self.previous_egg_count) != 0: | 
					
						
							|  |  |  |                     msg = { | 
					
						
							|  |  |  |                         "cmd": "Bounce", | 
					
						
							|  |  |  |                         "data": { | 
					
						
							|  |  |  |                             "time": time.time(), | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |                             "source": self.unique_client_id, | 
					
						
							| 
									
										
										
										
											2025-09-09 21:01:56 -04:00
										 |  |  |                             "amount": current_egg_count - self.previous_egg_count | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         "tags": ["RingLink"] | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     await ctx.send_msgs([msg]) | 
					
						
							|  |  |  |                     self.previous_egg_count = current_egg_count | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |                     # logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.") | 
					
						
							|  |  |  |                 await asyncio.sleep(0.1) | 
					
						
							| 
									
										
										
										
											2025-09-09 21:01:56 -04:00
										 |  |  |             except Exception as ex: | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |                 logger.error("While monitoring grinch's egg count ingame, an error occurred. Details:"+ str(ex)) | 
					
						
							|  |  |  |                 self.send_ring_link = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not ctx.slot: | 
					
						
							|  |  |  |             logger.info("You must be connected to the multi-world in order for RingLink to work properly.") | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"): | 
					
						
							| 
									
										
										
										
											2025-09-09 21:01:56 -04:00
										 |  |  |         from CommonClient import logger | 
					
						
							| 
									
										
										
										
											2025-09-12 21:27:11 -04:00
										 |  |  |         game_egg_count = int.from_bytes( | 
					
						
							| 
									
										
										
										
											2025-09-09 00:04:09 -04:00
										 |  |  |             (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little") | 
					
						
							| 
									
										
										
										
											2025-09-12 21:27:11 -04:00
										 |  |  |         non_neg_eggs = game_egg_count + egg_amount if game_egg_count + egg_amount > 0 else 0 | 
					
						
							|  |  |  |         current_egg_count = min(non_neg_eggs, MAX_EGGS) | 
					
						
							| 
									
										
										
										
											2025-09-09 21:01:56 -04:00
										 |  |  |         await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, | 
					
						
							|  |  |  |             int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"), "MainRAM")]) | 
					
						
							| 
									
										
										
										
											2025-09-11 00:11:25 -04:00
										 |  |  |         self.previous_egg_count = current_egg_count | 
					
						
							| 
									
										
										
										
											2025-09-13 00:58:12 -04:00
										 |  |  |         # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_uuid(self) -> int: | 
					
						
							|  |  |  |         string_id = str(uuid.uuid4()) | 
					
						
							|  |  |  |         uid: int = 0 | 
					
						
							|  |  |  |         for char in string_id: | 
					
						
							|  |  |  |             uid += ord(char) | 
					
						
							| 
									
										
										
										
											2025-09-22 21:38:06 -04:00
										 |  |  |         return uid | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _cmd_ringlink(self): | 
					
						
							|  |  |  |     """Toggle ringling from client. Overrides default setting.""" | 
					
						
							|  |  |  |     if not self.ctx.slot: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     Utils.async_start(_update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), name="Update RingLink") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async def _update_ring_link(ctx: "BizHawkClientContext", ring_link: bool): | 
					
						
							|  |  |  |     """Helper function to set Ring Link connection tag on/off and update the connection if already connected.""" | 
					
						
							|  |  |  |     old_tags = copy.deepcopy(ctx.tags) | 
					
						
							|  |  |  |     if ring_link: | 
					
						
							|  |  |  |         ctx.tags.add("RingLink") | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         ctx.tags -= {"RingLink"} | 
					
						
							|  |  |  |     if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed: | 
					
						
							|  |  |  |         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) |