Compare commits
	
		
			18 Commits
		
	
	
		
			v1.2.0
			...
			artamis-mo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0a8df25f6 | |||
|   | 6f552949fa | ||
|   | ca1eb82ce1 | ||
|   | 414a155323 | ||
|   | b9d8d9174f | ||
|   | 8e58f9662e | ||
|   | 6f4597398f | ||
|   | 5e71874446 | ||
|   | 1870dd24ba | ||
|   | f70b6c4c9c | ||
|   | 79d4d5b10b | ||
|   | 7fea34adc3 | ||
|   | a3f9e6cbc9 | ||
|   | bccc83f864 | ||
|   | 6409721841 | ||
|   | d3a7b014bd | ||
|   | 3ec8631203 | ||
|   | 2081912a39 | 
| @@ -6,7 +6,13 @@ import copy | |||||||
| import uuid | import uuid | ||||||
| import Utils | import Utils | ||||||
| from .Locations import grinch_locations, GrinchLocation | from .Locations import grinch_locations, GrinchLocation | ||||||
| from .Items import ALL_ITEMS_TABLE, MISSION_ITEMS_TABLE, GADGETS_TABLE, KEYS_TABLE, GrinchItemData #, SLEIGH_PARTS_TABLE | from .Items import ( | ||||||
|  |     ALL_ITEMS_TABLE, | ||||||
|  |     MISSION_ITEMS_TABLE, | ||||||
|  |     GADGETS_TABLE, | ||||||
|  |     KEYS_TABLE, | ||||||
|  |     GrinchItemData, | ||||||
|  | )  # , SLEIGH_PARTS_TABLE | ||||||
| import worlds._bizhawk as bizhawk | import worlds._bizhawk as bizhawk | ||||||
| from worlds._bizhawk.client import BizHawkClient | from worlds._bizhawk.client import BizHawkClient | ||||||
|  |  | ||||||
| @@ -30,6 +36,7 @@ MAX_EGGS: int = 200 | |||||||
| EGG_COUNT_ADDR: int = 0x010058 | EGG_COUNT_ADDR: int = 0x010058 | ||||||
| EGG_ADDR_BYTESIZE: int = 2 | EGG_ADDR_BYTESIZE: int = 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrinchClient(BizHawkClient): | class GrinchClient(BizHawkClient): | ||||||
|     game = "The Grinch" |     game = "The Grinch" | ||||||
|     system = "PSX" |     system = "PSX" | ||||||
| @@ -41,6 +48,7 @@ class GrinchClient(BizHawkClient): | |||||||
|     previous_egg_count: int = 0 |     previous_egg_count: int = 0 | ||||||
|     send_ring_link: bool = False |     send_ring_link: bool = False | ||||||
|     unique_client_id: int = 0 |     unique_client_id: int = 0 | ||||||
|  |     ring_link_enabled = False | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__() |         super().__init__() | ||||||
| @@ -51,27 +59,44 @@ class GrinchClient(BizHawkClient): | |||||||
|  |  | ||||||
|     async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: |     async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         # TODO Check the ROM data to see if it matches against bytes expected |         # TODO Check the ROM data to see if it matches against bytes expected | ||||||
|         grinch_identifier_ram_address: int = 0x00928C |         grinch_identifier_ram_address: int = 0x00928C | ||||||
|         bios_identifier_ram_address: int = 0x097F30 |         bios_identifier_ram_address: int = 0x097F30 | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             bytes_actual: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( |             bytes_actual: bytes = ( | ||||||
|                 grinch_identifier_ram_address, 11, "MainRAM")]))[0] |                 await bizhawk.read( | ||||||
|  |                     ctx.bizhawk_ctx, [(grinch_identifier_ram_address, 11, "MainRAM")] | ||||||
|  |                 ) | ||||||
|  |             )[0] | ||||||
|  |  | ||||||
|             psx_rom_name = bytes_actual.decode("ascii") |             psx_rom_name = bytes_actual.decode("ascii") | ||||||
|             if psx_rom_name != "SLUS_011.97": |             if psx_rom_name != "SLUS_011.97": | ||||||
|                 bios_bytes_check: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( |                 bios_bytes_check: bytes = ( | ||||||
|                     bios_identifier_ram_address, 24, "MainRAM")]))[0] |                     await bizhawk.read( | ||||||
|  |                         ctx.bizhawk_ctx, [(bios_identifier_ram_address, 24, "MainRAM")] | ||||||
|  |                     ) | ||||||
|  |                 )[0] | ||||||
|  |  | ||||||
|                 if "System ROM Version" in bios_bytes_check.decode("ascii"): |                 if "System ROM Version" in bios_bytes_check.decode("ascii"): | ||||||
|                     if not self.loading_bios_msg: |                     if not self.loading_bios_msg: | ||||||
|                         self.loading_bios_msg = True |                         self.loading_bios_msg = True | ||||||
|                         logger.error("BIOS is currently loading. Will wait up to 5 seconds before retrying.") |                         logger.error( | ||||||
|  |                             "BIOS is currently loading. Will wait up to 5 seconds before retrying." | ||||||
|  |                         ) | ||||||
|  |  | ||||||
|                     return False |                     return False | ||||||
|  |  | ||||||
|                 logger.error("Invalid rom detected. You are not playing Grinch USA Version.") |                 logger.error( | ||||||
|                 raise Exception("Invalid rom detected. You are not playing Grinch USA Version.") |                     "Invalid rom detected. You are not playing Grinch USA Version." | ||||||
|  |                 ) | ||||||
|  |                 raise Exception( | ||||||
|  |                     "Invalid rom detected. You are not playing Grinch USA Version." | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|             ctx.command_processor.commands["ringlink"] = _cmd_ringlink |             ctx.command_processor.commands["ringlink"] = _cmd_ringlink | ||||||
|  |  | ||||||
|         except Exception: |         except Exception: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
| @@ -85,40 +110,54 @@ class GrinchClient(BizHawkClient): | |||||||
|  |  | ||||||
|     def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: |     def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         super().on_package(ctx, cmd, args) |         super().on_package(ctx, cmd, args) | ||||||
|  |  | ||||||
|         match cmd: |         match cmd: | ||||||
|             case "Connected":  # On Connect |             case "Connected":  # On Connect | ||||||
|                 self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"]) |                 self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"]) | ||||||
|                 self.unique_client_id = self._get_uuid() |                 self.unique_client_id = self._get_uuid() | ||||||
|                 logger.info("You are now connected to the client. "+ |                 logger.info( | ||||||
|                     "There may be a slight delay to check you are not in demo mode before locations start to send.") |                     "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." | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|                 ring_link_enabled = bool(ctx.slot_data["ring_link"]) |                 self.ring_link_enabled = bool(ctx.slot_data["ring_link"]) | ||||||
|  |  | ||||||
|                 tags = copy.deepcopy(ctx.tags) |                 tags = copy.deepcopy(ctx.tags) | ||||||
|                 if ring_link_enabled: |  | ||||||
|                     self.send_ring_link = True |                 if self.ring_link_enabled: | ||||||
|                     Utils.async_start(self.ring_link_output(ctx), name="EggLink") |  | ||||||
|                     ctx.tags.add("RingLink") |                     ctx.tags.add("RingLink") | ||||||
|  |  | ||||||
|                 else: |                 else: | ||||||
|                     ctx.tags -= { "RingLink" } |                     ctx.tags -= {"RingLink"} | ||||||
|  |  | ||||||
|                 if tags != ctx.tags: |                 if tags != ctx.tags: | ||||||
|                     Utils.async_start(ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]), "Update RingLink Tags") |                     Utils.async_start( | ||||||
|  |                         ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]), | ||||||
|  |                         "Update RingLink Tags", | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|             case "Bounced": |             case "Bounced": | ||||||
|                 if "tags" not in args: |                 if "tags" not in args: | ||||||
|                     return |                     return | ||||||
|  |  | ||||||
|                 if "RingLink" in ctx.tags and "RingLink" in args["tags"] and args["data"]["source"] != self.unique_client_id: |                 if ( | ||||||
|                     Utils.async_start(self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs") |                     "RingLink" in ctx.tags | ||||||
|  |                     and "RingLink" in args["tags"] | ||||||
|  |                     and args["data"]["source"] != self.unique_client_id | ||||||
|  |                 ): | ||||||
|  |                     Utils.async_start( | ||||||
|  |                         self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs" | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|     async def set_auth(self, ctx: "BizHawkClientContext") -> None: |     async def set_auth(self, ctx: "BizHawkClientContext") -> None: | ||||||
|         await ctx.get_username() |         await ctx.get_username() | ||||||
|  |  | ||||||
|     async def game_watcher(self, ctx: "BizHawkClientContext") -> None: |     async def game_watcher(self, ctx: "BizHawkClientContext") -> None: | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|         #If the player is not connected to an AP Server, or their connection was disconnected. |  | ||||||
|  |         # If the player is not connected to an AP Server, or their connection was disconnected. | ||||||
|         if not ctx.slot: |         if not ctx.slot: | ||||||
|             return |             return | ||||||
|  |  | ||||||
| @@ -126,6 +165,13 @@ class GrinchClient(BizHawkClient): | |||||||
|             if not await self.ingame_checker(ctx): |             if not await self.ingame_checker(ctx): | ||||||
|                 return |                 return | ||||||
|  |  | ||||||
|  |             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") | ||||||
|  |  | ||||||
|             await self.location_checker(ctx) |             await self.location_checker(ctx) | ||||||
|             await self.receiving_items_handler(ctx) |             await self.receiving_items_handler(ctx) | ||||||
|             await self.goal_checker(ctx) |             await self.goal_checker(ctx) | ||||||
| @@ -134,16 +180,24 @@ class GrinchClient(BizHawkClient): | |||||||
|  |  | ||||||
|         except bizhawk.RequestFailedError as ex: |         except bizhawk.RequestFailedError as ex: | ||||||
|             # The connector didn't respond. Exit handler and return to main loop to reconnect |             # The connector didn't respond. Exit handler and return to main loop to reconnect | ||||||
|             logger.error("Failure to connect / authenticate the grinch. Error details: " + str(ex)) |             logger.error( | ||||||
|  |                 "Failure to connect / authenticate the grinch. Error details: " | ||||||
|  |                 + str(ex) | ||||||
|  |             ) | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|         except Exception as genericEx: |         except Exception as genericEx: | ||||||
|             # For all other errors, catch this and let the client gracefully disconnect |             # For all other errors, catch this and let the client gracefully disconnect | ||||||
|             logger.error("Unknown error occurred while playing the grinch. Error details: " + str(genericEx)) |             logger.error( | ||||||
|  |                 "Unknown error occurred while playing the grinch. Error details: " | ||||||
|  |                 + str(genericEx) | ||||||
|  |             ) | ||||||
|             await ctx.disconnect(False) |             await ctx.disconnect(False) | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|     async def location_checker(self, ctx: "BizHawkClientContext"): |     async def location_checker(self, ctx: "BizHawkClientContext"): | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         # Update the AP Server to know what locations are not checked yet. |         # Update the AP Server to know what locations are not checked yet. | ||||||
|         local_locations_checked: list[int] = [] |         local_locations_checked: list[int] = [] | ||||||
|         addr_list_to_read: list[tuple[int, int, str]] = [] |         addr_list_to_read: list[tuple[int, int, str]] = [] | ||||||
| @@ -153,11 +207,15 @@ class GrinchClient(BizHawkClient): | |||||||
|         for missing_location in local_ap_locations: |         for missing_location in local_ap_locations: | ||||||
|             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) |             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | ||||||
|             grinch_loc_ram_data = grinch_locations[grinch_loc_name] |             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 |             missing_addr_list: list[tuple[int, int, str]] = [ | ||||||
|                                                              read_addr in grinch_loc_ram_data.update_ram_addr] |                 (read_addr.ram_address, read_addr.byte_size, "MainRAM") | ||||||
|  |                 for read_addr in grinch_loc_ram_data.update_ram_addr | ||||||
|  |             ] | ||||||
|             addr_list_to_read = [*addr_list_to_read, *missing_addr_list] |             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) |         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, |         # 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. |         # and check to see if that ram address has our expected value. | ||||||
| @@ -170,22 +228,41 @@ class GrinchClient(BizHawkClient): | |||||||
|             # Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location |             # Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location | ||||||
|             # 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. |             # 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] = [] |             ram_checked_list: list[bool] = [] | ||||||
|  |  | ||||||
|             for addr_to_update in grinch_loc_ram_data.update_ram_addr: |             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 |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||||
|                 orig_index: int = addr_list_to_read.index((addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")) |                 orig_index: int = addr_list_to_read.index( | ||||||
|                 value_read_from_bizhawk: int = int.from_bytes(returned_bytes[orig_index], "little") |                     (addr_to_update.ram_address, addr_to_update.byte_size, "MainRAM") | ||||||
|  |                 ) | ||||||
|  |                 value_read_from_bizhawk: int = int.from_bytes( | ||||||
|  |                     returned_bytes[orig_index], "little" | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|                 if is_binary: |                 if is_binary: | ||||||
|                     ram_checked_list.append((value_read_from_bizhawk & (1 << addr_to_update.binary_bit_pos)) > 0) |                     ram_checked_list.append( | ||||||
|  |                         (value_read_from_bizhawk & (1 << addr_to_update.binary_bit_pos)) | ||||||
|  |                         > 0 | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|                 else: |                 else: | ||||||
|                     expected_int_value = addr_to_update.value |                     expected_int_value = addr_to_update.value | ||||||
|                     ram_checked_list.append(expected_int_value == value_read_from_bizhawk) |                     ram_checked_list.append( | ||||||
|  |                         expected_int_value == value_read_from_bizhawk | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|             if all(ram_checked_list): |             if all(ram_checked_list): | ||||||
|                 local_locations_checked.append(GrinchLocation.get_apid(grinch_loc_ram_data.id)) |                 local_locations_checked.append( | ||||||
|  |                     GrinchLocation.get_apid(grinch_loc_ram_data.id) | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|         # Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch) |         # Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch) | ||||||
|         locations_sent_to_ap: set[int] = await ctx.check_locations(local_locations_checked) |         locations_sent_to_ap: set[int] = await ctx.check_locations( | ||||||
|  |             local_locations_checked | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         if len(locations_sent_to_ap) > 0: |         if len(locations_sent_to_ap) > 0: | ||||||
|             await self.remove_physical_items(ctx) |             await self.remove_physical_items(ctx) | ||||||
|  |  | ||||||
|         ctx.locations_checked = set(local_locations_checked) |         ctx.locations_checked = set(local_locations_checked) | ||||||
|  |  | ||||||
|     async def receiving_items_handler(self, ctx: "BizHawkClientContext"): |     async def receiving_items_handler(self, ctx: "BizHawkClientContext"): | ||||||
| @@ -193,12 +270,20 @@ class GrinchClient(BizHawkClient): | |||||||
|         # If the list says that we have 3 items and we already received items, we will ignore and continue. |         # 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. |         # Otherwise, we will get the new items and give them to the player. | ||||||
|  |  | ||||||
|         self.last_received_index = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |         self.last_received_index = int.from_bytes( | ||||||
|             RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]))[0], "little") |             ( | ||||||
|  |                 await bizhawk.read( | ||||||
|  |                     ctx.bizhawk_ctx, [(RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")] | ||||||
|  |                 ) | ||||||
|  |             )[0], | ||||||
|  |             "little", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         if len(ctx.items_received) == self.last_received_index: |         if len(ctx.items_received) == self.last_received_index: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         # Ensures we only get the new items that we want to give the player |         # Ensures we only get the new items that we want to give the player | ||||||
|         new_items_only = ctx.items_received[self.last_received_index:] |         new_items_only = ctx.items_received[self.last_received_index :] | ||||||
|         ram_addr_dict: dict[int, list[int]] = {} |         ram_addr_dict: dict[int, list[int]] = {} | ||||||
|  |  | ||||||
|         for item_received in new_items_only: |         for item_received in new_items_only: | ||||||
| @@ -207,83 +292,164 @@ class GrinchClient(BizHawkClient): | |||||||
|  |  | ||||||
|             for addr_to_update in grinch_item_ram_data.update_ram_addr: |             for addr_to_update in grinch_item_ram_data.update_ram_addr: | ||||||
|                 is_binary = True if not addr_to_update.binary_bit_pos is None else False |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||||
|  |  | ||||||
|                 if addr_to_update.ram_address in ram_addr_dict.keys(): |                 if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|                     current_ram_address_value = ram_addr_dict[addr_to_update.ram_address][0] |                     current_ram_address_value = ram_addr_dict[ | ||||||
|  |                         addr_to_update.ram_address | ||||||
|  |                     ][0] | ||||||
|                 else: |                 else: | ||||||
|                     current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                     current_ram_address_value = int.from_bytes( | ||||||
|                         addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") |                         ( | ||||||
|  |                             await bizhawk.read( | ||||||
|  |                                 ctx.bizhawk_ctx, | ||||||
|  |                                 [ | ||||||
|  |                                     ( | ||||||
|  |                                         addr_to_update.ram_address, | ||||||
|  |                                         addr_to_update.byte_size, | ||||||
|  |                                         "MainRAM", | ||||||
|  |                                     ) | ||||||
|  |                                 ], | ||||||
|  |                             ) | ||||||
|  |                         )[0], | ||||||
|  |                         "little", | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|                 if is_binary: |                 if is_binary: | ||||||
|                     current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos)) |                     current_ram_address_value = current_ram_address_value | ( | ||||||
|  |                         1 << addr_to_update.binary_bit_pos | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|                 elif addr_to_update.update_existing_value: |                 elif addr_to_update.update_existing_value: | ||||||
|                     # Grabs minimum value of a list of numbers and makes sure it does not go above max count possible |                     # Grabs minimum value of a list of numbers and makes sure it does not go above max count possible | ||||||
|                     current_ram_address_value += addr_to_update.value |                     current_ram_address_value += addr_to_update.value | ||||||
|                     current_ram_address_value = min(current_ram_address_value, addr_to_update.max_count) |                     current_ram_address_value = min( | ||||||
|  |                         current_ram_address_value, addr_to_update.max_count | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|                 else: |                 else: | ||||||
|                     current_ram_address_value = addr_to_update.value |                     current_ram_address_value = addr_to_update.value | ||||||
|  |  | ||||||
|                 # Write the updated value back into RAM |                 # Write the updated value back into RAM | ||||||
|                 ram_addr_dict[addr_to_update.ram_address] = [current_ram_address_value, addr_to_update.bit_size] |                 ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|  |                     current_ram_address_value, | ||||||
|  |                     addr_to_update.byte_size, | ||||||
|  |                 ] | ||||||
|  |  | ||||||
|             self.last_received_index += 1 |             self.last_received_index += 1 | ||||||
|  |  | ||||||
|         # Update the latest received item index to ram as well. |         # Update the latest received item index to ram as well. | ||||||
|         ram_addr_dict[RECV_ITEM_ADDR] = [self.last_received_index, RECV_ITEM_BITSIZE] |         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)) |  | ||||||
|  |         await bizhawk.write( | ||||||
|  |             ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     async def goal_checker(self, ctx: "BizHawkClientContext"): |     async def goal_checker(self, ctx: "BizHawkClientContext"): | ||||||
|         if not ctx.finished_game: |         if not ctx.finished_game: | ||||||
|             goal_loc = grinch_locations["MC - Sleigh Ride - Neutralizing Santa"] |             goal_loc = grinch_locations["MC - Sleigh Ride - Neutralizing Santa"] | ||||||
|             goal_ram_address = goal_loc.update_ram_addr[0] |             goal_ram_address = goal_loc.update_ram_addr[0] | ||||||
|             current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |             current_ram_address_value = int.from_bytes( | ||||||
|                 goal_ram_address.ram_address, goal_ram_address.bit_size, "MainRAM")]))[0], "little") |                 ( | ||||||
|             # if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0: |                     await bizhawk.read( | ||||||
|             if current_ram_address_value == goal_ram_address.value: |                         ctx.bizhawk_ctx, | ||||||
|  |                         [ | ||||||
|  |                             ( | ||||||
|  |                                 goal_ram_address.ram_address, | ||||||
|  |                                 goal_ram_address.byte_size, | ||||||
|  |                                 "MainRAM", | ||||||
|  |                             ) | ||||||
|  |                         ], | ||||||
|  |                     ) | ||||||
|  |                 )[0], | ||||||
|  |                 "little", | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |             if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0: | ||||||
|  |                 # if current_ram_address_value == goal_ram_address.value: | ||||||
|                 ctx.finished_game = True |                 ctx.finished_game = True | ||||||
|                 await ctx.send_msgs([{ |                 await ctx.send_msgs( | ||||||
|                     "cmd": "StatusUpdate", |                     [ | ||||||
|                     "status": NetUtils.ClientStatus.CLIENT_GOAL, |                         { | ||||||
|                 }]) |                             "cmd": "StatusUpdate", | ||||||
|  |                             "status": NetUtils.ClientStatus.CLIENT_GOAL, | ||||||
|  |                         } | ||||||
|  |                     ] | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|     # This function's entire purpose is to take away items we physically received ingame, but have not received from AP |     # This function's entire purpose is to take away items we physically received ingame, but have not received from AP | ||||||
|     async def remove_physical_items(self, ctx: "BizHawkClientContext"): |     async def remove_physical_items(self, ctx: "BizHawkClientContext"): | ||||||
|         ram_addr_dict: dict[int, list[int]] = {} |         ram_addr_dict: dict[int, list[int]] = {} | ||||||
|  |  | ||||||
|         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] |         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | ||||||
|         items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE |         items_to_check: dict[str, GrinchItemData] = { | ||||||
|         heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570)) |             **GADGETS_TABLE | ||||||
|  |         }  # , **SLEIGH_PARTS_TABLE | ||||||
|  |         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"] |         heart_item_data = ALL_ITEMS_TABLE["Heart of Stone"] | ||||||
|         ram_addr_dict[heart_item_data.update_ram_addr[0].ram_address] = [min(heart_count, 4), 1] |         ram_addr_dict[heart_item_data.update_ram_addr[0].ram_address] = [ | ||||||
|  |             min(heart_count, 4), | ||||||
|  |             1, | ||||||
|  |         ] | ||||||
|  |  | ||||||
|         # Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions |         # Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions | ||||||
|         ram_addr_dict[0x0100F0] = [0, 4] |         ram_addr_dict[0x0100F0] = [0, 4] | ||||||
|  |  | ||||||
|         for (item_name, item_data) in items_to_check.items(): |         for item_name, item_data in items_to_check.items(): | ||||||
|             # If item is an event or already been received, ignore. |             # 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: |             if ( | ||||||
|  |                 item_data.id is None | ||||||
|  |                 or GrinchLocation.get_apid(item_data.id) in list_recv_itemids | ||||||
|  |             ): | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # This assumes we don't have the item so we must set all the data to 0 |             # 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: |             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 |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||||
|  |  | ||||||
|                 if is_binary: |                 if is_binary: | ||||||
|                     if addr_to_update.ram_address in ram_addr_dict.keys(): |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] |                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | ||||||
|  |  | ||||||
|                     else: |                     else: | ||||||
|                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                         current_bin_value = int.from_bytes( | ||||||
|                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") |                             ( | ||||||
|  |                                 await bizhawk.read( | ||||||
|  |                                     ctx.bizhawk_ctx, | ||||||
|  |                                     [ | ||||||
|  |                                         ( | ||||||
|  |                                             addr_to_update.ram_address, | ||||||
|  |                                             addr_to_update.byte_size, | ||||||
|  |                                             "MainRAM", | ||||||
|  |                                         ) | ||||||
|  |                                     ], | ||||||
|  |                                 ) | ||||||
|  |                             )[0], | ||||||
|  |                             "little", | ||||||
|  |                         ) | ||||||
|                     current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) |                     current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) | ||||||
|                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] |                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | ||||||
|  |  | ||||||
|                 else: |                 else: | ||||||
|                     ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size] |                     ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|  |                         0, | ||||||
|  |                         addr_to_update.byte_size, | ||||||
|  |                     ] | ||||||
|  |  | ||||||
|         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) |         await bizhawk.write( | ||||||
|  |             ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def convert_dict_to_ram_list( | ||||||
|     def convert_dict_to_ram_list(self, addr_dict: dict[int, list[int]]) -> list[tuple[int, Sequence[int], str]]: |         self, addr_dict: dict[int, list[int]] | ||||||
|  |     ) -> list[tuple[int, Sequence[int], str]]: | ||||||
|         addr_list_to_update: list[tuple[int, Sequence[int], str]] = [] |         addr_list_to_update: list[tuple[int, Sequence[int], str]] = [] | ||||||
|  |  | ||||||
|         for (key, val) in addr_dict.items(): |         for key, val in addr_dict.items(): | ||||||
|             addr_list_to_update.append((key, val[0].to_bytes(val[1], "little"), "MainRAM")) |             addr_list_to_update.append( | ||||||
|  |                 (key, val[0].to_bytes(val[1], "little"), "MainRAM") | ||||||
|  |             ) | ||||||
|  |  | ||||||
|         return addr_list_to_update |         return addr_list_to_update | ||||||
|  |  | ||||||
| @@ -292,56 +458,93 @@ class GrinchClient(BizHawkClient): | |||||||
|         ram_addr_dict: dict[int, list[int]] = {} |         ram_addr_dict: dict[int, list[int]] = {} | ||||||
|  |  | ||||||
|         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] |         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | ||||||
|         items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE} |         items_to_check: dict[str, GrinchItemData] = { | ||||||
|  |             **KEYS_TABLE, | ||||||
|  |             **MISSION_ITEMS_TABLE, | ||||||
|  |         } | ||||||
|  |  | ||||||
|         for (item_name, item_data) in items_to_check.items(): |         for item_name, item_data in items_to_check.items(): | ||||||
|             # If item is an event or already been received, ignore. |             # 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: |             if ( | ||||||
|  |                 item_data.id is None | ||||||
|  |             ):  # or GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # This will either constantly update the item to ensure you still have it or take it away if you don't deserve it |             # This will either constantly update the item to ensure you still have it or take it away if you don't deserve it | ||||||
|             for addr_to_update in item_data.update_ram_addr: |             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 |                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||||
|  |  | ||||||
|                 if is_binary: |                 if is_binary: | ||||||
|                     if addr_to_update.ram_address in ram_addr_dict.keys(): |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] |                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | ||||||
|  |  | ||||||
|                     else: |                     else: | ||||||
|                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                         current_bin_value = int.from_bytes( | ||||||
|                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") |                             ( | ||||||
|  |                                 await bizhawk.read( | ||||||
|  |                                     ctx.bizhawk_ctx, | ||||||
|  |                                     [ | ||||||
|  |                                         ( | ||||||
|  |                                             addr_to_update.ram_address, | ||||||
|  |                                             addr_to_update.byte_size, | ||||||
|  |                                             "MainRAM", | ||||||
|  |                                         ) | ||||||
|  |                                     ], | ||||||
|  |                                 ) | ||||||
|  |                             )[0], | ||||||
|  |                             "little", | ||||||
|  |                         ) | ||||||
|  |  | ||||||
|                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: |                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | ||||||
|                         current_bin_value |= (1 << addr_to_update.binary_bit_pos) |                         current_bin_value |= 1 << addr_to_update.binary_bit_pos | ||||||
|  |  | ||||||
|                     else: |                     else: | ||||||
|                         current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) |                         current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) | ||||||
|  |  | ||||||
|                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] |                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | ||||||
|  |  | ||||||
|                 else: |                 else: | ||||||
|                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: |                     if GrinchLocation.get_apid(item_data.id) in list_recv_itemids: | ||||||
|                         ram_addr_dict[addr_to_update.ram_address] = [addr_to_update.value, addr_to_update.bit_size] |                         ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|                     else: |                             addr_to_update.value, | ||||||
|                         ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size] |                             addr_to_update.byte_size, | ||||||
|  |                         ] | ||||||
|  |  | ||||||
|         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) |                     else: | ||||||
|  |                         ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|  |                             0, | ||||||
|  |                             addr_to_update.byte_size, | ||||||
|  |                         ] | ||||||
|  |  | ||||||
|  |         await bizhawk.write( | ||||||
|  |             ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     async def ingame_checker(self, ctx: "BizHawkClientContext"): |     async def ingame_checker(self, ctx: "BizHawkClientContext"): | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |         ingame_map_id = int.from_bytes( | ||||||
|             0x010000, 1, "MainRAM")]))[0], "little") |             (await bizhawk.read(ctx.bizhawk_ctx, [(0x010000, 1, "MainRAM")]))[0], | ||||||
|         initial_cutscene_checker = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |             "little", | ||||||
|             0x010094, 1, "MainRAM")]))[0], "little") |         ) | ||||||
|  |         initial_cutscene_checker = int.from_bytes( | ||||||
|  |             (await bizhawk.read(ctx.bizhawk_ctx, [(0x010094, 1, "MainRAM")]))[0], | ||||||
|  |             "little", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         #If not in game or at a menu, or loading the publisher logos |         # If not in game or at a menu, or loading the publisher logos | ||||||
|         if ingame_map_id <= 0x04 or ingame_map_id >= 0x35: |         if ingame_map_id <= 0x04 or ingame_map_id >= 0x35: | ||||||
|             self.ingame_log = False |             self.ingame_log = False | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         #If grinch has changed maps |         # If grinch has changed maps | ||||||
|         if not ingame_map_id == self.last_map_location: |         if not ingame_map_id == self.last_map_location: | ||||||
|             # If the last "map" we were on was a menu or a publisher logo |             # If the last "map" we were on was a menu or a publisher logo | ||||||
|             if self.last_map_location in MENU_MAP_IDS: |             if self.last_map_location in MENU_MAP_IDS: | ||||||
|                 # Reset our demo mode checker just in case the game is in demo mode. |                 # Reset our demo mode checker just in case the game is in demo mode. | ||||||
|                 self.demo_mode_buffer = 0 |                 self.demo_mode_buffer = 0 | ||||||
|                 self.ingame_log = False |                 self.ingame_log = False | ||||||
|  |  | ||||||
|             if initial_cutscene_checker != 1: |             if initial_cutscene_checker != 1: | ||||||
|                 return False |                 return False | ||||||
|  |  | ||||||
| @@ -354,27 +557,42 @@ class GrinchClient(BizHawkClient): | |||||||
|             self.demo_mode_buffer += 1 |             self.demo_mode_buffer += 1 | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |         demo_mode = int.from_bytes( | ||||||
|             0x01008A, 1, "MainRAM")]))[0], "little") |             (await bizhawk.read(ctx.bizhawk_ctx, [(0x01008A, 1, "MainRAM")]))[0], | ||||||
|  |             "little", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         if demo_mode == 1: |         if demo_mode == 1: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         if not self.ingame_log: |         if not self.ingame_log: | ||||||
|             logger.info("You can now start sending locations from the Grinch!") |             logger.info("You can now start sending locations from the Grinch!") | ||||||
|             self.ingame_log = True |             self.ingame_log = True | ||||||
|  |  | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     async def option_handler(self, ctx: "BizHawkClientContext"): |     async def option_handler(self, ctx: "BizHawkClientContext"): | ||||||
|         if self.loc_unlimited_eggs: |         if self.loc_unlimited_eggs: | ||||||
|             await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2,"little"), "MainRAM")]) |             await bizhawk.write( | ||||||
|  |                 ctx.bizhawk_ctx, | ||||||
|  |                 [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2, "little"), "MainRAM")], | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     async def ring_link_output(self, ctx: "BizHawkClientContext"): |     async def ring_link_output(self, ctx: "BizHawkClientContext"): | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         while self.send_ring_link and ctx.slot: |         while self.send_ring_link and ctx.slot: | ||||||
|  |  | ||||||
|             try: |             try: | ||||||
|                 current_egg_count = int.from_bytes( |                 current_egg_count = int.from_bytes( | ||||||
|                     (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little") |                     ( | ||||||
|  |                         await bizhawk.read( | ||||||
|  |                             ctx.bizhawk_ctx, | ||||||
|  |                             [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")], | ||||||
|  |                         ) | ||||||
|  |                     )[0], | ||||||
|  |                     "little", | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|                 if (current_egg_count - self.previous_egg_count) != 0: |                 if (current_egg_count - self.previous_egg_count) != 0: | ||||||
|                     msg = { |                     msg = { | ||||||
| @@ -382,29 +600,55 @@ class GrinchClient(BizHawkClient): | |||||||
|                         "data": { |                         "data": { | ||||||
|                             "time": time.time(), |                             "time": time.time(), | ||||||
|                             "source": self.unique_client_id, |                             "source": self.unique_client_id, | ||||||
|                             "amount": current_egg_count - self.previous_egg_count |                             "amount": current_egg_count - self.previous_egg_count, | ||||||
|                         }, |                         }, | ||||||
|                         "tags": ["RingLink"] |                         "tags": ["RingLink"], | ||||||
|                     } |                     } | ||||||
|                     await ctx.send_msgs([msg]) |                     await ctx.send_msgs([msg]) | ||||||
|                     self.previous_egg_count = current_egg_count |                     self.previous_egg_count = current_egg_count | ||||||
|                     # logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.") |                     # logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.") | ||||||
|  |  | ||||||
|                 await asyncio.sleep(0.1) |                 await asyncio.sleep(0.1) | ||||||
|  |  | ||||||
|             except Exception as ex: |             except Exception as ex: | ||||||
|                 logger.error("While monitoring grinch's egg count ingame, an error occurred. Details:"+ str(ex)) |                 logger.error( | ||||||
|  |                     "While monitoring grinch's egg count ingame, an error occurred. Details:" | ||||||
|  |                     + str(ex) | ||||||
|  |                 ) | ||||||
|                 self.send_ring_link = False |                 self.send_ring_link = False | ||||||
|  |  | ||||||
|         if not ctx.slot: |         if not ctx.slot: | ||||||
|             logger.info("You must be connected to the multi-world in order for RingLink to work properly.") |             logger.info( | ||||||
|  |                 "You must be connected to the multi-world in order for RingLink to work properly." | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"): |     async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"): | ||||||
|         from CommonClient import logger |         from CommonClient import logger | ||||||
|  |  | ||||||
|         game_egg_count = int.from_bytes( |         game_egg_count = int.from_bytes( | ||||||
|             (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little") |             ( | ||||||
|         non_neg_eggs = game_egg_count + egg_amount if game_egg_count + egg_amount > 0 else 0 |                 await bizhawk.read( | ||||||
|  |                     ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")] | ||||||
|  |                 ) | ||||||
|  |             )[0], | ||||||
|  |             "little", | ||||||
|  |         ) | ||||||
|  |         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) |         current_egg_count = min(non_neg_eggs, MAX_EGGS) | ||||||
|         await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, |  | ||||||
|             int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"), "MainRAM")]) |         await bizhawk.write( | ||||||
|  |             ctx.bizhawk_ctx, | ||||||
|  |             [ | ||||||
|  |                 ( | ||||||
|  |                     EGG_COUNT_ADDR, | ||||||
|  |                     int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"), | ||||||
|  |                     "MainRAM", | ||||||
|  |                 ) | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         self.previous_egg_count = current_egg_count |         self.previous_egg_count = current_egg_count | ||||||
|         # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.") |         # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.") | ||||||
|  |  | ||||||
| @@ -415,18 +659,27 @@ class GrinchClient(BizHawkClient): | |||||||
|             uid += ord(char) |             uid += ord(char) | ||||||
|         return uid |         return uid | ||||||
|  |  | ||||||
|  |  | ||||||
| def _cmd_ringlink(self): | def _cmd_ringlink(self): | ||||||
|     """Toggle ringling from client. Overrides default setting.""" |     """Toggle ringling from client. Overrides default setting.""" | ||||||
|     if not self.ctx.slot: |     if not self.ctx.slot: | ||||||
|         return |         return | ||||||
|     Utils.async_start(_update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), name="Update RingLink") |  | ||||||
|  |     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): | 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.""" |     """Helper function to set Ring Link connection tag on/off and update the connection if already connected.""" | ||||||
|     old_tags = copy.deepcopy(ctx.tags) |     old_tags = copy.deepcopy(ctx.tags) | ||||||
|  |  | ||||||
|     if ring_link: |     if ring_link: | ||||||
|         ctx.tags.add("RingLink") |         ctx.tags.add("RingLink") | ||||||
|  |  | ||||||
|     else: |     else: | ||||||
|         ctx.tags -= {"RingLink"} |         ctx.tags -= {"RingLink"} | ||||||
|  |  | ||||||
|     if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed: |     if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed: | ||||||
|         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) |         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) | ||||||
| @@ -1,32 +1,41 @@ | |||||||
| from typing import NamedTuple, Optional | from typing import NamedTuple, Optional | ||||||
|  |  | ||||||
| from .RamHandler import GrinchRamData | from .RamHandler import GrinchRamData, UpdateMethod | ||||||
| from BaseClasses import Item | from BaseClasses import Item | ||||||
| from BaseClasses import ItemClassification as IC #IC can be any name, saves having to type the whole word in code | from BaseClasses import ( | ||||||
|  |     ItemClassification as IC, | ||||||
|  | )  # IC can be any name, saves having to type the whole word in code | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrinchItemData(NamedTuple): | class GrinchItemData(NamedTuple): | ||||||
|     item_group: list[str] #arbituary that can be whatever it can be, basically the field/property for item groups |     item_group: list[ | ||||||
|  |         str | ||||||
|  |     ]  # arbituary that can be whatever it can be, basically the field/property for item groups | ||||||
|     id: Optional[int] |     id: Optional[int] | ||||||
|     classification: IC |     classification: IC | ||||||
|     update_ram_addr: list[GrinchRamData] |     update_ram_addr: list[GrinchRamData] | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrinchItem(Item): | class GrinchItem(Item): | ||||||
|     game: str = "The Grinch" |     game: str = "The Grinch" | ||||||
|  |  | ||||||
|     #Tells server what item id it is |     # Tells server what item id it is | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_apid(id: int): |     def get_apid(id: int): | ||||||
|         #If you give me an input id, I will return the Grinch equivalent server/ap id |         # If you give me an input id, I will return the Grinch equivalent server/ap id | ||||||
|         base_id: int = 42069 |         base_id: int = 42069 | ||||||
|         return base_id + id if id is not None else None |         return base_id + id if id is not None else None | ||||||
|  |  | ||||||
|     def __init__(self, name: str, player: int, data: GrinchItemData): |     def __init__(self, name: str, player: int, data: GrinchItemData): | ||||||
|         super(GrinchItem, self).__init__(name,data.classification, GrinchItem.get_apid(data.id), player) |         super(GrinchItem, self).__init__( | ||||||
|  |             name, data.classification, GrinchItem.get_apid(data.id), player | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         self.type = data.item_group |         self.type = data.item_group | ||||||
|         self.item_id = data.id |         self.item_id = data.id | ||||||
|  |  | ||||||
| #allows hinting of items via category |  | ||||||
|  | # allows hinting of items via category | ||||||
| def get_item_names_per_category() -> dict[str, set[str]]: | def get_item_names_per_category() -> dict[str, set[str]]: | ||||||
|     categories: dict[str, set[str]] = {} |     categories: dict[str, set[str]] = {} | ||||||
|  |  | ||||||
| @@ -36,123 +45,306 @@ def get_item_names_per_category() -> dict[str, set[str]]: | |||||||
|  |  | ||||||
|     return categories |     return categories | ||||||
|  |  | ||||||
| REL: str = "Rotten Egg Launcher" |  | ||||||
| RS: str = "Rocket Spring" |  | ||||||
| SS: str = "Slime Shooter" |  | ||||||
| OCD: str = "Octopus Climbing Device" |  | ||||||
| MM: str = "Marine Mobile" |  | ||||||
| GC: str = "Grinch Copter" |  | ||||||
| WV: str = "Whoville Vacuum Tube" |  | ||||||
| WF: str = "Who Forest Vacuum Tube" |  | ||||||
| WD: str = "Who Dump Vacuum Tube" |  | ||||||
| WL: str = "Who Lake Vacuum Tube" |  | ||||||
| VT: str = "Progressive Vacuum Tube" |  | ||||||
| PC: str = "Pancake" |  | ||||||
| SR: str = "Sleigh Room Key" |  | ||||||
| BB: str = "Bad Breath" |  | ||||||
| SE: str = "Seize" |  | ||||||
| MX: str = "Max" |  | ||||||
| SN: str = "Sneak" |  | ||||||
| WC: str = "Who Cloak" |  | ||||||
| PB: str = "Painting Bucket" |  | ||||||
| SC: str = "Scissors" |  | ||||||
| GB: str = "Glue Bucket" |  | ||||||
| CCAC: str = "Cable Car Access Card" |  | ||||||
| DRL: str = "Drill" |  | ||||||
| RP: str = "Rope" |  | ||||||
| HK: str = "Hook" |  | ||||||
| ST: str = "Sculpting Tools" |  | ||||||
| HMR: str = "Hammer" |  | ||||||
| SCL: str = "Scout Clothes" |  | ||||||
|  |  | ||||||
| #Gadgets | class grinch_items: | ||||||
| #All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit. |     class gadgets: | ||||||
|  |         BINOCULARS: str = "Binoculars" | ||||||
|  |         ROCKET_EGG_LAUNCHER: str = "Rotten Egg Launcher" | ||||||
|  |         ROCKET_SPRING: str = "Rocket Spring" | ||||||
|  |         SLIME_SHOOTER: str = "Slime Shooter" | ||||||
|  |         OCTOPUS_CLIMBING_DEVICE: str = "Octopus Climbing Device" | ||||||
|  |         MARINE_MOBILE: str = "Marine Mobile" | ||||||
|  |         GRINCH_COPTER: str = "Grinch Copter" | ||||||
|  |  | ||||||
|  |     class keys: | ||||||
|  |         WHOVILLE: str = "Whoville Vacuum Tube" | ||||||
|  |         WHO_FOREST: str = "Who Forest Vacuum Tube" | ||||||
|  |         WHO_DUMP: str = "Who Dump Vacuum Tube" | ||||||
|  |         WHO_LAKE: str = "Who Lake Vacuum Tube" | ||||||
|  |         PROGRESSIVE_VACUUM_TUBE: str = "Progressive Vacuum Tube" | ||||||
|  |         SLEIGH_ROOM_KEY: str = "Sleigh Room Key" | ||||||
|  |  | ||||||
|  |     class moves: | ||||||
|  |         PANCAKE: str = "Pancake" | ||||||
|  |         BAD_BREATH: str = "Bad Breath" | ||||||
|  |         SIEZE: str = "Seize" | ||||||
|  |         MAX: str = "Max" | ||||||
|  |         SNEAK: str = "Sneak" | ||||||
|  |  | ||||||
|  |     class level_items: | ||||||
|  |         WV_WHO_CLOAK: str = "Who Cloak" | ||||||
|  |         WV_PAINT_BUCKET: str = "Painting Bucket" | ||||||
|  |         WV_HAMMER: str = "Hammer" | ||||||
|  |         WV_SCULPTIN_TOOLS: str = "Sculpting Tools" | ||||||
|  |         WF_GLUE_BUCKET: str = "Glue Bucket" | ||||||
|  |         WF_CABLE_CAR_ACCESS_CARD: str = "Cable Car Access Card" | ||||||
|  |         WD_SCISSORS: str = "Scissors" | ||||||
|  |         WL_ROPE: str = "Rope" | ||||||
|  |         WL_HOOK: str = "Hook" | ||||||
|  |         WL_DRILLL: str = "Drill" | ||||||
|  |         WL_SCOUT_CLOTHES: str = "Scout Clothes" | ||||||
|  |  | ||||||
|  |     class useful_items: | ||||||
|  |         HEART_OF_STONE: str = "Heart of Stone" | ||||||
|  |  | ||||||
|  |     class trap_items: | ||||||
|  |         DEPLETION_TRAP: str = "Depletion Trap" | ||||||
|  |         DUMP_IT_TO_CRUMPIT: str = "Dump it to Crumpit" | ||||||
|  |         WHO_SENT_ME_BACK: str = "Who sent me back?" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class grinch_categories: | ||||||
|  |     FILLER: str = "Filler" | ||||||
|  |     GADGETS: str = "Gadgets" | ||||||
|  |     HEALING_ITEMS: str = "Healing Items" | ||||||
|  |     MISSION_SPECIFIC_ITEMS: str = "Mission Specific Items" | ||||||
|  |     MOVES: str = "Moves" | ||||||
|  |     REQUIRED_ITEM: str = "Required Items" | ||||||
|  |     ROTTEN_EGG_BUNDLES: str = "Rotten Egg Bundles" | ||||||
|  |     SLEIGH_ROOM: str = "Sleigh Room" | ||||||
|  |     TRAPS: str = "Traps" | ||||||
|  |     USEFUL_ITEMS: str = "Useful Items" | ||||||
|  |     VACUUM_TUBES: str = "Vacuum Tubes" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Gadgets | ||||||
|  | # All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit. | ||||||
| GADGETS_TABLE: dict[str, GrinchItemData] = { | GADGETS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     "Binoculars": GrinchItemData(["Gadgets"], 100, IC.useful, |     grinch_items.gadgets.BINOCULARS: GrinchItemData( | ||||||
|         [GrinchRamData(0x0102B6, value=0x40), GrinchRamData(0x0102B7, value=0x41), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102B8, value=0x44), GrinchRamData(0x0102B9, value=0x45), |         100, | ||||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=0) |         IC.useful, | ||||||
|          ]), |         [ | ||||||
|     "Rotten Egg Launcher": GrinchItemData(["Gadgets"], 101, IC.progression, |             GrinchRamData(0x0102B6, value=0x40), | ||||||
|         [GrinchRamData(0x0102BA, value=0x40), GrinchRamData(0x0102BB, value=0x41), |             GrinchRamData(0x0102B7, value=0x41), | ||||||
|         GrinchRamData(0x0102BC, value=0x44), GrinchRamData(0x0102BD, value=0x45), |             GrinchRamData(0x0102B8, value=0x44), | ||||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=1) |             GrinchRamData(0x0102B9, value=0x45), | ||||||
|          ]), |             # GrinchRamData(0x0100BC, binary_bit_pos=0) | ||||||
|     "Rocket Spring": GrinchItemData(["Gadgets"], 102, IC.progression, |         ], | ||||||
|         [GrinchRamData(0x0102BE, value=0x40), GrinchRamData(0x0102BF, value=0x41), |     ), | ||||||
|         GrinchRamData(0x0102C0, value=0x42), GrinchRamData(0x0102C1, value=0x44), |     grinch_items.gadgets.ROCKET_EGG_LAUNCHER: GrinchItemData( | ||||||
|         GrinchRamData(0x0102C2, value=0x45), GrinchRamData(0x0102C3, value=0x46), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102C4, value=0x48), GrinchRamData(0x0102C5, value=0x49), |         101, | ||||||
|         GrinchRamData(0x0102C6, value=0x4A), |         IC.progression, | ||||||
|          # GrinchRamData(0x0100BC, binary_bit_pos=2) |         [ | ||||||
|          ]), |             GrinchRamData(0x0102BA, value=0x40), | ||||||
|     "Slime Shooter": GrinchItemData(["Gadgets", "Slime Gun"], 103, IC.progression, |             GrinchRamData(0x0102BB, value=0x41), | ||||||
|         [GrinchRamData(0x0102C7, value=0x40), GrinchRamData(0x0102C8, value=0x41), |             GrinchRamData(0x0102BC, value=0x44), | ||||||
|         GrinchRamData(0x0102C9, value=0x42), GrinchRamData(0x0102CA, value=0x44), |             GrinchRamData(0x0102BD, value=0x45), | ||||||
|         GrinchRamData(0x0102CB, value=0x45), GrinchRamData(0x0102CC, value=0x46), |             # GrinchRamData(0x0100BC, binary_bit_pos=1) | ||||||
|         GrinchRamData(0x0102CD, value=0x48), GrinchRamData(0x0102CE, value=0x49), |         ], | ||||||
|         GrinchRamData(0x0102CF, value=0x4A), |     ), | ||||||
|          # GrinchRamData(0x0100BC, binary_bit_pos=3) |     grinch_items.gadgets.ROCKET_SPRING: GrinchItemData( | ||||||
|          ]), |         [grinch_categories.GADGETS], | ||||||
|     "Octopus Climbing Device": GrinchItemData(["Gadgets"], 104, IC.progression, |         102, | ||||||
|         [GrinchRamData(0x0102D0, value=0x40), GrinchRamData(0x0102D1, value=0x41), |         IC.progression, | ||||||
|         GrinchRamData(0x0102D2, value=0x42), GrinchRamData(0x0102D3, value=0x44), |         [ | ||||||
|         GrinchRamData(0x0102D4, value=0x45), GrinchRamData(0x0102D5, value=0x46), |             GrinchRamData(0x0102BE, value=0x40), | ||||||
|         GrinchRamData(0x0102D6, value=0x48), GrinchRamData(0x0102D7, value=0x49), |             GrinchRamData(0x0102BF, value=0x41), | ||||||
|         GrinchRamData(0x0102D8, value=0x4A), |             GrinchRamData(0x0102C0, value=0x42), | ||||||
|          # GrinchRamData(0x0100BC, binary_bit_pos=4) |             GrinchRamData(0x0102C1, value=0x44), | ||||||
|          ]), |             GrinchRamData(0x0102C2, value=0x45), | ||||||
|     "Marine Mobile": GrinchItemData(["Gadgets"], 105, IC.progression, |             GrinchRamData(0x0102C3, value=0x46), | ||||||
|         [GrinchRamData(0x0102D9, value=0x40), GrinchRamData(0x0102DA, value=0x41), |             GrinchRamData(0x0102C4, value=0x48), | ||||||
|         GrinchRamData(0x0102DB, value=0x42), GrinchRamData(0x0102DC, value=0x43), |             GrinchRamData(0x0102C5, value=0x49), | ||||||
|         GrinchRamData(0x0102DD, value=0x44), GrinchRamData(0x0102DE, value=0x45), |             GrinchRamData(0x0102C6, value=0x4A), | ||||||
|         GrinchRamData(0x0102DF, value=0x46), GrinchRamData(0x0102E0, value=0x47), |             # GrinchRamData(0x0100BC, binary_bit_pos=2) | ||||||
|         GrinchRamData(0x0102E1, value=0x48), GrinchRamData(0x0102E2, value=0x49), |         ], | ||||||
|         GrinchRamData(0x0102E3, value=0x4A), GrinchRamData(0x0102E4, value=0x4B), |     ), | ||||||
|         GrinchRamData(0x0102E5, value=0x4C), GrinchRamData(0x0102E6, value=0x4D), |     grinch_items.gadgets.SLIME_SHOOTER: GrinchItemData( | ||||||
|         GrinchRamData(0x0102E7, value=0x4E), GrinchRamData(0x0102E8, value=0x4F), |         [ | ||||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=5) |             grinch_categories.GADGETS, | ||||||
|          ]), |             "Slime Gun",  # For canon --MarioSpore | ||||||
|     "Grinch Copter": GrinchItemData(["Gadgets"], 106, IC.progression, |         ], | ||||||
|         [GrinchRamData(0x0102E9, value=0x40), GrinchRamData(0x0102EA, value=0x41), |         103, | ||||||
|         GrinchRamData(0x0102EB, value=0x42), GrinchRamData(0x0102EC, value=0x43), |         IC.progression, | ||||||
|         GrinchRamData(0x0102ED, value=0x44), GrinchRamData(0x0102EE, value=0x45), |         [ | ||||||
|         GrinchRamData(0x0102EF, value=0x46), GrinchRamData(0x0102F0, value=0x47), |             GrinchRamData(0x0102C7, value=0x40), | ||||||
|         GrinchRamData(0x0102F1, value=0x48), GrinchRamData(0x0102F2, value=0x49), |             GrinchRamData(0x0102C8, value=0x41), | ||||||
|         GrinchRamData(0x0102F3, value=0x4A), GrinchRamData(0x0102F4, value=0x4B), |             GrinchRamData(0x0102C9, value=0x42), | ||||||
|         GrinchRamData(0x0102F5, value=0x4C), GrinchRamData(0x0102F6, value=0x4D), |             GrinchRamData(0x0102CA, value=0x44), | ||||||
|         GrinchRamData(0x0102F7, value=0x4E), GrinchRamData(0x0102F8, value=0x4F), |             GrinchRamData(0x0102CB, value=0x45), | ||||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=6) |             GrinchRamData(0x0102CC, value=0x46), | ||||||
|     ]) |             GrinchRamData(0x0102CD, value=0x48), | ||||||
|  |             GrinchRamData(0x0102CE, value=0x49), | ||||||
|  |             GrinchRamData(0x0102CF, value=0x4A), | ||||||
|  |             # GrinchRamData(0x0100BC, binary_bit_pos=3) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.gadgets.OCTOPUS_CLIMBING_DEVICE: GrinchItemData( | ||||||
|  |         [grinch_categories.GADGETS], | ||||||
|  |         104, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0102D0, value=0x40), | ||||||
|  |             GrinchRamData(0x0102D1, value=0x41), | ||||||
|  |             GrinchRamData(0x0102D2, value=0x42), | ||||||
|  |             GrinchRamData(0x0102D3, value=0x44), | ||||||
|  |             GrinchRamData(0x0102D4, value=0x45), | ||||||
|  |             GrinchRamData(0x0102D5, value=0x46), | ||||||
|  |             GrinchRamData(0x0102D6, value=0x48), | ||||||
|  |             GrinchRamData(0x0102D7, value=0x49), | ||||||
|  |             GrinchRamData(0x0102D8, value=0x4A), | ||||||
|  |             # GrinchRamData(0x0100BC, binary_bit_pos=4) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.gadgets.MARINE_MOBILE: GrinchItemData( | ||||||
|  |         [grinch_categories.GADGETS], | ||||||
|  |         105, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0102D9, value=0x40), | ||||||
|  |             GrinchRamData(0x0102DA, value=0x41), | ||||||
|  |             GrinchRamData(0x0102DB, value=0x42), | ||||||
|  |             GrinchRamData(0x0102DC, value=0x43), | ||||||
|  |             GrinchRamData(0x0102DD, value=0x44), | ||||||
|  |             GrinchRamData(0x0102DE, value=0x45), | ||||||
|  |             GrinchRamData(0x0102DF, value=0x46), | ||||||
|  |             GrinchRamData(0x0102E0, value=0x47), | ||||||
|  |             GrinchRamData(0x0102E1, value=0x48), | ||||||
|  |             GrinchRamData(0x0102E2, value=0x49), | ||||||
|  |             GrinchRamData(0x0102E3, value=0x4A), | ||||||
|  |             GrinchRamData(0x0102E4, value=0x4B), | ||||||
|  |             GrinchRamData(0x0102E5, value=0x4C), | ||||||
|  |             GrinchRamData(0x0102E6, value=0x4D), | ||||||
|  |             GrinchRamData(0x0102E7, value=0x4E), | ||||||
|  |             GrinchRamData(0x0102E8, value=0x4F), | ||||||
|  |             # GrinchRamData(0x0100BC, binary_bit_pos=5) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.gadgets.GRINCH_COPTER: GrinchItemData( | ||||||
|  |         [grinch_categories.GADGETS], | ||||||
|  |         106, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0102E9, value=0x40), | ||||||
|  |             GrinchRamData(0x0102EA, value=0x41), | ||||||
|  |             GrinchRamData(0x0102EB, value=0x42), | ||||||
|  |             GrinchRamData(0x0102EC, value=0x43), | ||||||
|  |             GrinchRamData(0x0102ED, value=0x44), | ||||||
|  |             GrinchRamData(0x0102EE, value=0x45), | ||||||
|  |             GrinchRamData(0x0102EF, value=0x46), | ||||||
|  |             GrinchRamData(0x0102F0, value=0x47), | ||||||
|  |             GrinchRamData(0x0102F1, value=0x48), | ||||||
|  |             GrinchRamData(0x0102F2, value=0x49), | ||||||
|  |             GrinchRamData(0x0102F3, value=0x4A), | ||||||
|  |             GrinchRamData(0x0102F4, value=0x4B), | ||||||
|  |             GrinchRamData(0x0102F5, value=0x4C), | ||||||
|  |             GrinchRamData(0x0102F6, value=0x4D), | ||||||
|  |             GrinchRamData(0x0102F7, value=0x4E), | ||||||
|  |             GrinchRamData(0x0102F8, value=0x4F), | ||||||
|  |             # GrinchRamData(0x0100BC, binary_bit_pos=6) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
| #Mission Specific Items | # Mission Specific Items | ||||||
| MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = { | MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     "Who Cloak": GrinchItemData(["Mission Specific Items", "Useful Items"], 200, IC.progression, |     grinch_items.level_items.WV_WHO_CLOAK: GrinchItemData( | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=0)]), |         [ | ||||||
|     "Painting Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 201, IC.progression_deprioritized, |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=1)]), |             grinch_categories.USEFUL_ITEMS, | ||||||
|     "Scissors": GrinchItemData(["Mission Specific Items", "Useful Items"], 202, IC.progression_deprioritized, |         ], | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)]), |         200, | ||||||
|     "Glue Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 203, IC.progression_deprioritized, |         IC.progression, | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=4)]), |         [GrinchRamData(0x0101F9, binary_bit_pos=0)], | ||||||
|     "Cable Car Access Card": GrinchItemData(["Mission Specific Items", "Useful Items"], 204, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=5)]), |     grinch_items.level_items.WV_PAINT_BUCKET: GrinchItemData( | ||||||
|     "Drill": GrinchItemData(["Mission Specific Items", "Useful Items"], 205, IC.progression_deprioritized, |         [ | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=2)]), |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|     "Rope": GrinchItemData(["Mission Specific Items", "Useful Items"], 206, IC.progression_deprioritized, |             grinch_categories.USEFUL_ITEMS, | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=1)]), |         ], | ||||||
|     "Hook": GrinchItemData(["Mission Specific Items", "Useful Items"], 207, IC.progression_deprioritized, |         201, | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=0)]), |         IC.progression_deprioritized, | ||||||
|     "Sculpting Tools": GrinchItemData(["Mission Specific Items", "Useful Items"], 208, IC.progression_deprioritized, |         [GrinchRamData(0x0101F9, binary_bit_pos=1)], | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=2)]), |     ), | ||||||
|     "Hammer": GrinchItemData(["Mission Specific Items", "Useful Items"], 209, IC.progression_deprioritized, |     grinch_items.level_items.WD_SCISSORS: GrinchItemData( | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=3)]), |         [ | ||||||
|     "Scout Clothes": GrinchItemData(["Mission Specific Items", "Useful Items"], 210, IC.progression, |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=7)]) |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         202, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0101F9, binary_bit_pos=6), | ||||||
|  |             GrinchRamData(0x0100C2, binary_bit_pos=1), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WF_GLUE_BUCKET: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         203, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101F9, binary_bit_pos=4)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WF_CABLE_CAR_ACCESS_CARD: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         204, | ||||||
|  |         IC.progression, | ||||||
|  |         [GrinchRamData(0x0101F9, binary_bit_pos=5)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WL_DRILLL: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         205, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101FA, binary_bit_pos=2)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WL_ROPE: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         206, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101FA, binary_bit_pos=1)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WL_HOOK: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         207, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101FA, binary_bit_pos=0)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WV_SCULPTIN_TOOLS: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         208, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101F9, binary_bit_pos=2)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WV_HAMMER: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         209, | ||||||
|  |         IC.progression_deprioritized, | ||||||
|  |         [GrinchRamData(0x0101F9, binary_bit_pos=3)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.level_items.WL_SCOUT_CLOTHES: GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |         ], | ||||||
|  |         210, | ||||||
|  |         IC.progression, | ||||||
|  |         [GrinchRamData(0x0101F9, binary_bit_pos=7)], | ||||||
|  |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
| #Sleigh Parts | # Sleigh Parts | ||||||
| # SLEIGH_PARTS_TABLE: dict[str, GrinchItemData] = { | # SLEIGH_PARTS_TABLE: dict[str, GrinchItemData] = { | ||||||
| #     "Exhaust Pipes": GrinchItemData(["Sleigh Parts"], 300, IC.progression_skip_balancing, | #     "Exhaust Pipes": GrinchItemData(["Sleigh Parts"], 300, IC.progression_skip_balancing, | ||||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=2)]), | #         [GrinchRamData(0x0101FB, binary_bit_pos=2)]), | ||||||
| @@ -166,16 +358,32 @@ MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = { | |||||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=6)]) | #         [GrinchRamData(0x0101FB, binary_bit_pos=6)]) | ||||||
| # } | # } | ||||||
|  |  | ||||||
| #Access Keys | # Access Keys | ||||||
| KEYS_TABLE: dict[str, GrinchItemData] = { | KEYS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     "Whoville Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 400, IC.progression, |     grinch_items.keys.WHOVILLE: GrinchItemData( | ||||||
|         [GrinchRamData(0x010200, binary_bit_pos=1)]), |         [grinch_categories.VACUUM_TUBES], | ||||||
|     "Who Forest Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 401, IC.progression, |         400, | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=2)]), |         IC.progression, | ||||||
|     "Who Dump Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 402, IC.progression, |         [GrinchRamData(0x010200, binary_bit_pos=1)], | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=3)]), |     ), | ||||||
|     "Who Lake Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 403, IC.progression, |     grinch_items.keys.WHO_FOREST: GrinchItemData( | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=4)]), |         [grinch_categories.VACUUM_TUBES], | ||||||
|  |         401, | ||||||
|  |         IC.progression, | ||||||
|  |         [GrinchRamData(0x0100AA, binary_bit_pos=2)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.keys.WHO_DUMP: GrinchItemData( | ||||||
|  |         [grinch_categories.VACUUM_TUBES], | ||||||
|  |         402, | ||||||
|  |         IC.progression, | ||||||
|  |         [GrinchRamData(0x0100AA, binary_bit_pos=3)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.keys.WHO_LAKE: GrinchItemData( | ||||||
|  |         [grinch_categories.VACUUM_TUBES], | ||||||
|  |         403, | ||||||
|  |         IC.progression, | ||||||
|  |         [GrinchRamData(0x0100AA, binary_bit_pos=4)], | ||||||
|  |     ), | ||||||
|     # "Progressive Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 404, IC.progression, |     # "Progressive Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 404, IC.progression, | ||||||
|     #     [GrinchRamData()]), |     #     [GrinchRamData()]), | ||||||
|     # "Spin N' Win Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 405, IC.progression, |     # "Spin N' Win Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 405, IC.progression, | ||||||
| @@ -188,71 +396,196 @@ KEYS_TABLE: dict[str, GrinchItemData] = { | |||||||
|     #     [GrinchRamData()]), |     #     [GrinchRamData()]), | ||||||
|     # "Bike Race Access": GrinchItemData(["Supadow Door Unlocks", 409, IC.progression, |     # "Bike Race Access": GrinchItemData(["Supadow Door Unlocks", 409, IC.progression, | ||||||
|     #     [GrinchRamData()]) |     #     [GrinchRamData()]) | ||||||
|     "Sleigh Room Key": GrinchItemData(["Sleigh Room"], 410, IC.progression, |     grinch_items.keys.SLEIGH_ROOM_KEY: GrinchItemData( | ||||||
|         [GrinchRamData(0x010200, binary_bit_pos=6), GrinchRamData(0x0100AA, binary_bit_pos=5)]) |         [ | ||||||
|  |             grinch_categories.SLEIGH_ROOM, | ||||||
|  |             grinch_categories.REQUIRED_ITEM, | ||||||
|  |         ], | ||||||
|  |         410, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x010200, binary_bit_pos=6), | ||||||
|  |             GrinchRamData(0x0100AA, binary_bit_pos=5), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
| #Misc Items | # Misc Items | ||||||
| MISC_ITEMS_TABLE: dict[str, GrinchItemData] = { | MISC_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     # This item may not function properly if you receive it during a loading screen or in Mount Crumpit |     # This item may not function properly if you receive it during a loading screen or in Mount Crumpit | ||||||
|     # "Fully Healed Grinch": GrinchItemData(["Health Items", "Filler"], 500, IC.filler, |     # "Fully Healed Grinch": GrinchItemData(["Health Items", "Filler"], 500, IC.filler, | ||||||
|     #     [GrinchRamData(0x0E8FDC, value=120)]), |     #     [GrinchRamData(0x0E8FDC, value=120)]), | ||||||
|     "5 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 502, IC.filler, |     "5 Rotten Eggs": GrinchItemData( | ||||||
|         [GrinchRamData(0x010058, value=5, update_existing_value=True, max_count=200, bit_size=2)]), |         [ | ||||||
|     "10 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 503, IC.filler, |             grinch_categories.ROTTEN_EGG_BUNDLES, | ||||||
|         [GrinchRamData(0x010058, value=10, update_existing_value=True, max_count=200, bit_size=2)]), |             grinch_categories.FILLER, | ||||||
|     "20 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 504, IC.filler, |         ], | ||||||
|         [GrinchRamData(0x010058, value=20, update_existing_value=True, max_count=200, bit_size=2)]) |         502, | ||||||
|  |         IC.filler, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData( | ||||||
|  |                 0x010058, | ||||||
|  |                 value=5, | ||||||
|  |                 update_method=UpdateMethod.ADD, | ||||||
|  |                 max_count=200, | ||||||
|  |                 byte_size=2, | ||||||
|  |             ) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     "10 Rotten Eggs": GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.ROTTEN_EGG_BUNDLES, | ||||||
|  |             grinch_categories.FILLER, | ||||||
|  |         ], | ||||||
|  |         503, | ||||||
|  |         IC.filler, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData( | ||||||
|  |                 0x010058, | ||||||
|  |                 value=10, | ||||||
|  |                 update_method=UpdateMethod.ADD, | ||||||
|  |                 max_count=200, | ||||||
|  |                 byte_size=2, | ||||||
|  |             ) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     "20 Rotten Eggs": GrinchItemData( | ||||||
|  |         [ | ||||||
|  |             grinch_categories.ROTTEN_EGG_BUNDLES, | ||||||
|  |             grinch_categories.FILLER, | ||||||
|  |         ], | ||||||
|  |         504, | ||||||
|  |         IC.filler, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData( | ||||||
|  |                 0x010058, | ||||||
|  |                 value=20, | ||||||
|  |                 update_method=UpdateMethod.ADD, | ||||||
|  |                 max_count=200, | ||||||
|  |                 byte_size=2, | ||||||
|  |             ) | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
| USEFUL_IC_TABLE: dict[str, GrinchItemData] = { | USEFUL_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     "Heart of Stone": GrinchItemData(["Health Items"], 501, IC.useful, |     grinch_items.useful_items.HEART_OF_STONE: GrinchItemData( | ||||||
|         [GrinchRamData(0x0100ED, value=1, update_existing_value=True, max_count=4)]) |         [ | ||||||
|  |             grinch_categories.USEFUL_ITEMS, | ||||||
|  |             grinch_categories.HEALING_ITEMS, | ||||||
|  |         ], | ||||||
|  |         501, | ||||||
|  |         IC.useful, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData( | ||||||
|  |                 0x0100ED, | ||||||
|  |                 value=1, | ||||||
|  |                 update_method=UpdateMethod.ADD, | ||||||
|  |                 max_count=4, | ||||||
|  |             ) | ||||||
|  |         ], | ||||||
|  |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #Traps | # Traps | ||||||
| TRAPS_TABLE: dict[str, GrinchItemData] = { | TRAPS_TABLE: dict[str, GrinchItemData] = { | ||||||
| # alias to Ice Trap for traplink |     # alias to Ice Trap for traplink | ||||||
|     # "Freeze Trap": GrinchItemData(["Traps"], 600, IC.trap, [GrinchRamData()]), |     # "Freeze Trap": GrinchItemData(["Traps"], 600, IC.trap, [GrinchRamData()]), | ||||||
|     # "Bee Trap": GrinchItemData(["Traps"], 601, IC.trap, [GrinchRamData()]), |     # "Bee Trap": GrinchItemData(["Traps"], 601, IC.trap, [GrinchRamData()]), | ||||||
|     # "Electrocution Trap": GrinchItemData(["Traps"], 602, IC.trap, [GrinchRamData()]), |     # "Electrocution Trap": GrinchItemData(["Traps"], 602, IC.trap, [GrinchRamData()]), | ||||||
| # alias to Slowness Trap for traplink |     # alias to Slowness Trap for traplink | ||||||
|     # "Tip Toe Trap": GrinchItemData(["Traps"], 603, IC.trap, [GrinchRamData()]), |     # "Tip Toe Trap": GrinchItemData(["Traps"], 603, IC.trap, [GrinchRamData()]), | ||||||
| # This item may not function properly if you receive it during a loading screen or in Mount Crumpit |     # This item may not function properly if you receive it during a loading screen or in Mount Crumpit | ||||||
| # alias to Exhaustion Trap |     # alias to Exhaustion Trap | ||||||
| #     "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_existing_value=True)]), |     #     "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_method=UpdateMethod.ADD)]), | ||||||
|     "Depletion Trap": GrinchItemData(["Traps"], 605, IC.trap, [GrinchRamData(0x010058, value=0, bit_size=2)]), |     grinch_items.trap_items.DEPLETION_TRAP: GrinchItemData( | ||||||
|     "Dump it to Crumpit": GrinchItemData(["Traps"], 606, IC.trap, #Alias to Home Trap for traplink |         [grinch_categories.TRAPS], | ||||||
|         [GrinchRamData(0x010000, value=0x05), GrinchRamData(0x08FB94, value=1), GrinchRamData(0x0100B4, value=0)]), |         605, | ||||||
| #alias to Spring Trap for traplink |         IC.trap, | ||||||
|  |         [GrinchRamData(0x010058, value=0, byte_size=2)], | ||||||
|  |     ), | ||||||
|  |     grinch_items.trap_items.DUMP_IT_TO_CRUMPIT: GrinchItemData( | ||||||
|  |         [grinch_categories.TRAPS], | ||||||
|  |         606, | ||||||
|  |         IC.trap,  # Alias to Home Trap for traplink | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x010000, value=0x05), | ||||||
|  |             GrinchRamData(0x08FB94, value=1), | ||||||
|  |             GrinchRamData(0x0100B4, value=0), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     # alias to Spring Trap for traplink | ||||||
|     # "Rocket Spring Trap": GrinchItemData(["Traps"], 607, IC.trap, [GrinchRamData()]), |     # "Rocket Spring Trap": GrinchItemData(["Traps"], 607, IC.trap, [GrinchRamData()]), | ||||||
| #alias to Home Trap for traplink |     # alias to Home Trap for traplink | ||||||
|     "Who sent me back?": GrinchItemData(["Traps"], 608, IC.trap, [GrinchRamData(0x08FB94, value=1)]), |     grinch_items.trap_items.WHO_SENT_ME_BACK: GrinchItemData( | ||||||
|  |         [grinch_categories.TRAPS], | ||||||
|  |         608, | ||||||
|  |         IC.trap, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x08FB94, value=1), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|     # "Cutscene Trap": GrinchItemData(["Traps"], 609, IC.trap, [GrinchRamData()]), |     # "Cutscene Trap": GrinchItemData(["Traps"], 609, IC.trap, [GrinchRamData()]), | ||||||
|     # "No Vac Trap": GrinchItemData(["Traps"], 610, IC.trap, [GrinchRamData(0x0102DA, value=0]), |     # "No Vac Trap": GrinchItemData(["Traps"], 610, IC.trap, [GrinchRamData(0x0102DA, value=0]), | ||||||
|     # "Invisible Trap": GrinchItemData(["Traps"], 611, IC.trap, [GrinchRamData(0x0102DA, value=0, bit_size=4)]) |     # "Invisible Trap": GrinchItemData(["Traps"], 611, IC.trap, [GrinchRamData(0x0102DA, value=0, byte_size=4)]) | ||||||
|     # "Child Trap": GrinchItemData(["Traps"], 612, IC.trap,[GrinchRamData()]) |     # "Child Trap": GrinchItemData(["Traps"], 612, IC.trap,[GrinchRamData()]) | ||||||
|     # "Disable Jump Trap": GrinchItemData(["Traps"], 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)]) |     # "Disable Jump Trap": GrinchItemData(["Traps"], 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)]) | ||||||
| } | } | ||||||
|  |  | ||||||
| #Movesets | # Movesets | ||||||
| # MOVES_TABLE: dict[str, GrinchItemData] = { | MOVES_TABLE: dict[str, GrinchItemData] = { | ||||||
| #     "Bad Breath": GrinchItemData(["Movesets"], 700, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=1)]), |     grinch_items.moves.BAD_BREATH: GrinchItemData( | ||||||
| #     "Pancake": GrinchItemData(["Movesets"], 701, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=2)]), |         [grinch_categories.MOVES], | ||||||
| #     "Push & Pull": GrinchItemData(["Movesets"], 702, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=3)]), |         700, | ||||||
| #     "Max": GrinchItemData(["Movesets"], 703, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=4)]), |         IC.progression, | ||||||
| #     "Tip Toe": GrinchItemData(["Movesets"], 704, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=5)]) |         [ | ||||||
| # } |             GrinchRamData(0x0100BB, binary_bit_pos=1), | ||||||
| #Double star combines all dictionaries from each individual list together |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.moves.PANCAKE: GrinchItemData( | ||||||
|  |         [grinch_categories.MOVES], | ||||||
|  |         701, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0100BB, binary_bit_pos=2), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.moves.SIEZE: GrinchItemData( | ||||||
|  |         [grinch_categories.MOVES], | ||||||
|  |         702, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0100BB, binary_bit_pos=3), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.moves.MAX: GrinchItemData( | ||||||
|  |         [grinch_categories.MOVES], | ||||||
|  |         703, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0100BB, binary_bit_pos=4), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     grinch_items.moves.SNEAK: GrinchItemData( | ||||||
|  |         [grinch_categories.MOVES], | ||||||
|  |         704, | ||||||
|  |         IC.progression, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0100BB, binary_bit_pos=5), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # Double star combines all dictionaries from each individual list together | ||||||
| ALL_ITEMS_TABLE: dict[str, GrinchItemData] = { | ALL_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||||
|     **GADGETS_TABLE, |     **GADGETS_TABLE, | ||||||
|     **MISSION_ITEMS_TABLE, |     **MISSION_ITEMS_TABLE, | ||||||
|     **KEYS_TABLE, |     **KEYS_TABLE, | ||||||
|     **MISC_ITEMS_TABLE, |     **MISC_ITEMS_TABLE, | ||||||
|     **TRAPS_TABLE, |     **TRAPS_TABLE, | ||||||
|     **USEFUL_IC_TABLE, |     **USEFUL_ITEMS_TABLE, | ||||||
|     # **SLEIGH_PARTS_TABLE, |     # **SLEIGH_PARTS_TABLE, | ||||||
|     # **MOVES_TABLE, |     **MOVES_TABLE, | ||||||
| } | } | ||||||
|  |  | ||||||
| # Psuedocoding traplink table | # Psuedocoding traplink table | ||||||
| @@ -268,6 +601,7 @@ ALL_ITEMS_TABLE: dict[str, GrinchItemData] = { | |||||||
| # ELEC_TRAP_EQUIV = [] | # ELEC_TRAP_EQUIV = [] | ||||||
| # DEPL_TRAP_EQUIV = ["Dry Trap"] | # DEPL_TRAP_EQUIV = ["Dry Trap"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def grinch_items_to_id() -> dict[str, int]: | def grinch_items_to_id() -> dict[str, int]: | ||||||
|     item_mappings: dict[str, int] = {} |     item_mappings: dict[str, int] = {} | ||||||
|     for ItemName, ItemData in ALL_ITEMS_TABLE.items(): |     for ItemName, ItemData in ALL_ITEMS_TABLE.items(): | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,13 +1,26 @@ | |||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
|  |  | ||||||
| from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, OptionList, \ | from Options import ( | ||||||
|     PerGameCommonOptions, OptionSet |     FreeText, | ||||||
|  |     NumericOption, | ||||||
|  |     Toggle, | ||||||
|  |     DefaultOnToggle, | ||||||
|  |     Choice, | ||||||
|  |     TextChoice, | ||||||
|  |     Range, | ||||||
|  |     NamedRange, | ||||||
|  |     OptionList, | ||||||
|  |     PerGameCommonOptions, | ||||||
|  |     OptionSet, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class StartingArea(Choice): | class StartingArea(Choice): | ||||||
|     """ |     """ | ||||||
|     Here, you can select which area you'll start the game with. [NOT IMPLEMENTED] |     Here, you can select which area you'll start the game with. [NOT IMPLEMENTED] | ||||||
|     Whichever one you pick is the region you'll have access to at the start of the Multiworld. |     Whichever one you pick is the region you'll have access to at the start of the Multiworld. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     option_whoville = 0 |     option_whoville = 0 | ||||||
|     option_who_forest = 1 |     option_who_forest = 1 | ||||||
|     option_who_dump = 2 |     option_who_dump = 2 | ||||||
| @@ -15,14 +28,17 @@ class StartingArea(Choice): | |||||||
|     default = 0 |     default = 0 | ||||||
|     display_name = "Starting Area" |     display_name = "Starting Area" | ||||||
|  |  | ||||||
| class ProgressiveVacuum(Toggle):#DefaultOnToggle |  | ||||||
|  | class ProgressiveVacuum(Toggle):  # DefaultOnToggle | ||||||
|     """ |     """ | ||||||
|     Determines whether you get access to main areas progressively [NOT IMPLEMENTED] |     Determines whether you get access to main areas progressively [NOT IMPLEMENTED] | ||||||
|  |  | ||||||
|     Enabled: Whoville > Who Forest > Who Dump > Who Lake |     Enabled: Whoville > Who Forest > Who Dump > Who Lake | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     display_name = "Progressive Vacuum Tubes" |     display_name = "Progressive Vacuum Tubes" | ||||||
|  |  | ||||||
|  |  | ||||||
| class Missionsanity(Choice): | class Missionsanity(Choice): | ||||||
|     """ |     """ | ||||||
|     How mission checks are randomized in the pool [NOT IMPLEMENTED] |     How mission checks are randomized in the pool [NOT IMPLEMENTED] | ||||||
| @@ -32,6 +48,7 @@ class Missionsanity(Choice): | |||||||
|     Individual: Individual tasks for one mission, such as individual snowmen squashed, are checks. |     Individual: Individual tasks for one mission, such as individual snowmen squashed, are checks. | ||||||
|     Both: Both individual tasks and mission completion are randomized. |     Both: Both individual tasks and mission completion are randomized. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     display_name = "Mission Locations" |     display_name = "Mission Locations" | ||||||
|     option_none = 0 |     option_none = 0 | ||||||
|     option_completion = 1 |     option_completion = 1 | ||||||
| @@ -39,74 +56,93 @@ class Missionsanity(Choice): | |||||||
|     option_both = 3 |     option_both = 3 | ||||||
|     default = 1 |     default = 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| class ExcludeEnvironments(OptionSet): | class ExcludeEnvironments(OptionSet): | ||||||
|     """ |     """ | ||||||
|     Allows entire environments to be an excluded location to ensure you are not logically required to enter the environment along |     Allows entire environments to be an excluded location to ensure you are not logically required to enter the environment along | ||||||
|     with any and all checks that are in that environment too. WARNING: Excluding too many environments may cause generation to fail. |     with any and all checks that are in that environment too. | ||||||
|  |  | ||||||
|  |     WARNING: Excluding too many environments may cause generation to fail. | ||||||
|     [NOT IMPLEMENTED] |     [NOT IMPLEMENTED] | ||||||
|  |  | ||||||
|     Valid keys: "Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall", |     Valid keys: "Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall", | ||||||
|                   "Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut", |                   "Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut", | ||||||
|                   "North Shore", "Mayor's Villa", "Sleigh Ride" |                   "North Shore", "Mayor's Villa", "Sleigh Ride" | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     display_name = "Exclude Environments" |  | ||||||
|     valid_keys = {"Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall", |  | ||||||
|                   "Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut", |  | ||||||
|                   "North Shore", "Mayor's Villa", "Sleigh Ride"} |  | ||||||
|  |  | ||||||
| class ProgressiveGadget(Toggle):#DefaultOnToggle |     display_name = "Exclude Environments" | ||||||
|  |     valid_keys = { | ||||||
|  |         "Whoville", | ||||||
|  |         "Who Forest", | ||||||
|  |         "Who Dump", | ||||||
|  |         "Who Lake", | ||||||
|  |         "Post Office", | ||||||
|  |         "Clock Tower", | ||||||
|  |         "City Hall", | ||||||
|  |         "Ski Resort", | ||||||
|  |         "Civic Center", | ||||||
|  |         "Minefield", | ||||||
|  |         "Power Plant", | ||||||
|  |         "Generator Building", | ||||||
|  |         "Scout's Hut", | ||||||
|  |         "North Shore", | ||||||
|  |         "Mayor's Villa", | ||||||
|  |         "Sleigh Ride", | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProgressiveGadget(Toggle):  # DefaultOnToggle | ||||||
|     """ |     """ | ||||||
|     Determines whether you get access to a gadget as individual blueprint count. [NOT IMPLEMENTED] |     Determines whether you get access to a gadget as individual blueprint count. [NOT IMPLEMENTED] | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     display_name = "Progressive Gadgets" |     display_name = "Progressive Gadgets" | ||||||
|  |  | ||||||
|  |  | ||||||
| class Supadow(Toggle): | class Supadow(Toggle): | ||||||
|     """Enables completing minigames through the Supadows in Mount Crumpit as checks. NOT IMPLEMENTED]""" |     """Enables completing minigames through the Supadows in Mount Crumpit as checks. NOT IMPLEMENTED]""" | ||||||
|  |  | ||||||
|     display_name = "Supadow Minigames" |     display_name = "Supadow Minigames" | ||||||
|  |  | ||||||
|  |  | ||||||
| class Gifts(Range): | class Gifts(Range): | ||||||
|     """ |     """ | ||||||
|     Considers how many gifts must be squashed per check. |     Considers how many gifts must be squashed per check. | ||||||
|     Enabling this will also enable squashing all gifts in a region mission along side this. [NOT IMPLEMENTED]""" |     Enabling this will also enable squashing all gifts in a region mission along side this. [NOT IMPLEMENTED] | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     display_name = "Gifts Squashed per Check" |     display_name = "Gifts Squashed per Check" | ||||||
|     range_start = 0 |     range_start = 0 | ||||||
|     range_end = 300 |     range_end = 300 | ||||||
|     default = 0 |     default = 0 | ||||||
|  |  | ||||||
| class GadgetRando(OptionSet): |  | ||||||
|     """ |  | ||||||
|     Randomizes Grinch's gadgets along with randomizing Binoculars into the pool. [NOT IMPLEMENTED] |  | ||||||
|  |  | ||||||
|     Valid keys: "Binoculars", "Rotten Egg Launcher", "Rocket Spring", "Slime Shooter", "Octopus Climbing Device", | class Moverando(Toggle): | ||||||
|                 "Marine Mobile", "Grinch Copter" |     """Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]""" | ||||||
|     """ |  | ||||||
|     display_name = "Gadgets Randomized" |  | ||||||
|     valid_keys = {"Binoculars", "Rotten Egg Launcher", "Rocket Spring", "Slime Shooter", "Octopus Climbing Device", |  | ||||||
|                 "Marine Mobile", "Grinch Copter"} |  | ||||||
|  |  | ||||||
| class Moverando(OptionSet): |  | ||||||
|     """Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED] |  | ||||||
|  |  | ||||||
|     Valid keys: "Pancake", "Seize", "Max", "Bad Breath", "Sneak" |  | ||||||
|     """ |  | ||||||
|     display_name = "Moves Randomized" |     display_name = "Moves Randomized" | ||||||
|     valid_keys = {"Pancake", "Seize", "Max", "Bad Breath", "Sneak"} |  | ||||||
|  |  | ||||||
| class UnlimitedEggs(Toggle): | class UnlimitedEggs(Toggle): | ||||||
|     """Determine whether or not you run out of rotten eggs when you utilize your gadgets.""" |     """Determine whether or not you run out of rotten eggs when you utilize your gadgets.""" | ||||||
|  |  | ||||||
|     display_name = "Unlimited Rotten Eggs" |     display_name = "Unlimited Rotten Eggs" | ||||||
|  |  | ||||||
|  |  | ||||||
| class RingLinkOption(Toggle): | class RingLinkOption(Toggle): | ||||||
|     """Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled.""" |     """Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled.""" | ||||||
|  |  | ||||||
|     display_name = "Ring Link" |     display_name = "Ring Link" | ||||||
|  |  | ||||||
|  |  | ||||||
| class TrapLinkOption(Toggle): | class TrapLinkOption(Toggle): | ||||||
|     """If a trap is sent from Grinch, traps that are compatible with other games are triggered as well. [NOT IMPLEMENTED]""" |     """If a trap is sent from Grinch, traps that are compatible with other games are triggered as well. [NOT IMPLEMENTED]""" | ||||||
|  |  | ||||||
|     display_name = "Trap Link" |     display_name = "Trap Link" | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin | class GrinchOptions(PerGameCommonOptions):  # DeathLinkMixin | ||||||
|     starting_area: StartingArea |     starting_area: StartingArea | ||||||
|     progressive_vacuum: ProgressiveVacuum |     progressive_vacuum: ProgressiveVacuum | ||||||
|     missionsanity: Missionsanity |     missionsanity: Missionsanity | ||||||
|   | |||||||
| @@ -1,12 +1,66 @@ | |||||||
|  | from enum import STRICT, IntEnum | ||||||
| from typing import NamedTuple, Optional | from typing import NamedTuple, Optional | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UpdateMethod(IntEnum, boundary=STRICT): | ||||||
|  |     SET = 1 | ||||||
|  |     ADD = 2 | ||||||
|  |     SUBTRACT = 3 | ||||||
|  |     FREEZE = 4 | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrinchRamData(NamedTuple): | class GrinchRamData(NamedTuple): | ||||||
|  |     """A Representation of an update to RAM data for The Grinch. | ||||||
|  |  | ||||||
|  |     ram_address (int): The RAM address that we are updating, usually passed in with hex, representation (0x11111111) | ||||||
|  |     value (int; Optional): The value we are using to set, add, or subtract from the RAM Address Value. Defaults to 1 if binary_bit_pos is passed in | ||||||
|  |     binary_bit_pos: (int; Optional): If passed in, we are looking for a specific bit within the byte of the ram_address. This is represented as a small-endian bit position, meaning the right-most bit is 0, and the left-most bit is 7 | ||||||
|  |     byte_size: (int: Default: 1): The size of the RAM Address address we are looking for. | ||||||
|  |     update_method (UpdateMethod; Default: SET): Determines what we are doing to the RAM Address. We can either SET the address, simply assigning a value. We can ADD or SUBTRACT, modifying hte existing value by a set amount. And we can FREEZE the address, preventing it from updating in the future | ||||||
|  |     min_count: The minimum amount that a value can go down to using SUBTRACT | ||||||
|  |     max_count: The maximum amount that a value can go down to using ADD | ||||||
|  |     """ | ||||||
|  |  | ||||||
|     ram_address: int |     ram_address: int | ||||||
|     value: Optional[int] = None #none is empty/null |     value: Optional[int] = None  # none is empty/null | ||||||
|     # Either set or add either hex or unsigned values through Client.py |     # Either set or add either hex or unsigned values through Client.py | ||||||
|     # Hex uses 0x00, unsigned are base whole numbers |     # Hex uses 0x00, unsigned are base whole numbers | ||||||
|     binary_bit_pos: Optional[int] = None |     binary_bit_pos: Optional[int] = None | ||||||
|     bit_size: int = 1 |     byte_size: int = 1 | ||||||
|     update_existing_value: bool = False |     update_method: UpdateMethod = UpdateMethod.SET | ||||||
|     max_count: int = 0 |     min_count: int = 0 | ||||||
|  |     max_count: int = 255 | ||||||
|  |  | ||||||
|  |     def __init__( | ||||||
|  |         self, | ||||||
|  |         ram_address: int, | ||||||
|  |         value: Optional[int], | ||||||
|  |         byte_size: int, | ||||||
|  |         update_method: UpdateMethod, | ||||||
|  |         min_count: int, | ||||||
|  |         max_count: int, | ||||||
|  |     ): | ||||||
|  |         self.ram_address = ram_address | ||||||
|  |  | ||||||
|  |         if value: | ||||||
|  |             self.value = value | ||||||
|  |         else: | ||||||
|  |             self.value = 1 | ||||||
|  |  | ||||||
|  |         if byte_size: | ||||||
|  |             self.byte_size = byte_size | ||||||
|  |  | ||||||
|  |         if update_method: | ||||||
|  |             self.update_method = update_method | ||||||
|  |  | ||||||
|  |         if min_count and min_count > -1: | ||||||
|  |             self.min_count = min_count | ||||||
|  |  | ||||||
|  |         if max_count and max_count > -1: | ||||||
|  |             self.max_count = max_count | ||||||
|  |         elif max_count and max_count > ((2 ** (self.byte_size * 8)) - 1): | ||||||
|  |             raise ValueError( | ||||||
|  |                 "max_count cannot be larger than the RAM addresses max possible value" | ||||||
|  |             ) | ||||||
|  |         else: | ||||||
|  |             self.max_count = (2 ** (self.byte_size * 8)) - 1 | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ mainareas_list = [ | |||||||
|     "Whoville", |     "Whoville", | ||||||
|     "Who Forest", |     "Who Forest", | ||||||
|     "Who Dump", |     "Who Dump", | ||||||
|     "Who Lake" |     "Who Lake", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| subareas_list = [ | subareas_list = [ | ||||||
| @@ -29,29 +29,35 @@ subareas_list = [ | |||||||
|     "Scout's Hut", |     "Scout's Hut", | ||||||
|     "North Shore", |     "North Shore", | ||||||
|     "Mayor's Villa", |     "Mayor's Villa", | ||||||
|     "Sleigh Room" |     "Sleigh Room", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| supadow_list = [ | supadow_list = [ | ||||||
|     "Spin N' Win Supadow", |     "Spin N' Win Supadow", | ||||||
|     "Dankamania Supadow", |     "Dankamania Supadow", | ||||||
|     "The Copter Race Contest Supadow", |     "The Copter Race Contest Supadow", | ||||||
|     "Bike Race" |     "Bike Race", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_regions(world: "GrinchWorld"): | def create_regions(world: "GrinchWorld"): | ||||||
|     for mainarea in mainareas_list: |     for mainarea in mainareas_list: | ||||||
|         #Each area in mainarea, create a region for the given player |         # Each area in mainarea, create a region for the given player | ||||||
|         world.multiworld.regions.append(Region(mainarea, world.player, world.multiworld)) |         world.multiworld.regions.append( | ||||||
|  |             Region(mainarea, world.player, world.multiworld) | ||||||
|  |         ) | ||||||
|     for subarea in subareas_list: |     for subarea in subareas_list: | ||||||
|         #Each area in subarea, create a region for the given player |         # Each area in subarea, create a region for the given player | ||||||
|         world.multiworld.regions.append(Region(subarea, world.player, world.multiworld)) |         world.multiworld.regions.append(Region(subarea, world.player, world.multiworld)) | ||||||
|     for supadow in supadow_list: |     for supadow in supadow_list: | ||||||
|         #Each area in supadow, create a region for the given player |         # Each area in supadow, create a region for the given player | ||||||
|         world.multiworld.regions.append(Region(supadow, world.player, world.multiworld)) |         world.multiworld.regions.append(Region(supadow, world.player, world.multiworld)) | ||||||
|  |  | ||||||
|  |  | ||||||
| # TODO Optimize this function | # TODO Optimize this function | ||||||
| def grinchconnect(world: "GrinchWorld", current_region_name: str, connected_region_name: str): | def grinchconnect( | ||||||
|  |     world: "GrinchWorld", current_region_name: str, connected_region_name: str | ||||||
|  | ): | ||||||
|     current_region = world.get_region(current_region_name) |     current_region = world.get_region(current_region_name) | ||||||
|     connected_region = world.get_region(connected_region_name) |     connected_region = world.get_region(connected_region_name) | ||||||
|     required_items: list[list[str]] = access_rules_dict[connected_region.name] |     required_items: list[list[str]] = access_rules_dict[connected_region.name] | ||||||
| @@ -62,21 +68,26 @@ def grinchconnect(world: "GrinchWorld", current_region_name: str, connected_regi | |||||||
|     connected_region.connect(current_region) |     connected_region.connect(current_region) | ||||||
|     for access_rule in rule_list: |     for access_rule in rule_list: | ||||||
|         for region_entrance in current_region.entrances: |         for region_entrance in current_region.entrances: | ||||||
|             if region_entrance.connected_region.name == current_region_name and \ |             if ( | ||||||
|                 region_entrance.parent_region.name == connected_region_name: |                 region_entrance.connected_region.name == current_region_name | ||||||
|  |                 and region_entrance.parent_region.name == connected_region_name | ||||||
|  |             ): | ||||||
|                 if rule_list.index(access_rule) == 0: |                 if rule_list.index(access_rule) == 0: | ||||||
|                     add_rule(region_entrance, access_rule) |                     add_rule(region_entrance, access_rule) | ||||||
|                 else: |                 else: | ||||||
|                     add_rule(region_entrance, access_rule, combine="or") |                     add_rule(region_entrance, access_rule, combine="or") | ||||||
|         for region_entrance in connected_region.entrances: |         for region_entrance in connected_region.entrances: | ||||||
|             if region_entrance.connected_region.name == connected_region_name and \ |             if ( | ||||||
|                     region_entrance.parent_region.name == current_region_name: |                 region_entrance.connected_region.name == connected_region_name | ||||||
|  |                 and region_entrance.parent_region.name == current_region_name | ||||||
|  |             ): | ||||||
|                 if rule_list.index(access_rule) == 0: |                 if rule_list.index(access_rule) == 0: | ||||||
|                     add_rule(region_entrance, access_rule) |                     add_rule(region_entrance, access_rule) | ||||||
|                 else: |                 else: | ||||||
|                     add_rule(region_entrance, access_rule, combine="or") |                     add_rule(region_entrance, access_rule, combine="or") | ||||||
|  |  | ||||||
| #What regions are connected to each other |  | ||||||
|  | # What regions are connected to each other | ||||||
| def connect_regions(world: "GrinchWorld"): | def connect_regions(world: "GrinchWorld"): | ||||||
|     grinchconnect(world, "Mount Crumpit", "Whoville") |     grinchconnect(world, "Mount Crumpit", "Whoville") | ||||||
|     grinchconnect(world, "Mount Crumpit", "Who Forest") |     grinchconnect(world, "Mount Crumpit", "Who Forest") | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -8,7 +8,7 @@ from .Client import * | |||||||
| from typing import ClassVar | from typing import ClassVar | ||||||
|  |  | ||||||
| from worlds.AutoWorld import World | from worlds.AutoWorld import World | ||||||
| import Options | from Options import OptionError | ||||||
|  |  | ||||||
| from .Options import GrinchOptions | from .Options import GrinchOptions | ||||||
| from .Rules import access_rules_dict | from .Rules import access_rules_dict | ||||||
| @@ -31,7 +31,7 @@ class GrinchWorld(World): | |||||||
|  |  | ||||||
|     def generate_early(self) -> None: #Special conditions changed before generation occurs |     def generate_early(self) -> None: #Special conditions changed before generation occurs | ||||||
|         if self.options.ring_link == 1 and self.options.unlimited_eggs == 1: |         if self.options.ring_link == 1 and self.options.unlimited_eggs == 1: | ||||||
|             raise Options.OptionError("Cannot enable both unlimited rotten eggs and ring links. You can only enable one of these at a time."+ |             raise OptionError("Cannot enable both unlimited rotten eggs and ring links. You can only enable one of these at a time." + | ||||||
|                                       f"The following player's YAML needs to be fixed: {self.player_name}") |                                       f"The following player's YAML needs to be fixed: {self.player_name}") | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								worlds/grinch/archipelago.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								worlds/grinch/archipelago.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | { | ||||||
|  |     "minimum_ap_version": "0.6.3",  | ||||||
|  |     "world_version": "1.2.3", | ||||||
|  |     "authors": ["MarioSpore"], | ||||||
|  |     "game": "The Grinch" | ||||||
|  | } | ||||||
| @@ -5,7 +5,8 @@ | |||||||
| BizHawk support. | BizHawk support. | ||||||
| - Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.  | - Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.  | ||||||
| The game's CUE file should also work aswell along side the BIN file if you have troubles opening the BIN file. | The game's CUE file should also work aswell along side the BIN file if you have troubles opening the BIN file. | ||||||
| - [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) Versions 2.9.1 & 2.10 is supported, but I can't promise if any version is stable or not. As of September 2025, the Grinch may not be compatible with 2.11 yet. | - [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) Version 2.9.1 is required to play. Any version is NOT compatible and may have unintended behavior in  | ||||||
|  | the game as well as not being able to connect to the client through the LUA Console. | ||||||
| - The latest `grinch.apworld` file. You can find this on the [Releases page](https://github.com/MarioSpore/Grinch-AP/releases/latest). Put this in your `Archipelago/custom_worlds` folder. | - The latest `grinch.apworld` file. You can find this on the [Releases page](https://github.com/MarioSpore/Grinch-AP/releases/latest). Put this in your `Archipelago/custom_worlds` folder. | ||||||
| - PSX BIOS Firmware bin file, which is required to run the game through Bizhawk. The file you need should be | - PSX BIOS Firmware bin file, which is required to run the game through Bizhawk. The file you need should be | ||||||
| named something like `SCPH-5501.BIN`. | named something like `SCPH-5501.BIN`. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user