Compare commits
	
		
			13 Commits
		
	
	
		
			v1.2.2
			...
			artamis-mo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0a8df25f6 | |||
|   | 6f552949fa | ||
|   | ca1eb82ce1 | ||
|   | 414a155323 | ||
|   | b9d8d9174f | ||
|   | 8e58f9662e | ||
|   | 6f4597398f | ||
|   | 5e71874446 | ||
|   | 1870dd24ba | ||
|   | f70b6c4c9c | ||
|   | 79d4d5b10b | ||
|   | 7fea34adc3 | ||
|   | a3f9e6cbc9 | 
| @@ -6,7 +6,13 @@ import copy | ||||
| import uuid | ||||
| import Utils | ||||
| 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 | ||||
| from worlds._bizhawk.client import BizHawkClient | ||||
|  | ||||
| @@ -30,6 +36,7 @@ MAX_EGGS: int = 200 | ||||
| EGG_COUNT_ADDR: int = 0x010058 | ||||
| EGG_ADDR_BYTESIZE: int = 2 | ||||
|  | ||||
|  | ||||
| class GrinchClient(BizHawkClient): | ||||
|     game = "The Grinch" | ||||
|     system = "PSX" | ||||
| @@ -52,27 +59,44 @@ class GrinchClient(BizHawkClient): | ||||
|  | ||||
|     async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         # TODO Check the ROM data to see if it matches against bytes expected | ||||
|         grinch_identifier_ram_address: int = 0x00928C | ||||
|         bios_identifier_ram_address: int = 0x097F30 | ||||
|  | ||||
|         try: | ||||
|             bytes_actual: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                 grinch_identifier_ram_address, 11, "MainRAM")]))[0] | ||||
|             bytes_actual: bytes = ( | ||||
|                 await bizhawk.read( | ||||
|                     ctx.bizhawk_ctx, [(grinch_identifier_ram_address, 11, "MainRAM")] | ||||
|                 ) | ||||
|             )[0] | ||||
|  | ||||
|             psx_rom_name = bytes_actual.decode("ascii") | ||||
|             if psx_rom_name != "SLUS_011.97": | ||||
|                 bios_bytes_check: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                     bios_identifier_ram_address, 24, "MainRAM")]))[0] | ||||
|                 bios_bytes_check: bytes = ( | ||||
|                     await bizhawk.read( | ||||
|                         ctx.bizhawk_ctx, [(bios_identifier_ram_address, 24, "MainRAM")] | ||||
|                     ) | ||||
|                 )[0] | ||||
|  | ||||
|                 if "System ROM Version" in bios_bytes_check.decode("ascii"): | ||||
|                     if not self.loading_bios_msg: | ||||
|                         self.loading_bios_msg = True | ||||
|                         logger.error("BIOS is currently loading. Will wait up to 5 seconds before retrying.") | ||||
|                         logger.error( | ||||
|                             "BIOS is currently loading. Will wait up to 5 seconds before retrying." | ||||
|                         ) | ||||
|  | ||||
|                     return False | ||||
|  | ||||
|                 logger.error("Invalid rom detected. You are not playing Grinch USA Version.") | ||||
|                 raise Exception("Invalid rom detected. You are not playing Grinch USA Version.") | ||||
|                 logger.error( | ||||
|                     "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 | ||||
|  | ||||
|         except Exception: | ||||
|             return False | ||||
|  | ||||
| @@ -86,38 +110,54 @@ class GrinchClient(BizHawkClient): | ||||
|  | ||||
|     def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         super().on_package(ctx, cmd, args) | ||||
|  | ||||
|         match cmd: | ||||
|             case "Connected":  # On Connect | ||||
|                 self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"]) | ||||
|                 self.unique_client_id = self._get_uuid() | ||||
|                 logger.info("You are now connected to the client. "+ | ||||
|                     "There may be a slight delay to check you are not in demo mode before locations start to send.") | ||||
|                 logger.info( | ||||
|                     "You are now connected to the client. " | ||||
|                     + "There may be a slight delay to check you are not in demo mode before locations start to send." | ||||
|                 ) | ||||
|  | ||||
|                 self.ring_link_enabled = bool(ctx.slot_data["ring_link"]) | ||||
|  | ||||
|                 tags = copy.deepcopy(ctx.tags) | ||||
|  | ||||
|                 if self.ring_link_enabled: | ||||
|                     ctx.tags.add("RingLink") | ||||
|  | ||||
|                 else: | ||||
|                     ctx.tags -= { "RingLink" } | ||||
|                     ctx.tags -= {"RingLink"} | ||||
|  | ||||
|                 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": | ||||
|                 if "tags" not in args: | ||||
|                     return | ||||
|  | ||||
|                 if "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") | ||||
|                 if ( | ||||
|                     "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: | ||||
|         await ctx.get_username() | ||||
|  | ||||
|     async def game_watcher(self, ctx: "BizHawkClientContext") -> None: | ||||
|         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: | ||||
|             return | ||||
|  | ||||
| @@ -125,7 +165,9 @@ class GrinchClient(BizHawkClient): | ||||
|             if not await self.ingame_checker(ctx): | ||||
|                 return | ||||
|  | ||||
|             if not any(task.get_name() == "Grinch EggLink" for task in asyncio.all_tasks()): | ||||
|             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") | ||||
| @@ -138,16 +180,24 @@ class GrinchClient(BizHawkClient): | ||||
|  | ||||
|         except bizhawk.RequestFailedError as ex: | ||||
|             # 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 | ||||
|  | ||||
|         except Exception as genericEx: | ||||
|             # 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) | ||||
|             pass | ||||
|  | ||||
|     async def location_checker(self, ctx: "BizHawkClientContext"): | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         # Update the AP Server to know what locations are not checked yet. | ||||
|         local_locations_checked: list[int] = [] | ||||
|         addr_list_to_read: list[tuple[int, int, str]] = [] | ||||
| @@ -157,11 +207,15 @@ class GrinchClient(BizHawkClient): | ||||
|         for missing_location in local_ap_locations: | ||||
|             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | ||||
|             grinch_loc_ram_data = grinch_locations[grinch_loc_name] | ||||
|             missing_addr_list: list[tuple[int, int, str]] = [(read_addr.ram_address, read_addr.bit_size, "MainRAM") for | ||||
|                                                              read_addr in grinch_loc_ram_data.update_ram_addr] | ||||
|             missing_addr_list: list[tuple[int, int, str]] = [ | ||||
|                 (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] | ||||
|  | ||||
|         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, | ||||
|         # and check to see if that ram address has our expected value. | ||||
| @@ -174,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 | ||||
|             # 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] = [] | ||||
|  | ||||
|             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 | ||||
|                 orig_index: int = addr_list_to_read.index((addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")) | ||||
|                 value_read_from_bizhawk: int = int.from_bytes(returned_bytes[orig_index], "little") | ||||
|                 orig_index: int = addr_list_to_read.index( | ||||
|                     (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: | ||||
|                     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: | ||||
|                     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): | ||||
|                 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) | ||||
|         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: | ||||
|             await self.remove_physical_items(ctx) | ||||
|  | ||||
|         ctx.locations_checked = set(local_locations_checked) | ||||
|  | ||||
|     async def receiving_items_handler(self, ctx: "BizHawkClientContext"): | ||||
| @@ -197,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. | ||||
|         # 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, [( | ||||
|             RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]))[0], "little") | ||||
|         self.last_received_index = int.from_bytes( | ||||
|             ( | ||||
|                 await bizhawk.read( | ||||
|                     ctx.bizhawk_ctx, [(RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")] | ||||
|                 ) | ||||
|             )[0], | ||||
|             "little", | ||||
|         ) | ||||
|  | ||||
|         if len(ctx.items_received) == self.last_received_index: | ||||
|             return | ||||
|  | ||||
|         # Ensures we only get the new items that we want to give the player | ||||
|         new_items_only = ctx.items_received[self.last_received_index:] | ||||
|         new_items_only = ctx.items_received[self.last_received_index :] | ||||
|         ram_addr_dict: dict[int, list[int]] = {} | ||||
|  | ||||
|         for item_received in new_items_only: | ||||
| @@ -211,83 +292,164 @@ class GrinchClient(BizHawkClient): | ||||
|  | ||||
|             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 | ||||
|  | ||||
|                 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: | ||||
|                     current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                         addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | ||||
|                     current_ram_address_value = int.from_bytes( | ||||
|                         ( | ||||
|                             await bizhawk.read( | ||||
|                                 ctx.bizhawk_ctx, | ||||
|                                 [ | ||||
|                                     ( | ||||
|                                         addr_to_update.ram_address, | ||||
|                                         addr_to_update.byte_size, | ||||
|                                         "MainRAM", | ||||
|                                     ) | ||||
|                                 ], | ||||
|                             ) | ||||
|                         )[0], | ||||
|                         "little", | ||||
|                     ) | ||||
|  | ||||
|                 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: | ||||
|                     # 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 = 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: | ||||
|                     current_ram_address_value = addr_to_update.value | ||||
|  | ||||
|                 # 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 | ||||
|  | ||||
|         # Update the latest received item index to ram as well. | ||||
|         ram_addr_dict[RECV_ITEM_ADDR] = [self.last_received_index, RECV_ITEM_BITSIZE] | ||||
|         await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict)) | ||||
|  | ||||
|         await bizhawk.write( | ||||
|             ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict) | ||||
|         ) | ||||
|  | ||||
|     async def goal_checker(self, ctx: "BizHawkClientContext"): | ||||
|         if not ctx.finished_game: | ||||
|             goal_loc = grinch_locations["MC - Sleigh Ride - Neutralizing Santa"] | ||||
|             goal_ram_address = goal_loc.update_ram_addr[0] | ||||
|             current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                 goal_ram_address.ram_address, goal_ram_address.bit_size, "MainRAM")]))[0], "little") | ||||
|             # if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0: | ||||
|             if current_ram_address_value == goal_ram_address.value: | ||||
|             current_ram_address_value = int.from_bytes( | ||||
|                 ( | ||||
|                     await bizhawk.read( | ||||
|                         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 | ||||
|                 await ctx.send_msgs([{ | ||||
|                     "cmd": "StatusUpdate", | ||||
|                     "status": NetUtils.ClientStatus.CLIENT_GOAL, | ||||
|                 }]) | ||||
|                 await ctx.send_msgs( | ||||
|                     [ | ||||
|                         { | ||||
|                             "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 | ||||
|     async def remove_physical_items(self, ctx: "BizHawkClientContext"): | ||||
|         ram_addr_dict: dict[int, list[int]] = {} | ||||
|  | ||||
|         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | ||||
|         items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE | ||||
|         heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570)) | ||||
|         items_to_check: dict[str, GrinchItemData] = { | ||||
|             **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"] | ||||
|         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 | ||||
|         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_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 | ||||
|  | ||||
|             # This assumes we don't have the item so we must set all the data to 0 | ||||
|             for addr_to_update in item_data.update_ram_addr: | ||||
|                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||
|  | ||||
|                 if is_binary: | ||||
|                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||
|                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | ||||
|  | ||||
|                     else: | ||||
|                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | ||||
|                         current_bin_value = int.from_bytes( | ||||
|                             ( | ||||
|                                 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) | ||||
|                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | ||||
|  | ||||
|                 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(self, addr_dict: dict[int, list[int]]) -> list[tuple[int, Sequence[int], str]]: | ||||
|     def convert_dict_to_ram_list( | ||||
|         self, addr_dict: dict[int, list[int]] | ||||
|     ) -> list[tuple[int, Sequence[int], str]]: | ||||
|         addr_list_to_update: list[tuple[int, Sequence[int], str]] = [] | ||||
|  | ||||
|         for (key, val) in addr_dict.items(): | ||||
|             addr_list_to_update.append((key, val[0].to_bytes(val[1], "little"), "MainRAM")) | ||||
|         for key, val in addr_dict.items(): | ||||
|             addr_list_to_update.append( | ||||
|                 (key, val[0].to_bytes(val[1], "little"), "MainRAM") | ||||
|             ) | ||||
|  | ||||
|         return addr_list_to_update | ||||
|  | ||||
| @@ -296,56 +458,93 @@ class GrinchClient(BizHawkClient): | ||||
|         ram_addr_dict: dict[int, list[int]] = {} | ||||
|  | ||||
|         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_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 | ||||
|  | ||||
|             # 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: | ||||
|                 is_binary = True if not addr_to_update.binary_bit_pos is None else False | ||||
|  | ||||
|                 if is_binary: | ||||
|                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||
|                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | ||||
|  | ||||
|                     else: | ||||
|                         current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|                             addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") | ||||
|                         current_bin_value = int.from_bytes( | ||||
|                             ( | ||||
|                                 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: | ||||
|                         current_bin_value |= (1 << addr_to_update.binary_bit_pos) | ||||
|                         current_bin_value |= 1 << addr_to_update.binary_bit_pos | ||||
|  | ||||
|                     else: | ||||
|                         current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) | ||||
|  | ||||
|                     ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1] | ||||
|  | ||||
|                 else: | ||||
|                     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] | ||||
|                     else: | ||||
|                         ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size] | ||||
|                         ram_addr_dict[addr_to_update.ram_address] = [ | ||||
|                             addr_to_update.value, | ||||
|                             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"): | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|             0x010000, 1, "MainRAM")]))[0], "little") | ||||
|         initial_cutscene_checker = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|             0x010094, 1, "MainRAM")]))[0], "little") | ||||
|         ingame_map_id = int.from_bytes( | ||||
|             (await bizhawk.read(ctx.bizhawk_ctx, [(0x010000, 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: | ||||
|             self.ingame_log = False | ||||
|             return False | ||||
|  | ||||
|         #If grinch has changed maps | ||||
|         # If grinch has changed maps | ||||
|         if not ingame_map_id == self.last_map_location: | ||||
|             # If the last "map" we were on was a menu or a publisher logo | ||||
|             if self.last_map_location in MENU_MAP_IDS: | ||||
|                 # Reset our demo mode checker just in case the game is in demo mode. | ||||
|                 self.demo_mode_buffer = 0 | ||||
|                 self.ingame_log = False | ||||
|  | ||||
|             if initial_cutscene_checker != 1: | ||||
|                 return False | ||||
|  | ||||
| @@ -358,27 +557,42 @@ class GrinchClient(BizHawkClient): | ||||
|             self.demo_mode_buffer += 1 | ||||
|             return False | ||||
|  | ||||
|         demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( | ||||
|             0x01008A, 1, "MainRAM")]))[0], "little") | ||||
|         demo_mode = int.from_bytes( | ||||
|             (await bizhawk.read(ctx.bizhawk_ctx, [(0x01008A, 1, "MainRAM")]))[0], | ||||
|             "little", | ||||
|         ) | ||||
|  | ||||
|         if demo_mode == 1: | ||||
|             return False | ||||
|  | ||||
|         if not self.ingame_log: | ||||
|             logger.info("You can now start sending locations from the Grinch!") | ||||
|             self.ingame_log = True | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     async def option_handler(self, ctx: "BizHawkClientContext"): | ||||
|         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"): | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         while self.send_ring_link and ctx.slot: | ||||
|  | ||||
|             try: | ||||
|                 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: | ||||
|                     msg = { | ||||
| @@ -386,29 +600,55 @@ class GrinchClient(BizHawkClient): | ||||
|                         "data": { | ||||
|                             "time": time.time(), | ||||
|                             "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]) | ||||
|                     self.previous_egg_count = current_egg_count | ||||
|                     # logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.") | ||||
|  | ||||
|                 await asyncio.sleep(0.1) | ||||
|  | ||||
|             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 | ||||
|  | ||||
|         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"): | ||||
|         from CommonClient import logger | ||||
|  | ||||
|         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) | ||||
|         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 | ||||
|         # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.") | ||||
|  | ||||
| @@ -419,18 +659,27 @@ class GrinchClient(BizHawkClient): | ||||
|             uid += ord(char) | ||||
|         return uid | ||||
|  | ||||
|  | ||||
| def _cmd_ringlink(self): | ||||
|     """Toggle ringling from client. Overrides default setting.""" | ||||
|     if not self.ctx.slot: | ||||
|         return | ||||
|     Utils.async_start(_update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), name="Update RingLink") | ||||
|  | ||||
|     Utils.async_start( | ||||
|         _update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), | ||||
|         name="Update RingLink", | ||||
|     ) | ||||
|  | ||||
|  | ||||
| async def _update_ring_link(ctx: "BizHawkClientContext", ring_link: bool): | ||||
|     """Helper function to set Ring Link connection tag on/off and update the connection if already connected.""" | ||||
|     old_tags = copy.deepcopy(ctx.tags) | ||||
|  | ||||
|     if ring_link: | ||||
|         ctx.tags.add("RingLink") | ||||
|  | ||||
|     else: | ||||
|         ctx.tags -= {"RingLink"} | ||||
|  | ||||
|     if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed: | ||||
|         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) | ||||
|         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) | ||||
|   | ||||
| @@ -1,32 +1,41 @@ | ||||
| from typing import NamedTuple, Optional | ||||
|  | ||||
| from .RamHandler import GrinchRamData | ||||
| from .RamHandler import GrinchRamData, UpdateMethod | ||||
| 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): | ||||
|     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] | ||||
|     classification: IC | ||||
|     update_ram_addr: list[GrinchRamData] | ||||
|  | ||||
|  | ||||
| class GrinchItem(Item): | ||||
|     game: str = "The Grinch" | ||||
|  | ||||
|     #Tells server what item id it is | ||||
|     # Tells server what item id it is | ||||
|     @staticmethod | ||||
|     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 | ||||
|         return base_id + id if id is not None else None | ||||
|  | ||||
|     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.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]]: | ||||
|     categories: dict[str, set[str]] = {} | ||||
|  | ||||
| @@ -36,123 +45,306 @@ def get_item_names_per_category() -> dict[str, set[str]]: | ||||
|  | ||||
|     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 | ||||
| #All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit. | ||||
| class grinch_items: | ||||
|     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] = { | ||||
|     "Binoculars": GrinchItemData(["Gadgets"], 100, IC.useful, | ||||
|         [GrinchRamData(0x0102B6, value=0x40), GrinchRamData(0x0102B7, value=0x41), | ||||
|         GrinchRamData(0x0102B8, value=0x44), GrinchRamData(0x0102B9, value=0x45), | ||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=0) | ||||
|          ]), | ||||
|     "Rotten Egg Launcher": GrinchItemData(["Gadgets"], 101, IC.progression, | ||||
|         [GrinchRamData(0x0102BA, value=0x40), GrinchRamData(0x0102BB, value=0x41), | ||||
|         GrinchRamData(0x0102BC, value=0x44), GrinchRamData(0x0102BD, value=0x45), | ||||
|         # GrinchRamData(0x0100BC, binary_bit_pos=1) | ||||
|          ]), | ||||
|     "Rocket Spring": GrinchItemData(["Gadgets"], 102, IC.progression, | ||||
|         [GrinchRamData(0x0102BE, value=0x40), GrinchRamData(0x0102BF, value=0x41), | ||||
|         GrinchRamData(0x0102C0, value=0x42), GrinchRamData(0x0102C1, value=0x44), | ||||
|         GrinchRamData(0x0102C2, value=0x45), GrinchRamData(0x0102C3, value=0x46), | ||||
|         GrinchRamData(0x0102C4, value=0x48), GrinchRamData(0x0102C5, value=0x49), | ||||
|         GrinchRamData(0x0102C6, value=0x4A), | ||||
|          # GrinchRamData(0x0100BC, binary_bit_pos=2) | ||||
|          ]), | ||||
|     "Slime Shooter": GrinchItemData(["Gadgets", "Slime Gun"], 103, IC.progression, | ||||
|         [GrinchRamData(0x0102C7, value=0x40), GrinchRamData(0x0102C8, value=0x41), | ||||
|         GrinchRamData(0x0102C9, value=0x42), GrinchRamData(0x0102CA, value=0x44), | ||||
|         GrinchRamData(0x0102CB, value=0x45), GrinchRamData(0x0102CC, value=0x46), | ||||
|         GrinchRamData(0x0102CD, value=0x48), GrinchRamData(0x0102CE, value=0x49), | ||||
|         GrinchRamData(0x0102CF, value=0x4A), | ||||
|          # GrinchRamData(0x0100BC, binary_bit_pos=3) | ||||
|          ]), | ||||
|     "Octopus Climbing Device": GrinchItemData(["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) | ||||
|          ]), | ||||
|     "Marine Mobile": GrinchItemData(["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 Copter": GrinchItemData(["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) | ||||
|     ]) | ||||
|     grinch_items.gadgets.BINOCULARS: GrinchItemData( | ||||
|         [grinch_categories.GADGETS], | ||||
|         100, | ||||
|         IC.useful, | ||||
|         [ | ||||
|             GrinchRamData(0x0102B6, value=0x40), | ||||
|             GrinchRamData(0x0102B7, value=0x41), | ||||
|             GrinchRamData(0x0102B8, value=0x44), | ||||
|             GrinchRamData(0x0102B9, value=0x45), | ||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=0) | ||||
|         ], | ||||
|     ), | ||||
|     grinch_items.gadgets.ROCKET_EGG_LAUNCHER: GrinchItemData( | ||||
|         [grinch_categories.GADGETS], | ||||
|         101, | ||||
|         IC.progression, | ||||
|         [ | ||||
|             GrinchRamData(0x0102BA, value=0x40), | ||||
|             GrinchRamData(0x0102BB, value=0x41), | ||||
|             GrinchRamData(0x0102BC, value=0x44), | ||||
|             GrinchRamData(0x0102BD, value=0x45), | ||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=1) | ||||
|         ], | ||||
|     ), | ||||
|     grinch_items.gadgets.ROCKET_SPRING: GrinchItemData( | ||||
|         [grinch_categories.GADGETS], | ||||
|         102, | ||||
|         IC.progression, | ||||
|         [ | ||||
|             GrinchRamData(0x0102BE, value=0x40), | ||||
|             GrinchRamData(0x0102BF, value=0x41), | ||||
|             GrinchRamData(0x0102C0, value=0x42), | ||||
|             GrinchRamData(0x0102C1, value=0x44), | ||||
|             GrinchRamData(0x0102C2, value=0x45), | ||||
|             GrinchRamData(0x0102C3, value=0x46), | ||||
|             GrinchRamData(0x0102C4, value=0x48), | ||||
|             GrinchRamData(0x0102C5, value=0x49), | ||||
|             GrinchRamData(0x0102C6, value=0x4A), | ||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=2) | ||||
|         ], | ||||
|     ), | ||||
|     grinch_items.gadgets.SLIME_SHOOTER: GrinchItemData( | ||||
|         [ | ||||
|             grinch_categories.GADGETS, | ||||
|             "Slime Gun",  # For canon --MarioSpore | ||||
|         ], | ||||
|         103, | ||||
|         IC.progression, | ||||
|         [ | ||||
|             GrinchRamData(0x0102C7, value=0x40), | ||||
|             GrinchRamData(0x0102C8, value=0x41), | ||||
|             GrinchRamData(0x0102C9, value=0x42), | ||||
|             GrinchRamData(0x0102CA, value=0x44), | ||||
|             GrinchRamData(0x0102CB, value=0x45), | ||||
|             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] = { | ||||
|     "Who Cloak": GrinchItemData(["Mission Specific Items", "Useful Items"], 200, IC.progression, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=0)]), | ||||
|     "Painting Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 201, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=1)]), | ||||
|     "Scissors": GrinchItemData(["Mission Specific Items", "Useful Items"], 202, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)]), | ||||
|     "Glue Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 203, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=4)]), | ||||
|     "Cable Car Access Card": GrinchItemData(["Mission Specific Items", "Useful Items"], 204, IC.progression, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=5)]), | ||||
|     "Drill": GrinchItemData(["Mission Specific Items", "Useful Items"], 205, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=2)]), | ||||
|     "Rope": GrinchItemData(["Mission Specific Items", "Useful Items"], 206, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=1)]), | ||||
|     "Hook": GrinchItemData(["Mission Specific Items", "Useful Items"], 207, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=0)]), | ||||
|     "Sculpting Tools": GrinchItemData(["Mission Specific Items", "Useful Items"], 208, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=2)]), | ||||
|     "Hammer": GrinchItemData(["Mission Specific Items", "Useful Items"], 209, IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=3)]), | ||||
|     "Scout Clothes": GrinchItemData(["Mission Specific Items", "Useful Items"], 210, IC.progression, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=7)]) | ||||
|     grinch_items.level_items.WV_WHO_CLOAK: GrinchItemData( | ||||
|         [ | ||||
|             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||
|             grinch_categories.USEFUL_ITEMS, | ||||
|         ], | ||||
|         200, | ||||
|         IC.progression, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=0)], | ||||
|     ), | ||||
|     grinch_items.level_items.WV_PAINT_BUCKET: GrinchItemData( | ||||
|         [ | ||||
|             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||
|             grinch_categories.USEFUL_ITEMS, | ||||
|         ], | ||||
|         201, | ||||
|         IC.progression_deprioritized, | ||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=1)], | ||||
|     ), | ||||
|     grinch_items.level_items.WD_SCISSORS: GrinchItemData( | ||||
|         [ | ||||
|             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||
|             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] = { | ||||
| #     "Exhaust Pipes": GrinchItemData(["Sleigh Parts"], 300, IC.progression_skip_balancing, | ||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=2)]), | ||||
| @@ -166,16 +358,32 @@ MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=6)]) | ||||
| # } | ||||
|  | ||||
| #Access Keys | ||||
| # Access Keys | ||||
| KEYS_TABLE: dict[str, GrinchItemData] = { | ||||
|     "Whoville Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 400, IC.progression, | ||||
|         [GrinchRamData(0x010200, binary_bit_pos=1)]), | ||||
|     "Who Forest Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 401, IC.progression, | ||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=2)]), | ||||
|     "Who Dump Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 402, IC.progression, | ||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=3)]), | ||||
|     "Who Lake Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 403, IC.progression, | ||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=4)]), | ||||
|     grinch_items.keys.WHOVILLE: GrinchItemData( | ||||
|         [grinch_categories.VACUUM_TUBES], | ||||
|         400, | ||||
|         IC.progression, | ||||
|         [GrinchRamData(0x010200, binary_bit_pos=1)], | ||||
|     ), | ||||
|     grinch_items.keys.WHO_FOREST: GrinchItemData( | ||||
|         [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, | ||||
|     #     [GrinchRamData()]), | ||||
|     # "Spin N' Win Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 405, IC.progression, | ||||
| @@ -188,71 +396,196 @@ KEYS_TABLE: dict[str, GrinchItemData] = { | ||||
|     #     [GrinchRamData()]), | ||||
|     # "Bike Race Access": GrinchItemData(["Supadow Door Unlocks", 409, IC.progression, | ||||
|     #     [GrinchRamData()]) | ||||
|     "Sleigh Room Key": GrinchItemData(["Sleigh Room"], 410, IC.progression, | ||||
|         [GrinchRamData(0x010200, binary_bit_pos=6), GrinchRamData(0x0100AA, binary_bit_pos=5)]) | ||||
|     grinch_items.keys.SLEIGH_ROOM_KEY: GrinchItemData( | ||||
|         [ | ||||
|             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] = { | ||||
|     # 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, | ||||
|     #     [GrinchRamData(0x0E8FDC, value=120)]), | ||||
|     "5 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 502, IC.filler, | ||||
|         [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, | ||||
|         [GrinchRamData(0x010058, value=10, update_existing_value=True, max_count=200, bit_size=2)]), | ||||
|     "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)]) | ||||
|     "5 Rotten Eggs": GrinchItemData( | ||||
|         [ | ||||
|             grinch_categories.ROTTEN_EGG_BUNDLES, | ||||
|             grinch_categories.FILLER, | ||||
|         ], | ||||
|         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] = { | ||||
|     "Heart of Stone": GrinchItemData(["Health Items"], 501, IC.useful, | ||||
|         [GrinchRamData(0x0100ED, value=1, update_existing_value=True, max_count=4)]) | ||||
| USEFUL_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||
|     grinch_items.useful_items.HEART_OF_STONE: GrinchItemData( | ||||
|         [ | ||||
|             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] = { | ||||
| # alias to Ice Trap for traplink | ||||
|     # alias to Ice Trap for traplink | ||||
|     # "Freeze Trap": GrinchItemData(["Traps"], 600, IC.trap, [GrinchRamData()]), | ||||
|     # "Bee Trap": GrinchItemData(["Traps"], 601, 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()]), | ||||
| # This item may not function properly if you receive it during a loading screen or in Mount Crumpit | ||||
| # alias to Exhaustion Trap | ||||
| #     "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_existing_value=True)]), | ||||
|     "Depletion Trap": GrinchItemData(["Traps"], 605, IC.trap, [GrinchRamData(0x010058, value=0, bit_size=2)]), | ||||
|     "Dump it to Crumpit": GrinchItemData(["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 | ||||
|     # This item may not function properly if you receive it during a loading screen or in Mount Crumpit | ||||
|     # alias to Exhaustion Trap | ||||
|     #     "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_method=UpdateMethod.ADD)]), | ||||
|     grinch_items.trap_items.DEPLETION_TRAP: GrinchItemData( | ||||
|         [grinch_categories.TRAPS], | ||||
|         605, | ||||
|         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()]), | ||||
| #alias to Home Trap for traplink | ||||
|     "Who sent me back?": GrinchItemData(["Traps"], 608, IC.trap, [GrinchRamData(0x08FB94, value=1)]), | ||||
|     # alias to Home Trap for traplink | ||||
|     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()]), | ||||
|     # "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()]) | ||||
|     # "Disable Jump Trap": GrinchItemData(["Traps"], 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)]) | ||||
| } | ||||
|  | ||||
| #Movesets | ||||
| # MOVES_TABLE: dict[str, GrinchItemData] = { | ||||
| #     "Bad Breath": GrinchItemData(["Movesets"], 700, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=1)]), | ||||
| #     "Pancake": GrinchItemData(["Movesets"], 701, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=2)]), | ||||
| #     "Push & Pull": GrinchItemData(["Movesets"], 702, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=3)]), | ||||
| #     "Max": GrinchItemData(["Movesets"], 703, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=4)]), | ||||
| #     "Tip Toe": GrinchItemData(["Movesets"], 704, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=5)]) | ||||
| # } | ||||
| #Double star combines all dictionaries from each individual list together | ||||
| # Movesets | ||||
| MOVES_TABLE: dict[str, GrinchItemData] = { | ||||
|     grinch_items.moves.BAD_BREATH: GrinchItemData( | ||||
|         [grinch_categories.MOVES], | ||||
|         700, | ||||
|         IC.progression, | ||||
|         [ | ||||
|             GrinchRamData(0x0100BB, binary_bit_pos=1), | ||||
|         ], | ||||
|     ), | ||||
|     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] = { | ||||
|     **GADGETS_TABLE, | ||||
|     **MISSION_ITEMS_TABLE, | ||||
|     **KEYS_TABLE, | ||||
|     **MISC_ITEMS_TABLE, | ||||
|     **TRAPS_TABLE, | ||||
|     **USEFUL_IC_TABLE, | ||||
|     **USEFUL_ITEMS_TABLE, | ||||
|     # **SLEIGH_PARTS_TABLE, | ||||
|     # **MOVES_TABLE, | ||||
|     **MOVES_TABLE, | ||||
| } | ||||
|  | ||||
| # Psuedocoding traplink table | ||||
| @@ -268,8 +601,9 @@ ALL_ITEMS_TABLE: dict[str, GrinchItemData] = { | ||||
| # ELEC_TRAP_EQUIV = [] | ||||
| # DEPL_TRAP_EQUIV = ["Dry Trap"] | ||||
|  | ||||
|  | ||||
| def grinch_items_to_id() -> dict[str, int]: | ||||
|     item_mappings: dict[str, int] = {} | ||||
|     for ItemName, ItemData in ALL_ITEMS_TABLE.items(): | ||||
|         item_mappings.update({ItemName: GrinchItem.get_apid(ItemData.id)}) | ||||
|     return item_mappings | ||||
|     return item_mappings | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,13 +1,26 @@ | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, OptionList, \ | ||||
|     PerGameCommonOptions, OptionSet | ||||
| from Options import ( | ||||
|     FreeText, | ||||
|     NumericOption, | ||||
|     Toggle, | ||||
|     DefaultOnToggle, | ||||
|     Choice, | ||||
|     TextChoice, | ||||
|     Range, | ||||
|     NamedRange, | ||||
|     OptionList, | ||||
|     PerGameCommonOptions, | ||||
|     OptionSet, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class StartingArea(Choice): | ||||
|     """ | ||||
|     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. | ||||
|     """ | ||||
|  | ||||
|     option_whoville = 0 | ||||
|     option_who_forest = 1 | ||||
|     option_who_dump = 2 | ||||
| @@ -15,14 +28,17 @@ class StartingArea(Choice): | ||||
|     default = 0 | ||||
|     display_name = "Starting Area" | ||||
|  | ||||
| class ProgressiveVacuum(Toggle):#DefaultOnToggle | ||||
|  | ||||
| class ProgressiveVacuum(Toggle):  # DefaultOnToggle | ||||
|     """ | ||||
|     Determines whether you get access to main areas progressively [NOT IMPLEMENTED] | ||||
|  | ||||
|     Enabled: Whoville > Who Forest > Who Dump > Who Lake | ||||
|     """ | ||||
|  | ||||
|     display_name = "Progressive Vacuum Tubes" | ||||
|  | ||||
|  | ||||
| class Missionsanity(Choice): | ||||
|     """ | ||||
|     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. | ||||
|     Both: Both individual tasks and mission completion are randomized. | ||||
|     """ | ||||
|  | ||||
|     display_name = "Mission Locations" | ||||
|     option_none = 0 | ||||
|     option_completion = 1 | ||||
| @@ -39,6 +56,7 @@ class Missionsanity(Choice): | ||||
|     option_both = 3 | ||||
|     default = 1 | ||||
|  | ||||
|  | ||||
| class ExcludeEnvironments(OptionSet): | ||||
|     """ | ||||
|     Allows entire environments to be an excluded location to ensure you are not logically required to enter the environment along | ||||
| @@ -51,74 +69,80 @@ class ExcludeEnvironments(OptionSet): | ||||
|                   "Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut", | ||||
|                   "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] | ||||
|     """ | ||||
|  | ||||
|     display_name = "Progressive Gadgets" | ||||
|  | ||||
|  | ||||
| class Supadow(Toggle): | ||||
|     """Enables completing minigames through the Supadows in Mount Crumpit as checks. NOT IMPLEMENTED]""" | ||||
|  | ||||
|     display_name = "Supadow Minigames" | ||||
|  | ||||
|  | ||||
| class Gifts(Range): | ||||
|     """ | ||||
|     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] | ||||
|     """ | ||||
|  | ||||
|     display_name = "Gifts Squashed per Check" | ||||
|     range_start = 0 | ||||
|     range_end = 300 | ||||
|     default = 0 | ||||
|  | ||||
| class GadgetRando(OptionSet): | ||||
|     """ | ||||
|     Randomizes Grinch's gadgets along with randomizing Binoculars into the pool. [NOT IMPLEMENTED] | ||||
|     """ | ||||
|     display_name = "Gadgets Randomized" | ||||
|     default = [ | ||||
|         "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] | ||||
| class Moverando(Toggle): | ||||
|     """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" | ||||
|     default = [ | ||||
|         "Pancake", | ||||
|         "Seize", | ||||
|         "Max", | ||||
|         "Bad Breath", | ||||
|         "Sneak" | ||||
|     ] | ||||
|  | ||||
|  | ||||
| class UnlimitedEggs(Toggle): | ||||
|     """Determine whether or not you run out of rotten eggs when you utilize your gadgets.""" | ||||
|  | ||||
|     display_name = "Unlimited Rotten Eggs" | ||||
|  | ||||
|  | ||||
| class RingLinkOption(Toggle): | ||||
|     """Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled.""" | ||||
|  | ||||
|     display_name = "Ring Link" | ||||
|  | ||||
|  | ||||
| class TrapLinkOption(Toggle): | ||||
|     """If a trap is sent from Grinch, traps that are compatible with other games are triggered as well. [NOT IMPLEMENTED]""" | ||||
|  | ||||
|     display_name = "Trap Link" | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin | ||||
| class GrinchOptions(PerGameCommonOptions):  # DeathLinkMixin | ||||
|     starting_area: StartingArea | ||||
|     progressive_vacuum: ProgressiveVacuum | ||||
|     missionsanity: Missionsanity | ||||
| @@ -126,7 +150,6 @@ class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin | ||||
|     progressive_gadget: ProgressiveGadget | ||||
|     supadow_minigames: Supadow | ||||
|     giftsanity: Gifts | ||||
|     gadget_rando: GadgetRando | ||||
|     move_rando: Moverando | ||||
|     unlimited_eggs: UnlimitedEggs | ||||
|     ring_link: RingLinkOption | ||||
|   | ||||
| @@ -1,12 +1,66 @@ | ||||
| from enum import STRICT, IntEnum | ||||
| from typing import NamedTuple, Optional | ||||
|  | ||||
|  | ||||
| class UpdateMethod(IntEnum, boundary=STRICT): | ||||
|     SET = 1 | ||||
|     ADD = 2 | ||||
|     SUBTRACT = 3 | ||||
|     FREEZE = 4 | ||||
|  | ||||
|  | ||||
| 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 | ||||
|     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 | ||||
|     # Hex uses 0x00, unsigned are base whole numbers | ||||
|     binary_bit_pos: Optional[int] = None | ||||
|     bit_size: int = 1 | ||||
|     update_existing_value: bool = False | ||||
|     max_count: int = 0 | ||||
|     byte_size: int = 1 | ||||
|     update_method: UpdateMethod = UpdateMethod.SET | ||||
|     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", | ||||
|     "Who Forest", | ||||
|     "Who Dump", | ||||
|     "Who Lake" | ||||
|     "Who Lake", | ||||
| ] | ||||
|  | ||||
| subareas_list = [ | ||||
| @@ -29,29 +29,35 @@ subareas_list = [ | ||||
|     "Scout's Hut", | ||||
|     "North Shore", | ||||
|     "Mayor's Villa", | ||||
|     "Sleigh Room" | ||||
|     "Sleigh Room", | ||||
| ] | ||||
|  | ||||
| supadow_list = [ | ||||
|     "Spin N' Win Supadow", | ||||
|     "Dankamania Supadow", | ||||
|     "The Copter Race Contest Supadow", | ||||
|     "Bike Race" | ||||
|     "Bike Race", | ||||
| ] | ||||
|  | ||||
|  | ||||
| def create_regions(world: "GrinchWorld"): | ||||
|     for mainarea in mainareas_list: | ||||
|         #Each area in mainarea, create a region for the given player | ||||
|         world.multiworld.regions.append(Region(mainarea, world.player, world.multiworld)) | ||||
|         # Each area in mainarea, create a region for the given player | ||||
|         world.multiworld.regions.append( | ||||
|             Region(mainarea, world.player, world.multiworld) | ||||
|         ) | ||||
|     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)) | ||||
|     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)) | ||||
|  | ||||
|  | ||||
| # 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) | ||||
|     connected_region = world.get_region(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) | ||||
|     for access_rule in rule_list: | ||||
|         for region_entrance in current_region.entrances: | ||||
|             if region_entrance.connected_region.name == current_region_name and \ | ||||
|                 region_entrance.parent_region.name == connected_region_name: | ||||
|             if ( | ||||
|                 region_entrance.connected_region.name == current_region_name | ||||
|                 and region_entrance.parent_region.name == connected_region_name | ||||
|             ): | ||||
|                 if rule_list.index(access_rule) == 0: | ||||
|                     add_rule(region_entrance, access_rule) | ||||
|                 else: | ||||
|                     add_rule(region_entrance, access_rule, combine="or") | ||||
|         for region_entrance in connected_region.entrances: | ||||
|             if region_entrance.connected_region.name == connected_region_name and \ | ||||
|                     region_entrance.parent_region.name == current_region_name: | ||||
|             if ( | ||||
|                 region_entrance.connected_region.name == connected_region_name | ||||
|                 and region_entrance.parent_region.name == current_region_name | ||||
|             ): | ||||
|                 if rule_list.index(access_rule) == 0: | ||||
|                     add_rule(region_entrance, access_rule) | ||||
|                 else: | ||||
|                     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"): | ||||
|     grinchconnect(world, "Mount Crumpit", "Whoville") | ||||
|     grinchconnect(world, "Mount Crumpit", "Who Forest") | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										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. | ||||
| - 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. | ||||
| - [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. | ||||
| - 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`. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user