diff --git a/worlds/grinch/Client.py b/worlds/grinch/Client.py index 6a5839f1..b3e034f1 100644 --- a/worlds/grinch/Client.py +++ b/worlds/grinch/Client.py @@ -1,9 +1,11 @@ +import asyncio from typing import TYPE_CHECKING from .Locations import grinch_locations from .Items import ALL_ITEMS_TABLE import worlds._bizhawk as bizhawk from worlds._bizhawk.client import BizHawkClient from worlds.Files import APDeltaPatch + if TYPE_CHECKING: from worlds._bizhawk.context import BizHawkClientContext from CommonClient import logger @@ -14,35 +16,49 @@ class GrinchClient(BizHawkClient): system = "PSX" patch_suffix = ".apgrinch" items_handling = 0b111 - last_received_index = 0 def __init__(self): super().__init__() + self.last_received_index = 0 + self.loading_bios_msg = False + self.loc_unlimited_eggs = False async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: from CommonClient import logger # TODO Check the ROM data to see if it matches against bytes expected grinch_identifier_ram_address: int = 0x00928C - bytes_expected: bytes = bytes.fromhex("e903c14f4becf89082f43ec936a68e62") + bios_identifier_ram_address: int = 0x097F30 try: bytes_actual: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( grinch_identifier_ram_address, 11, "MainRAM")]))[0] psx_rom_name = bytes_actual.decode("ascii") if psx_rom_name != "SLUS_011.97": + bios_bytes_check: bytes = (await bizhawk.read(ctx.bizhawk_ctx, [( + bios_identifier_ram_address, 24, "MainRAM")]))[0] + if "System ROM Version" in bios_bytes_check.decode("ascii"): + if not self.loading_bios_msg: + self.loading_bios_msg = True + logger.error("BIOS is currently loading. Will wait up to 5 seconds before retrying.") + return False + logger.error("Invalid rom detected. You are not playing Grinch USA Version.") raise Exception("Invalid rom detected. You are not playing Grinch USA Version.") - except Exception(): + except Exception: return False ctx.game = self.game ctx.items_handling = self.items_handling ctx.want_slot_data = True ctx.watcher_timeout = 0.125 + self.loading_bios_msg = False return True + async def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: + self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"]) + async def set_auth(self, ctx: "BizHawkClientContext") -> None: await ctx.get_username() @@ -52,6 +68,8 @@ class GrinchClient(BizHawkClient): return try: + if not self.ingame_checker(ctx): + return # # Read save data # save_data = await bizhawk.read( # ctx.bizhawk_ctx, @@ -79,8 +97,8 @@ class GrinchClient(BizHawkClient): pass async def location_checker(self, ctx: "BizHawkClientContext"): - # TODO Write a function to check if I am ingame, determine how I am playing the game vs not in demo mode/game menu # Update the AP Server to know what locations are not checked yet. + local_locations_checked: list[int] = [] for missing_location in ctx.missing_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. @@ -94,14 +112,15 @@ class GrinchClient(BizHawkClient): addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") if is_binary: if (current_ram_address_value & (1 << addr_to_update.binary_bit_pos)) > 0: - ctx.locations_checked.add(missing_location) + local_locations_checked.append(missing_location) else: expected_int_value = addr_to_update.value if expected_int_value == current_ram_address_value: - ctx.locations_checked.add(missing_location) + local_locations_checked.append(missing_location) # Update the AP server with the locally checked list of locations (In other words, locations I found in Grinch) - await ctx.check_locations(ctx.locations_checked) + await ctx.check_locations(local_locations_checked) + ctx.locations_checked = set(local_locations_checked) async def receiving_items_handler(self, ctx: "BizHawkClientContext"): # Len will give us the size of the items received list & we will track that against how many items we received already @@ -119,10 +138,12 @@ class GrinchClient(BizHawkClient): for addr_to_update in grinch_item_ram_data.update_ram_addr: is_binary = True if not addr_to_update.binary_bit_pos is None else False + 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 is_binary: - current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( - addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos)) + elif addr_to_update.update_existing_value: + current_ram_address_value += addr_to_update.value else: current_ram_address_value = addr_to_update.value @@ -130,4 +151,21 @@ class GrinchClient(BizHawkClient): await bizhawk.write(ctx.bizhawk_ctx, [(addr_to_update.ram_address, current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"), "MainRAM")]) - self.last_received_index += 1 \ No newline at end of file + self.last_received_index += 1 + + async def ingame_checker(self, ctx: "BizHawkClientContext"): + demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( + 0x01008A, 1, "MainRAM")]))[0], "little") + if demo_mode == 1: + return False + + is_not_ingame = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( + 0x010000, 1, "MainRAM")]))[0], "little") + if is_not_ingame <= 0x04 or is_not_ingame >= 0x35: + return False + return True + + async def option_handler(self, ctx: "BizHawkClientContext"): + if self.loc_unlimited_eggs: + max_eggs: int = 200 + await bizhawk.write(ctx.bizhawk_ctx, [(0x010058, max_eggs.to_bytes(1,"little"), "MainRAM")]) diff --git a/worlds/grinch/__init__.py b/worlds/grinch/__init__.py index a75b35cd..7cabca55 100644 --- a/worlds/grinch/__init__.py +++ b/worlds/grinch/__init__.py @@ -5,7 +5,7 @@ from .Regions import connect_regions from .Rules import set_rules from .Client import * -from typing import ClassVar +from typing import ClassVar, Mapping, Any from worlds.AutoWorld import World @@ -67,4 +67,10 @@ class GrinchWorld(World): set_rules(self) def get_other_filler_item(self, other_filler: list[str]) -> str: - return self.random.choices(other_filler)[0] \ No newline at end of file + return self.random.choices(other_filler)[0] + + def fill_slot_data(self): + return { + "give_unlimited_eggs": self.options.unlimited_eggs.value, + + } \ No newline at end of file