Compare commits
	
		
			82 Commits
		
	
	
		
			v1.0.3
			...
			artamis-mo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0a8df25f6 | |||
|   | 6f552949fa | ||
|   | ca1eb82ce1 | ||
|   | 414a155323 | ||
|   | b9d8d9174f | ||
|   | 8e58f9662e | ||
|   | 6f4597398f | ||
|   | 5e71874446 | ||
|   | 1870dd24ba | ||
|   | f70b6c4c9c | ||
|   | 79d4d5b10b | ||
|   | 7fea34adc3 | ||
|   | a3f9e6cbc9 | ||
|   | bccc83f864 | ||
|   | 6409721841 | ||
|   | d3a7b014bd | ||
|   | 3ec8631203 | ||
|   | 2081912a39 | ||
|   | 67bbde2556 | ||
|   | 0503c2ead3 | ||
|   | 7a642cc1a9 | ||
|   | eb8d44e975 | ||
|   | 1f35f5fa93 | ||
|   | 6bc819f4bc | ||
|   | bb0c5f5b9a | ||
|   | 0e397c7079 | ||
|   | 2572a25089 | ||
|   | dcdf168618 | ||
|   | 8a8136a267 | ||
|   | 5f158497f9 | ||
|   | bc74e67e07 | ||
|   | dbc592dad0 | ||
|   | afe1345e34 | ||
|   | 410df2a948 | ||
|   | 7c6eada7b2 | ||
|   | 8e1217d1a5 | ||
|   | 5615277705 | ||
|   | 2de0f9d766 | ||
|   | 4da88cf794 | ||
|   | e2cc1b5de7 | ||
|   | 103a6f79d1 | ||
|   | 17861c1050 | ||
|   | a63c33a711 | ||
|   | c84ef117c8 | ||
|   | 834096f282 | ||
|   | 0a31d96ee4 | ||
|   | 3edb733dcb | ||
|   | 9c01cc31e0 | ||
|   | ea8262855e | ||
|   | 98ea11887e | ||
|   | 18aefcd3f2 | ||
|   | 3a13332533 | ||
|   | c279ef7bc6 | ||
|   | 55ef0cc8c9 | ||
|   | babc4f441c | ||
|   | 92d932da55 | ||
|   | b1b65a3adf | ||
|   | d6f5e87ccf | ||
|   | 079239ea70 | ||
|   | 93bac29e8c | ||
|   | c9fea29d92 | ||
|   | e17895902e | ||
|   | 1596550111 | ||
|   | c888e17845 | ||
|   | 3dee611b51 | ||
|   | d6c7a04316 | ||
|   | 900c8a519a | ||
|   | 43acc9f003 | ||
|   | 96eb8fcd9a | ||
|   | d4bd682ac9 | ||
|   | 61d4783f61 | ||
|   | 00fff466ff | ||
|   | d8483bef6e | ||
|   | 56a198fcfd | ||
|   | 4e362dc722 | ||
|   | cfcfc9ecfd | ||
|   | a3a415adfd | ||
|   | 14c95aa85b | ||
|   | 8d941dad6f | ||
|   | 8628f6637a | ||
|   | 17b7914c35 | ||
|   | b8dfd5ce4c | 
| @@ -1,10 +1,18 @@ | |||||||
| import time | import time | ||||||
| from typing import TYPE_CHECKING | from typing import TYPE_CHECKING, Sequence | ||||||
| import asyncio | import asyncio | ||||||
| import NetUtils | import NetUtils | ||||||
| import copy | import copy | ||||||
|  | import uuid | ||||||
|  | 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 | ||||||
|  |  | ||||||
| @@ -24,70 +32,131 @@ MAX_DEMO_MODE_CHECK = 30 | |||||||
| # List of Menu Map IDs | # List of Menu Map IDs | ||||||
| MENU_MAP_IDS: list[int] = [0x00, 0x02, 0x35, 0x36, 0x37] | MENU_MAP_IDS: list[int] = [0x00, 0x02, 0x35, 0x36, 0x37] | ||||||
|  |  | ||||||
|  | MAX_EGGS: int = 200 | ||||||
|  | EGG_COUNT_ADDR: int = 0x010058 | ||||||
|  | EGG_ADDR_BYTESIZE: int = 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrinchClient(BizHawkClient): | class GrinchClient(BizHawkClient): | ||||||
|     game = "The Grinch" |     game = "The Grinch" | ||||||
|     system = "PSX" |     system = "PSX" | ||||||
|     patch_suffix = ".apgrinch" |     patch_suffix = ".apgrinch" | ||||||
|     items_handling = 0b111 |     items_handling = 0b111 | ||||||
|     demo_mode_buffer = 0 |     demo_mode_buffer: int = 0 | ||||||
|     last_map_location = -1 |     last_map_location: int = -1 | ||||||
|     ingame_log = False |     ingame_log: bool = False | ||||||
|  |     previous_egg_count: int = 0 | ||||||
|  |     send_ring_link: bool = False | ||||||
|  |     unique_client_id: int = 0 | ||||||
|  |     ring_link_enabled = False | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|         self.last_received_index = 0 |         self.last_received_index = 0 | ||||||
|         self.loading_bios_msg = False |         self.loading_bios_msg = False | ||||||
|         self.loc_unlimited_eggs = False |         self.loc_unlimited_eggs = False | ||||||
|  |         self.unique_client_id = 0 | ||||||
|  |  | ||||||
|     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 | ||||||
|  |  | ||||||
|         except Exception: |         except Exception: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         ctx.game = self.game |         ctx.game = self.game | ||||||
|         ctx.items_handling = self.items_handling |         ctx.items_handling = self.items_handling | ||||||
|         ctx.want_slot_data = True |         ctx.want_slot_data = True | ||||||
|         ctx.watcher_timeout = 0.25 |         ctx.watcher_timeout = 0.125 | ||||||
|         self.loading_bios_msg = False |         self.loading_bios_msg = False | ||||||
|  |  | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     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"]) | ||||||
|                 logger.info("You are now connected to the client. "+ |                 self.unique_client_id = self._get_uuid() | ||||||
|                     "There may be a slight delay to check you are not in demo mode before locations start to send.") |                 logger.info( | ||||||
|                 # tags = args.get("tags", []) |                     "You are now connected to the client. " | ||||||
|                 # if "RingLink" in tags: |                     + "There may be a slight delay to check you are not in demo mode before locations start to send." | ||||||
|                 #     ring_link_input(self, args["data"]) |                 ) | ||||||
|  |  | ||||||
|  |                 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"} | ||||||
|  |  | ||||||
|  |                 if tags != ctx.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" | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|     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 | ||||||
| @@ -96,30 +165,61 @@ 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) | ||||||
|             await self.option_handler(ctx) |             await self.option_handler(ctx) | ||||||
|             await self.constant_address_update(ctx) |             await self.constant_address_update(ctx) | ||||||
|             # await self.ring_link_input(args["args"]) |  | ||||||
|  |  | ||||||
|         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]] = [] | ||||||
|         local_ap_locations: set[int] = copy.deepcopy(ctx.missing_locations) |         local_ap_locations: set[int] = copy.deepcopy(ctx.missing_locations) | ||||||
|  |  | ||||||
|  |         # Loop through the first time of everything left to create the list of RAM addresses to read / monitor. | ||||||
|  |         for missing_location in local_ap_locations: | ||||||
|  |             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | ||||||
|  |             grinch_loc_ram_data = grinch_locations[grinch_loc_name] | ||||||
|  |             missing_addr_list: list[tuple[int, int, str]] = [ | ||||||
|  |                 (read_addr.ram_address, read_addr.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 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # 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. | ||||||
|         for missing_location in local_ap_locations: |         for missing_location in local_ap_locations: | ||||||
|             # local_location = ctx.location_names.lookup_in_game(missing_location) |  | ||||||
|             # Missing location is the AP ID & we need to convert it back to a location name within our game. |             # Missing location is the AP ID & we need to convert it back to a location name within our game. | ||||||
|             # Using the location name, we can then get the Grinch ram data from there. |             # Using the location name, we can then get the Grinch ram data from there. | ||||||
|             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) |             grinch_loc_name = ctx.location_names.lookup_in_game(missing_location) | ||||||
| @@ -128,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 | ||||||
|                 current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                 orig_index: int = addr_list_to_read.index( | ||||||
|                     addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "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((current_ram_address_value & (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 == current_ram_address_value) |                     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"): | ||||||
| @@ -151,13 +270,21 @@ 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]] = {} | ||||||
|  |  | ||||||
|         for item_received in new_items_only: |         for item_received in new_items_only: | ||||||
|             local_item = ctx.item_names.lookup_in_game(item_received.item) |             local_item = ctx.item_names.lookup_in_game(item_received.item) | ||||||
| @@ -165,100 +292,249 @@ 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 | ||||||
|                 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") |                 if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|  |                     current_ram_address_value = ram_addr_dict[ | ||||||
|  |                         addr_to_update.ram_address | ||||||
|  |                     ][0] | ||||||
|  |                 else: | ||||||
|  |                     current_ram_address_value = int.from_bytes( | ||||||
|  |                         ( | ||||||
|  |                             await bizhawk.read( | ||||||
|  |                                 ctx.bizhawk_ctx, | ||||||
|  |                                 [ | ||||||
|  |                                     ( | ||||||
|  |                                         addr_to_update.ram_address, | ||||||
|  |                                         addr_to_update.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 | ||||||
|                 await self.update_and_validate_address(ctx, addr_to_update.ram_address, current_ram_address_value, addr_to_update.bit_size) |                 ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|                 # await bizhawk.write(ctx.bizhawk_ctx, [(addr_to_update.ram_address, |                     current_ram_address_value, | ||||||
|                 #     current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"), "MainRAM")]) |                     addr_to_update.byte_size, | ||||||
|  |                 ] | ||||||
|  |  | ||||||
|             self.last_received_index += 1 |             self.last_received_index += 1 | ||||||
|             await self.update_and_validate_address(ctx, RECV_ITEM_ADDR, self.last_received_index, RECV_ITEM_BITSIZE) |  | ||||||
|  |         # 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) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     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["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") |                 ( | ||||||
|  |                     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 & (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", |                             "cmd": "StatusUpdate", | ||||||
|                             "status": NetUtils.ClientStatus.CLIENT_GOAL, |                             "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]] = {} | ||||||
|  |  | ||||||
|         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"] | ||||||
|         await self.update_and_validate_address(ctx, 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 Who Lake Mission Count back to 0 to prevent warping after completing 3 missions |         # Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions | ||||||
|         await self.update_and_validate_address(ctx,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: | ||||||
|                     current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|                         addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") |                         current_bin_value = ram_addr_dict[addr_to_update.ram_address][0] | ||||||
|                     current_bin_value &= ~(1 << addr_to_update.binary_bit_pos) |  | ||||||
|                     await self.update_and_validate_address(ctx, addr_to_update.ram_address, current_bin_value, 1) |  | ||||||
|                     else: |                     else: | ||||||
|                     await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1) |                         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.byte_size, | ||||||
|  |                     ] | ||||||
|  |  | ||||||
|  |         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]]: | ||||||
|  |         addr_list_to_update: list[tuple[int, Sequence[int], str]] = [] | ||||||
|  |  | ||||||
|  |         for key, val in addr_dict.items(): | ||||||
|  |             addr_list_to_update.append( | ||||||
|  |                 (key, val[0].to_bytes(val[1], "little"), "MainRAM") | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         return addr_list_to_update | ||||||
|  |  | ||||||
|     # Removes the regional access until you actually received it from AP. |     # Removes the regional access until you actually received it from AP. | ||||||
|     async def constant_address_update(self, ctx: "BizHawkClientContext"): |     async def constant_address_update(self, ctx: "BizHawkClientContext"): | ||||||
|         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] |         ram_addr_dict: dict[int, list[int]] = {} | ||||||
|         items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE} |  | ||||||
|  |  | ||||||
|         for (item_name, item_data) in items_to_check.items(): |         list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] | ||||||
|  |         items_to_check: dict[str, GrinchItemData] = { | ||||||
|  |             **KEYS_TABLE, | ||||||
|  |             **MISSION_ITEMS_TABLE, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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: | ||||||
|                     current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( |                     if addr_to_update.ram_address in ram_addr_dict.keys(): | ||||||
|                         addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") |                         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.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) | ||||||
|                     await self.update_and_validate_address(ctx, 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: | ||||||
|                         await self.update_and_validate_address(ctx, addr_to_update.ram_address, addr_to_update.value, 1) |                         ram_addr_dict[addr_to_update.ram_address] = [ | ||||||
|  |                             addr_to_update.value, | ||||||
|  |                             addr_to_update.byte_size, | ||||||
|  |                         ] | ||||||
|  |  | ||||||
|                     else: |                     else: | ||||||
|                         await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1) |                         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], | ||||||
|  |             "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 | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         # If grinch has changed maps |         # If grinch has changed maps | ||||||
| @@ -268,6 +544,8 @@ class GrinchClient(BizHawkClient): | |||||||
|                 # 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: | ||||||
|                 return False |                 return False | ||||||
|  |  | ||||||
|             # Update the previous map we were on to be the current map. |             # Update the previous map we were on to be the current map. | ||||||
| @@ -279,61 +557,129 @@ 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: | ||||||
|             max_eggs: int = 200 |             await bizhawk.write( | ||||||
|             await bizhawk.write(ctx.bizhawk_ctx, [(0x010058, max_eggs.to_bytes(2,"little"), "MainRAM")]) |                 ctx.bizhawk_ctx, | ||||||
|  |                 [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2, "little"), "MainRAM")], | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     async def update_and_validate_address(self, ctx: "BizHawkClientContext", address_to_validate: int, expected_value: int, byte_size: int): |     async def ring_link_output(self, ctx: "BizHawkClientContext"): | ||||||
|         await bizhawk.write(ctx.bizhawk_ctx, [(address_to_validate, expected_value.to_bytes(byte_size, "little"), "MainRAM")]) |         from CommonClient import logger | ||||||
|         current_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(address_to_validate, byte_size, "MainRAM")]))[0], "little") |  | ||||||
|         if not current_value == expected_value: |         while self.send_ring_link and ctx.slot: | ||||||
|             if address_to_validate == 0x010000 or address_to_validate == 0x08FB94: # TODO Temporairly skips teleportation addresses; to be changed later on. |  | ||||||
|  |             try: | ||||||
|  |                 current_egg_count = int.from_bytes( | ||||||
|  |                     ( | ||||||
|  |                         await bizhawk.read( | ||||||
|  |                             ctx.bizhawk_ctx, | ||||||
|  |                             [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")], | ||||||
|  |                         ) | ||||||
|  |                     )[0], | ||||||
|  |                     "little", | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 if (current_egg_count - self.previous_egg_count) != 0: | ||||||
|  |                     msg = { | ||||||
|  |                         "cmd": "Bounce", | ||||||
|  |                         "data": { | ||||||
|  |                             "time": time.time(), | ||||||
|  |                             "source": self.unique_client_id, | ||||||
|  |                             "amount": current_egg_count - self.previous_egg_count, | ||||||
|  |                         }, | ||||||
|  |                         "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) | ||||||
|  |                 ) | ||||||
|  |                 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." | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |         ) | ||||||
|  |         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", | ||||||
|  |                 ) | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.previous_egg_count = current_egg_count | ||||||
|  |         # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.") | ||||||
|  |  | ||||||
|  |     def _get_uuid(self) -> int: | ||||||
|  |         string_id = str(uuid.uuid4()) | ||||||
|  |         uid: int = 0 | ||||||
|  |         for char in string_id: | ||||||
|  |             uid += ord(char) | ||||||
|  |         return uid | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _cmd_ringlink(self): | ||||||
|  |     """Toggle ringling from client. Overrides default setting.""" | ||||||
|  |     if not self.ctx.slot: | ||||||
|         return |         return | ||||||
|             raise Exception("Unable to update address as expected. Address: "+ str(address_to_validate)+"; Expected Value: "+str(expected_value)) |  | ||||||
|  |  | ||||||
|     # async def ring_link_output(self, ctx: "BizHawkClientContext", byte_size: int): |     Utils.async_start( | ||||||
|     #     bizhawk.seek(0x010058) |         _update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), | ||||||
|     #     byte_size = 2 |         name="Update RingLink", | ||||||
|     #     current_eggs = int.from_bytes(byte_size=2, byteorder="little") |     ) | ||||||
|     #     difference = current_eggs - ctx.previous_eggs |  | ||||||
|     #     ctx.previous_eggs = current_eggs + ctx.ring_link_eggs |  | ||||||
|     # | async def _update_ring_link(ctx: "BizHawkClientContext", ring_link: bool): | ||||||
|     #     if difference != 0: |     """Helper function to set Ring Link connection tag on/off and update the connection if already connected.""" | ||||||
|     #         # logger.info("got here with a difference of " + str(difference)) |     old_tags = copy.deepcopy(ctx.tags) | ||||||
|     #         msg = { |  | ||||||
|     #             "cmd": "Bounce", |     if ring_link: | ||||||
|     #             "slots": [ctx.slot], |         ctx.tags.add("RingLink") | ||||||
|     #             "data": { |  | ||||||
|     #                 "time": time.time(), |     else: | ||||||
|     #                 "source": ctx.slot, |         ctx.tags -= {"RingLink"} | ||||||
|     #                 "amount": difference |  | ||||||
|     #             }, |     if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed: | ||||||
|     #             "tags": ["RingLink"] |         await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]) | ||||||
|     #         } |  | ||||||
|     #         await ctx.send_msgs([msg]) |  | ||||||
|     # |  | ||||||
|     #     # here write new ring value back into file |  | ||||||
|     #     bizhawk.seek(0x010058) |  | ||||||
|     #     if current_eggs + ctx.ring_link_eggs < 0: |  | ||||||
|     #         ctx.ring_link_eggs = -current_eggs |  | ||||||
|     #     bizhawk.write(int(current_eggs + ctx.ring_link_eggs).to_bytes(byte_size=2, byteorder="little")) |  | ||||||
|     #     ctx.ring_link_eggs = 0 |  | ||||||
|     # |  | ||||||
|     # async def ring_link_input(self, data): |  | ||||||
|     #     amount = data["amount"] |  | ||||||
|     #     source = data["source"] |  | ||||||
|     #     if source == self.slot: |  | ||||||
|     #         return |  | ||||||
|     #     else: |  | ||||||
|     #         self.ring_link_eggs += amount |  | ||||||
|   | |||||||
| @@ -1,15 +1,20 @@ | |||||||
| 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: 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] | ||||||
|     second_item_group: Optional[str] = None |  | ||||||
|  |  | ||||||
| class GrinchItem(Item): | class GrinchItem(Item): | ||||||
|     game: str = "The Grinch" |     game: str = "The Grinch" | ||||||
| @@ -22,198 +27,555 @@ class GrinchItem(Item): | |||||||
|         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]] = {} | ||||||
|  |  | ||||||
|     for name, data in ALL_ITEMS_TABLE.items(): |     for name, data in ALL_ITEMS_TABLE.items(): | ||||||
|         categories.setdefault(data.item_group, set()).add(name) |         for group in data.item_group:  # iterate over each category | ||||||
|  |             categories.setdefault(group, set()).add(name) | ||||||
|  |  | ||||||
|     return categories |     return categories | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | # Gadgets | ||||||
| # All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit. | # 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, | ||||||
|  |         IC.useful, | ||||||
|  |         [ | ||||||
|  |             GrinchRamData(0x0102B6, value=0x40), | ||||||
|  |             GrinchRamData(0x0102B7, value=0x41), | ||||||
|  |             GrinchRamData(0x0102B8, value=0x44), | ||||||
|  |             GrinchRamData(0x0102B9, value=0x45), | ||||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=0) |             # GrinchRamData(0x0100BC, binary_bit_pos=0) | ||||||
|          ]), |         ], | ||||||
|     "Rotten Egg Launcher": GrinchItemData("Gadgets", 101, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102BA, value=0x40), GrinchRamData(0x0102BB, value=0x41), |     grinch_items.gadgets.ROCKET_EGG_LAUNCHER: GrinchItemData( | ||||||
|         GrinchRamData(0x0102BC, value=0x44), GrinchRamData(0x0102BD, value=0x45), |         [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) |             # GrinchRamData(0x0100BC, binary_bit_pos=1) | ||||||
|          ]), |         ], | ||||||
|     "Rocket Spring": GrinchItemData("Gadgets", 102, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102BE, value=0x40), GrinchRamData(0x0102BF, value=0x41), |     grinch_items.gadgets.ROCKET_SPRING: GrinchItemData( | ||||||
|         GrinchRamData(0x0102C0, value=0x42), GrinchRamData(0x0102C1, value=0x44), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102C2, value=0x45), GrinchRamData(0x0102C3, value=0x46), |         102, | ||||||
|         GrinchRamData(0x0102C4, value=0x48), GrinchRamData(0x0102C5, value=0x49), |         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(0x0102C6, value=0x4A), | ||||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=2) |             # GrinchRamData(0x0100BC, binary_bit_pos=2) | ||||||
|          ]), |         ], | ||||||
|     "Slime Shooter": GrinchItemData("Gadgets", 103, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102C7, value=0x40), GrinchRamData(0x0102C8, value=0x41), |     grinch_items.gadgets.SLIME_SHOOTER: GrinchItemData( | ||||||
|         GrinchRamData(0x0102C9, value=0x42), GrinchRamData(0x0102CA, value=0x44), |         [ | ||||||
|         GrinchRamData(0x0102CB, value=0x45), GrinchRamData(0x0102CC, value=0x46), |             grinch_categories.GADGETS, | ||||||
|         GrinchRamData(0x0102CD, value=0x48), GrinchRamData(0x0102CE, value=0x49), |             "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(0x0102CF, value=0x4A), | ||||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=3) |             # GrinchRamData(0x0100BC, binary_bit_pos=3) | ||||||
|          ]), |         ], | ||||||
|     "Octopus Climbing Device": GrinchItemData("Gadgets", 104, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102D0, value=0x40), GrinchRamData(0x0102D1, value=0x41), |     grinch_items.gadgets.OCTOPUS_CLIMBING_DEVICE: GrinchItemData( | ||||||
|         GrinchRamData(0x0102D2, value=0x42), GrinchRamData(0x0102D3, value=0x44), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102D4, value=0x45), GrinchRamData(0x0102D5, value=0x46), |         104, | ||||||
|         GrinchRamData(0x0102D6, value=0x48), GrinchRamData(0x0102D7, value=0x49), |         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(0x0102D8, value=0x4A), | ||||||
|             # GrinchRamData(0x0100BC, binary_bit_pos=4) |             # GrinchRamData(0x0100BC, binary_bit_pos=4) | ||||||
|          ]), |         ], | ||||||
|     "Marine Mobile": GrinchItemData("Gadgets", 105, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102D9, value=0x40), GrinchRamData(0x0102DA, value=0x41), |     grinch_items.gadgets.MARINE_MOBILE: GrinchItemData( | ||||||
|         GrinchRamData(0x0102DB, value=0x42), GrinchRamData(0x0102DC, value=0x43), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102DD, value=0x44), GrinchRamData(0x0102DE, value=0x45), |         105, | ||||||
|         GrinchRamData(0x0102DF, value=0x46), GrinchRamData(0x0102E0, value=0x47), |         IC.progression, | ||||||
|         GrinchRamData(0x0102E1, value=0x48), GrinchRamData(0x0102E2, value=0x49), |         [ | ||||||
|         GrinchRamData(0x0102E3, value=0x4A), GrinchRamData(0x0102E4, value=0x4B), |             GrinchRamData(0x0102D9, value=0x40), | ||||||
|         GrinchRamData(0x0102E5, value=0x4C), GrinchRamData(0x0102E6, value=0x4D), |             GrinchRamData(0x0102DA, value=0x41), | ||||||
|         GrinchRamData(0x0102E7, value=0x4E), GrinchRamData(0x0102E8, value=0x4F), |             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) |             # GrinchRamData(0x0100BC, binary_bit_pos=5) | ||||||
|          ]), |         ], | ||||||
|     "Grinch Copter": GrinchItemData("Gadgets", 106, IC.progression, |     ), | ||||||
|         [GrinchRamData(0x0102E9, value=0x40), GrinchRamData(0x0102EA, value=0x41), |     grinch_items.gadgets.GRINCH_COPTER: GrinchItemData( | ||||||
|         GrinchRamData(0x0102EB, value=0x42), GrinchRamData(0x0102EC, value=0x43), |         [grinch_categories.GADGETS], | ||||||
|         GrinchRamData(0x0102ED, value=0x44), GrinchRamData(0x0102EE, value=0x45), |         106, | ||||||
|         GrinchRamData(0x0102EF, value=0x46), GrinchRamData(0x0102F0, value=0x47), |         IC.progression, | ||||||
|         GrinchRamData(0x0102F1, value=0x48), GrinchRamData(0x0102F2, value=0x49), |         [ | ||||||
|         GrinchRamData(0x0102F3, value=0x4A), GrinchRamData(0x0102F4, value=0x4B), |             GrinchRamData(0x0102E9, value=0x40), | ||||||
|         GrinchRamData(0x0102F5, value=0x4C), GrinchRamData(0x0102F6, value=0x4D), |             GrinchRamData(0x0102EA, value=0x41), | ||||||
|         GrinchRamData(0x0102F7, value=0x4E), GrinchRamData(0x0102F8, value=0x4F), |             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) |             # 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", 200, IC.progression, |     grinch_items.level_items.WV_WHO_CLOAK: GrinchItemData( | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=0)], second_item_group="Useful Items"), |         [ | ||||||
|     "Painting Bucket": GrinchItemData("Mission Specific Items", 201, IC.progression_deprioritized, |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=1)], second_item_group="Useful Items"), |             grinch_categories.USEFUL_ITEMS, | ||||||
|     "Scissors": GrinchItemData("Mission Specific Items", 202, IC.progression_deprioritized, |         ], | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)], |         200, | ||||||
|         second_item_group="Useful Items"), |         IC.progression, | ||||||
|     "Glue Bucket": GrinchItemData("Mission Specific Items", 203, IC.progression_deprioritized, |         [GrinchRamData(0x0101F9, binary_bit_pos=0)], | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=4)], second_item_group="Useful Items"), |     ), | ||||||
|     "Cable Car Access Card": GrinchItemData("Mission Specific Items", 204, IC.progression, |     grinch_items.level_items.WV_PAINT_BUCKET: GrinchItemData( | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=5)], second_item_group="Useful Items"), |         [ | ||||||
|     "Drill": GrinchItemData("Mission Specific Items", 205, IC.progression_deprioritized, |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=2)], second_item_group="Useful Items"), |             grinch_categories.USEFUL_ITEMS, | ||||||
|     "Rope": GrinchItemData("Mission Specific Items", 206, IC.progression_deprioritized, |         ], | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=1)], second_item_group="Useful Items"), |         201, | ||||||
|     "Hook": GrinchItemData("Mission Specific Items", 207, IC.progression_deprioritized, |         IC.progression_deprioritized, | ||||||
|         [GrinchRamData(0x0101FA, binary_bit_pos=0)], second_item_group="Useful Items"), |         [GrinchRamData(0x0101F9, binary_bit_pos=1)], | ||||||
|     "Sculpting Tools": GrinchItemData("Mission Specific Items", 208, IC.progression_deprioritized, |     ), | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=2)], second_item_group="Useful Items"), |     grinch_items.level_items.WD_SCISSORS: GrinchItemData( | ||||||
|     "Hammer": GrinchItemData("Mission Specific Items", 209, IC.progression_deprioritized, |         [ | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=3)], second_item_group="Useful Items"), |             grinch_categories.MISSION_SPECIFIC_ITEMS, | ||||||
|     "Scout Clothes": GrinchItemData("Mission Specific Items", 210, IC.progression, |             grinch_categories.USEFUL_ITEMS, | ||||||
|         [GrinchRamData(0x0101F9, binary_bit_pos=7)], second_item_group="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)]), | ||||||
| #     "GPS": GrinchItemData("Sleigh Parts", 301, IC.useful, | #     "GPS": GrinchItemData(["Sleigh Parts"], 301, IC.useful, | ||||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=5)]), | #         [GrinchRamData(0x0101FB, binary_bit_pos=5)]), | ||||||
| #     "Tires": GrinchItemData("Sleigh Parts", 302, IC.progression_skip_balancing, | #     "Tires": GrinchItemData(["Sleigh Parts"], 302, IC.progression_skip_balancing, | ||||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=4)]), | #         [GrinchRamData(0x0101FB, binary_bit_pos=4)]), | ||||||
| #     "Skis": GrinchItemData("Sleigh Parts", 303, IC.progression_skip_balancing, | #     "Skis": GrinchItemData(["Sleigh Parts"], 303, IC.progression_skip_balancing, | ||||||
| #         [GrinchRamData(0x0101FB, binary_bit_pos=3)]), | #         [GrinchRamData(0x0101FB, binary_bit_pos=3)]), | ||||||
| #     "Twin-End Tuba": GrinchItemData("Sleigh Parts", 304, IC.progression_skip_balancing, | #     "Twin-End Tuba": GrinchItemData(["Sleigh Parts"], 304, IC.progression_skip_balancing, | ||||||
| #         [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 Access": GrinchItemData("Vacuum Access", 400, IC.progression, |     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()]), |     #     [GrinchRamData()]), | ||||||
|     "Who Forest Vacuum Access": GrinchItemData("Vacuum Access", 401, IC.progression, |     # "Spin N' Win Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 405, IC.progression, | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=2)]), |  | ||||||
|     "Who Dump Vacuum Access": GrinchItemData("Vacuum Access", 402, IC.progression, |  | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=3)]), |  | ||||||
|     "Who Lake Vacuum Access": GrinchItemData("Vacuum Access", 403, IC.progression, |  | ||||||
|         [GrinchRamData(0x0100AA, binary_bit_pos=4)]), |  | ||||||
|     # "Progressive Vacuum Access": GrinchItemData("Vacuum Access", 404, IC.progression, |  | ||||||
|     #     [GrinchRamData()]), |     #     [GrinchRamData()]), | ||||||
|     # "Spin N' Win Door Unlock": GrinchItemData("Supadow Door Unlocks", 405, IC.progression, |     # "Dankamania Door Unlock": GrinchItemData(["Supadow Door Unlocks"], 406, IC.progression, | ||||||
|     #     [GrinchRamData()]), |  | ||||||
|     # "Dankamania Door Unlock": GrinchItemData("Supadow Door Unlocks", 406, IC.progression, |  | ||||||
|     #     [GrinchRamData()]), |     #     [GrinchRamData()]), | ||||||
|     # "The Copter Race Contest Door Unlock": GrinchItemData("Supadow Door Unlocks", 407, IC.progression, |     # "The Copter Race Contest Door Unlock": GrinchItemData("Supadow Door Unlocks", 407, IC.progression, | ||||||
|     #     [GrinchRamData()]), |     #     [GrinchRamData()]), | ||||||
|     # "Progressive Supadow Door Unlock": GrinchItemData("Supadow Door Unlocks", 408, IC.progression, |     # "Progressive Supadow Door Unlock": GrinchItemData("Supadow Door Unlocks", 408, IC.progression, | ||||||
|     #     [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", 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", 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", 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", 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 | ||||||
| #     "Damage Trap": GrinchItemData("Traps", 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_existing_value=True)]), |     # alias to Exhaustion Trap | ||||||
|     "Depletion Trap": GrinchItemData("Traps", 605, IC.trap, [GrinchRamData(0x010058, value=0, bit_size=2)]), |     #     "Damage Trap": GrinchItemData(["Traps"], 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_method=UpdateMethod.ADD)]), | ||||||
|     "Dump it to Crumpit": GrinchItemData("Traps", 606, IC.trap, #Alias to Home Trap for traplink |     grinch_items.trap_items.DEPLETION_TRAP: GrinchItemData( | ||||||
|         [GrinchRamData(0x010000, value=0x05), GrinchRamData(0x08FB94, value=1)]), |         [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 |     # 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( | ||||||
|     # "Cutscene Trap": GrinchItemData("Traps", 609, IC.trap, [GrinchRamData()]), |         [grinch_categories.TRAPS], | ||||||
|     # "No Vac Trap": GrinchItemData("Traps", 610, IC.trap, [GrinchRamData(0x0102DA, value=0]), |         608, | ||||||
|     # "Invisible Trap": GrinchItemData("Traps", 611, IC.trap, [GrinchRamData(0x0102DA, value=0, bit_size=4)]) |         IC.trap, | ||||||
|     # "Child Trap": GrinchItemData("Traps", 612, IC.trap,[GrinchRamData()]) |         [ | ||||||
|     # "Disable Jump Trap": GrinchItemData("Traps", 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)]) |             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, 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 | # 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), | ||||||
|  |         ], | ||||||
|  |     ), | ||||||
|  |     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 | # 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, | ||||||
| @@ -221,9 +583,9 @@ ALL_ITEMS_TABLE: dict[str, GrinchItemData] = { | |||||||
|     **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 | ||||||
| @@ -239,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 |     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,13 +28,16 @@ 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 Access" |  | ||||||
|  |     display_name = "Progressive Vacuum Tubes" | ||||||
|  |  | ||||||
|  |  | ||||||
| class Missionsanity(Choice): | class Missionsanity(Choice): | ||||||
|     """ |     """ | ||||||
| @@ -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,51 +56,101 @@ class Missionsanity(Choice): | |||||||
|     option_both = 3 |     option_both = 3 | ||||||
|     default = 1 |     default = 1 | ||||||
|  |  | ||||||
| class AnnoyingLocations(DefaultOnToggle): |  | ||||||
|     """Makes certain long, annoying, and tedious checks to be excluded [NOT IMPLEMENTED]""" | class ExcludeEnvironments(OptionSet): | ||||||
|     display_name = "Annoying Locations" |     """ | ||||||
|  |     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. | ||||||
|  |     [NOT IMPLEMENTED] | ||||||
|  |  | ||||||
|  |     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" | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     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 | 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. (9 locations) [NOT IMPLEMENTED]""" |     """Enables completing minigames through the Supadows in Mount Crumpit as checks. NOT IMPLEMENTED]""" | ||||||
|     display_name = "Supadow Minigame Locations" |  | ||||||
|  |     display_name = "Supadow Minigames" | ||||||
|  |  | ||||||
|  |  | ||||||
| class Gifts(Toggle): | class Gifts(Range): | ||||||
|     """Missions that require you to squash every present in a level. (4 locations) [NOT IMPLEMENTED]""" |     """ | ||||||
|     display_name = "Gift Collection Locations" |     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 Movesanity(Toggle): | class Moverando(Toggle): | ||||||
|     """Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]""" |     """Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]""" | ||||||
|     display_name = "Movesanity" |  | ||||||
|  |     display_name = "Moves Randomized" | ||||||
|  |  | ||||||
|  |  | ||||||
| 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. [NOT IMPLEMENTED]""" |     """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): | 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" | ||||||
|  |  | ||||||
|  |  | ||||||
| @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 | ||||||
|     annoying_locations: AnnoyingLocations |     exclude_environments: ExcludeEnvironments | ||||||
|     progressive_gadget: ProgressiveGadget |     progressive_gadget: ProgressiveGadget | ||||||
|     supadow_minigames: Supadow |     supadow_minigames: Supadow | ||||||
|     giftsanity: Gifts |     giftsanity: Gifts | ||||||
|     movesanity: Movesanity |     move_rando: Moverando | ||||||
|     unlimited_eggs: UnlimitedEggs |     unlimited_eggs: UnlimitedEggs | ||||||
|     ring_link: RingLinkOption |     ring_link: RingLinkOption | ||||||
|     trap_link: TrapLinkOption |     trap_link: TrapLinkOption | ||||||
|   | |||||||
| @@ -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,13 +13,13 @@ mainareas_list = [ | |||||||
|     "Whoville", |     "Whoville", | ||||||
|     "Who Forest", |     "Who Forest", | ||||||
|     "Who Dump", |     "Who Dump", | ||||||
|     "Who Lake" |     "Who Lake", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| subareas_list = [ | subareas_list = [ | ||||||
|     "Post Office", |     "Post Office", | ||||||
|     "City Hall", |     "City Hall", | ||||||
|     "Countdown to X-Mas Tower", |     "Clock Tower", | ||||||
|     "Ski Resort", |     "Ski Resort", | ||||||
|     "Civic Center", |     "Civic Center", | ||||||
|     "Minefield", |     "Minefield", | ||||||
| @@ -29,20 +29,23 @@ 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)) | ||||||
| @@ -50,8 +53,11 @@ def create_regions(world: "GrinchWorld"): | |||||||
|         # 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,20 +68,25 @@ 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") | ||||||
| @@ -83,12 +94,12 @@ def connect_regions(world: "GrinchWorld"): | |||||||
|     grinchconnect(world, "Mount Crumpit", "Who Dump") |     grinchconnect(world, "Mount Crumpit", "Who Dump") | ||||||
|     grinchconnect(world, "Mount Crumpit", "Who Lake") |     grinchconnect(world, "Mount Crumpit", "Who Lake") | ||||||
|     grinchconnect(world, "Mount Crumpit", "Sleigh Room") |     grinchconnect(world, "Mount Crumpit", "Sleigh Room") | ||||||
|     grinchconnect(world, "Mount Crumpit", "Spin N' Win Supadow") |     grinchconnect(world, "Mount Crumpit", "Spin N' Win") | ||||||
|     grinchconnect(world, "Mount Crumpit", "Dankamania Supadow") |     grinchconnect(world, "Mount Crumpit", "Dankamania") | ||||||
|     grinchconnect(world, "Mount Crumpit", "The Copter Race Contest Supadow") |     grinchconnect(world, "Mount Crumpit", "The Copter Race Contest") | ||||||
|     grinchconnect(world, "Whoville", "Post Office") |     grinchconnect(world, "Whoville", "Post Office") | ||||||
|     grinchconnect(world, "Whoville", "City Hall") |     grinchconnect(world, "Whoville", "City Hall") | ||||||
|     grinchconnect(world, "Whoville", "Countdown to X-Mas Clock Tower") |     grinchconnect(world, "Whoville", "Clock Tower") | ||||||
|     grinchconnect(world, "Who Forest", "Ski Resort") |     grinchconnect(world, "Who Forest", "Ski Resort") | ||||||
|     grinchconnect(world, "Who Forest", "Civic Center") |     grinchconnect(world, "Who Forest", "Civic Center") | ||||||
|     grinchconnect(world, "Who Dump", "Minefield") |     grinchconnect(world, "Who Dump", "Minefield") | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| from BaseClasses import Region, Item, ItemClassification | from BaseClasses import Region, Item, ItemClassification | ||||||
| from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation | from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation, get_location_names_per_category | ||||||
| from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE | from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE, get_item_names_per_category | ||||||
| from .Regions import connect_regions | from .Regions import connect_regions | ||||||
| from .Rules import set_location_rules | from .Rules import set_location_rules | ||||||
|  |  | ||||||
| @@ -8,8 +8,9 @@ from .Client import * | |||||||
| from typing import ClassVar | from typing import ClassVar | ||||||
|  |  | ||||||
| from worlds.AutoWorld import World | from worlds.AutoWorld import World | ||||||
|  | from Options import OptionError | ||||||
|  |  | ||||||
| from . import Options | from .Options import GrinchOptions | ||||||
| from .Rules import access_rules_dict | from .Rules import access_rules_dict | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -21,14 +22,18 @@ class GrinchWorld(World): | |||||||
|     item_name_to_id: ClassVar[dict[str,int]] = grinch_items_to_id() |     item_name_to_id: ClassVar[dict[str,int]] = grinch_items_to_id() | ||||||
|     location_name_to_id: ClassVar[dict[str,int]] = grinch_locations_to_id() |     location_name_to_id: ClassVar[dict[str,int]] = grinch_locations_to_id() | ||||||
|     required_client_version = (0, 6, 3) |     required_client_version = (0, 6, 3) | ||||||
|  |     item_name_groups = get_item_names_per_category() | ||||||
|  |     location_name_groups = get_location_names_per_category() | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): #Pulls __init__ function and takes control from there in BaseClasses.py |     def __init__(self, *args, **kwargs): #Pulls __init__ function and takes control from there in BaseClasses.py | ||||||
|         self.origin_region_name: str = "Mount Crumpit" |         self.origin_region_name: str = "Mount Crumpit" | ||||||
|         super(GrinchWorld, self).__init__(*args, **kwargs) |         super(GrinchWorld, self).__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|     def generate_early(self) -> None: #Special conditions changed before generation occurs |     def generate_early(self) -> None: #Special conditions changed before generation occurs | ||||||
|         pass |         if self.options.ring_link == 1 and self.options.unlimited_eggs == 1: | ||||||
|  |             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}") | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_regions(self): #Generates all regions for the multiworld |     def create_regions(self): #Generates all regions for the multiworld | ||||||
|         for region_name in access_rules_dict.keys(): |         for region_name in access_rules_dict.keys(): | ||||||
| @@ -37,7 +42,7 @@ class GrinchWorld(World): | |||||||
|         for location, data in grinch_locations.items(): |         for location, data in grinch_locations.items(): | ||||||
|             region = self.get_region(data.region) |             region = self.get_region(data.region) | ||||||
|             entry = GrinchLocation(self.player, location, region, data) |             entry = GrinchLocation(self.player, location, region, data) | ||||||
|             if location == "Neutralizing Santa": |             if location == "MC - Sleigh Ride - Neutralizing Santa": | ||||||
|                 entry.place_locked_item(Item("Goal", ItemClassification.progression, None, self.player)) |                 entry.place_locked_item(Item("Goal", ItemClassification.progression, None, self.player)) | ||||||
|             region.locations.append(entry) |             region.locations.append(entry) | ||||||
|         connect_regions(self) |         connect_regions(self) | ||||||
| @@ -72,7 +77,7 @@ class GrinchWorld(World): | |||||||
|     def fill_slot_data(self): |     def fill_slot_data(self): | ||||||
|         return { |         return { | ||||||
|             "give_unlimited_eggs": self.options.unlimited_eggs.value, |             "give_unlimited_eggs": self.options.unlimited_eggs.value, | ||||||
|  |             "ring_link": self.options.ring_link.value, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     def generate_output(self, output_directory: str) -> None: |     def generate_output(self, output_directory: str) -> None: | ||||||
|   | |||||||
							
								
								
									
										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" | ||||||
|  | } | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| - Credit to Raven-187 & Hacc on gamehacking.org for providing the addresses for various cheat codes. Without them, this  | - Credit to Raven-187 & Hacc on gamehacking.org for providing the addresses for various cheat codes. Without them, this  | ||||||
| would of made RAM searching much more tedious. | would of made RAM searching much more tedious. | ||||||
| - Shoutouts to SomeJakeGuy for basically teaching me how to code in general. | - Shoutouts to SomeJakeGuy for basically teaching me how to code in general along with starting decompilation of the game | ||||||
|  | into motion | ||||||
| - Shoutouts to BootsinSoots for helping with the implementation of the logic rules code itself. | - Shoutouts to BootsinSoots for helping with the implementation of the logic rules code itself. | ||||||
| - Thanks to the Grinch PS1 speedrunning discord community server for encouraging the production of this randomizer. | - Thanks to the Grinch PS1 speedrunning discord community server for encouraging the production of this randomizer. | ||||||
|  | - Credit to Artamiss for the vast majority of BZZ decompilation and general backend coding. | ||||||
| @@ -1,31 +1,45 @@ | |||||||
| # The Grinch - Setup Guide | # The Grinch (PS1) - Setup Guide | ||||||
|  |  | ||||||
| ## Required Software | ## Required Software | ||||||
| - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.2 or later for integrated | - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.3 or later for integrated | ||||||
| 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`.  | ||||||
| - CUE files may work, but I have not tested this. | 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) Version 2.9.1 is supported, but I can't promise if any version is stable or not. | - [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 | ||||||
|  | named something like `SCPH-5501.BIN`. | ||||||
|  |  | ||||||
| ## Configuring your Config (.yaml) file | ## Configuring BizHawk | ||||||
|  | Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings: | ||||||
|  |  | ||||||
| ### What is a config file and why do I need one? | - If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from | ||||||
|  | `NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.) | ||||||
|  | - Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're | ||||||
|  | tabbed out of EmuHawk. | ||||||
|  | - Under `Config > Preferred Cores > PSX`, select NymaShock. | ||||||
|  | - Open any PlayStation game in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click | ||||||
|  | `Controllers…`, it's because you need to load a game first. | ||||||
|  | You may need to invert Sensitivity for the up/down axis to -100%. | ||||||
|  | This can be found under Analog Controls through `Config > Controllers…`. | ||||||
|  | Depending on your controller, you may also want to tweak the Deadzone. Something like 6% is recommended for a DualShock 4. | ||||||
|  | - Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to | ||||||
|  | clear it. | ||||||
|  | - You are required to legally obtain a PSX Bios BIN firmware file for the game to be opened. To import this, you go to | ||||||
|  | `Config > Firmware... > Tools` and scrolling until you see the PlayStation tab. You might right click on the bios region | ||||||
|  | and click `Set Customization` or `Import` on the top of the window. Then a window should open, telling you what file to  | ||||||
|  | import, which is the BIN file required. The bios should be recognized if Bizhawk displays a checkmark beside it, saying  | ||||||
|  | the bios version you have is stable and should run without issues. If a bios is already been imported and the game | ||||||
|  | runs fine by itself, you may skip this step. | ||||||
|  |  | ||||||
| See the guide on setting up a basic YAML at the Archipelago setup | ## Generating a Game | ||||||
| guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) |  | ||||||
|  |  | ||||||
| ### Where do I get a config file? | 1. Create your options file (YAML). After installing the `grinch.apworld` file, you can generate a template within the Archipelago Launcher by clicking `Generate Template Settings`. | ||||||
|  | 2. Follow the general Archipelago instructions for [generating a game](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game). | ||||||
| The Player options page on the website allows you to configure your personal | 3. Open `ArchipelagoLauncher.exe` | ||||||
| options and export a config file from them: [The Grinch Player Options Page](../player-options) | 4. Select "BizHawk Client" in the right-side column. On your first time opening BizHawk Client, you will also be asked to | ||||||
|  | locate `EmuHawk.exe` in your BizHawk install. | ||||||
| ### Verifying your config file |  | ||||||
|  |  | ||||||
| If you would like to validate your config file to make sure it works, you may do |  | ||||||
| so on the YAML Validator page: [YAML Validation page](/check) |  | ||||||
|  |  | ||||||
| ## Joining a MultiWorld Game |  | ||||||
|  |  | ||||||
| ### Connect to the Multiserver | ### Connect to the Multiserver | ||||||
|  |  | ||||||
| @@ -37,18 +51,3 @@ script. Navigate to your Archipelago install folder and open `data/lua/connector | |||||||
| To connect the client to the multiserver simply put `<address>:<port>` on the text field on top and | To connect the client to the multiserver simply put `<address>:<port>` on the text field on top and | ||||||
| press enter (if the server uses a password, type in the bottom text field | press enter (if the server uses a password, type in the bottom text field | ||||||
| `/connect <address>:<port> [password]`) | `/connect <address>:<port> [password]`) | ||||||
|  |  | ||||||
| ## Hosting a MultiWorld game |  | ||||||
|  |  | ||||||
| The recommended way to host a game is to use our hosting service. The process is relatively simple: |  | ||||||
|  |  | ||||||
| 1. Collect config files from your players. |  | ||||||
| 2. Upload the config files to the Generate page above. |  | ||||||
|     - Generate page: [WebHost Seed Generation Page](/generate) |  | ||||||
| 3. Wait a moment while the seed is generated. |  | ||||||
| 4. When the seed is generated, you will be redirected to a "Seed Info" page. |  | ||||||
| 5. Click "Create New Room". This will take you to the server page. Provide the link to this page to |  | ||||||
|   your players, so they may download their patch files from there. |  | ||||||
| 6. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the |  | ||||||
|   progress of all players in the game. Any observers may also be given the link to this page. |  | ||||||
| 7. Once all players have joined, you may begin playing. |  | ||||||
		Reference in New Issue
	
	Block a user