16 Commits

Author SHA1 Message Date
MarioSpore
900c8a519a Fixed SADX/SA2B crashes? 2025-09-12 05:44:49 -04:00
MarioSpore
43acc9f003 Minefield logical access oversight 2025-09-11 19:35:42 -04:00
MarioSpore
96eb8fcd9a Fix ringlink output 2025-09-11 00:11:25 -04:00
MarioSpore
d4bd682ac9 Fix location send speed code 2025-09-10 23:19:36 -04:00
MarioSpore
61d4783f61 Minor setup doc tweak 2025-09-10 18:49:31 -04:00
MarioSpore
00fff466ff Updated setup docs that actually make more sense 2025-09-10 18:36:38 -04:00
MarioSpore
d8483bef6e Fixes client crash if the emulator is paused with ringlink enabled. Still won't be able to send out ringlink when this occurs 2025-09-09 23:09:56 -04:00
MarioSpore
56a198fcfd Vastly improved speed of remove_physical_items, constant_address_update, & receiving_items_handler 2025-09-09 22:06:07 -04:00
MarioSpore
4e362dc722 Fixed output for ring link to be in a loop 2025-09-09 21:01:56 -04:00
MarioSpore
cfcfc9ecfd Fixed ring link input 2025-09-09 17:44:04 -04:00
MarioSpore
a3a415adfd Merge pull request #1 from MarioSpore/jake_ring-link-fix
Fix some small issues with async on_package and changing things to be async starts instead
2025-09-09 05:46:14 -04:00
SomeJakeGuy
14c95aa85b Fix some small issues with async on_package and changing things to be async starts instead. 2025-09-09 01:29:32 -04:00
MarioSpore
8d941dad6f Adjust import of options to fix AttributeError for previous commit 2025-09-09 00:23:36 -04:00
MarioSpore
8628f6637a Fully implement ringlink 2025-09-09 00:04:09 -04:00
MarioSpore
17b7914c35 Now passing ring link option value to client 2025-09-08 22:48:43 -04:00
MarioSpore
b8dfd5ce4c Adds mount crumpit crate locations 2025-09-08 22:41:22 -04:00
6 changed files with 169 additions and 126 deletions

View File

@@ -1,8 +1,10 @@
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 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
@@ -24,14 +26,20 @@ 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
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -66,7 +74,7 @@ class GrinchClient(BizHawkClient):
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
@@ -79,9 +87,26 @@ class GrinchClient(BizHawkClient):
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. "+ logger.info("You are now connected to the client. "+
"There may be a slight delay to check you are not in demo mode before locations start to send.") "There may be a slight delay to check you are not in demo mode before locations start to send.")
# tags = args.get("tags", [])
# if "RingLink" in tags: ring_link_enabled = bool(ctx.slot_data["ring_link"])
# ring_link_input(self, args["data"])
tags = copy.deepcopy(ctx.tags)
if ring_link_enabled:
self.send_ring_link = True
Utils.async_start(self.ring_link_output(ctx), name="EggLink")
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"] != ctx.player_names[ctx.slot]:
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()
@@ -101,7 +126,6 @@ class GrinchClient(BizHawkClient):
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
@@ -117,9 +141,22 @@ class GrinchClient(BizHawkClient):
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.bit_size, "MainRAM") for
read_addr in grinch_loc_ram_data.update_ram_addr]
addr_list_to_read = [*addr_list_to_read, *missing_addr_list]
returned_bytes: list[bytes] = await bizhawk.read(ctx.bizhawk_ctx, addr_list_to_read)
# Now loop through everything again and this time get the byte value from the above read, convert to int,
# and check to see if that ram address has our expected value.
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)
@@ -130,13 +167,13 @@ class GrinchClient(BizHawkClient):
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"))
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") 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))
@@ -158,6 +195,7 @@ class GrinchClient(BizHawkClient):
# 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:]
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
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)
@@ -177,12 +215,15 @@ class GrinchClient(BizHawkClient):
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) addr_list_to_update.append((addr_to_update.ram_address,
# await bizhawk.write(ctx.bizhawk_ctx, [(addr_to_update.ram_address, current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"),"MainRAM"))
# current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"), "MainRAM")])
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)
addr_list_to_update.append((RECV_ITEM_ADDR,
self.last_received_index.to_bytes(RECV_ITEM_BITSIZE,"little"), "MainRAM"))
await bizhawk.write(ctx.bizhawk_ctx, addr_list_to_update)
async def goal_checker(self, ctx: "BizHawkClientContext"): async def goal_checker(self, ctx: "BizHawkClientContext"):
if not ctx.finished_game: if not ctx.finished_game:
@@ -199,14 +240,17 @@ class GrinchClient(BizHawkClient):
# 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"):
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
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] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE
heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570)) 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) addr_list_to_update.append((heart_item_data.update_ram_addr[0].ram_address,
min(heart_count, 4).to_bytes(1, "little"), "MainRAM"))
# 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) addr_list_to_update.append((0x0100F0, int(0).to_bytes(4, "little"), "MainRAM"))
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.
@@ -220,12 +264,18 @@ class GrinchClient(BizHawkClient):
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [( current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little") addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
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) addr_list_to_update.append((addr_to_update.ram_address,
current_bin_value.to_bytes(1, "little"), "MainRAM"))
else: else:
await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1) addr_list_to_update.append((addr_to_update.ram_address,
int(0).to_bytes(1, "little"), "MainRAM"))
await bizhawk.write(ctx.bizhawk_ctx, 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"):
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received] list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE} items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE}
@@ -244,12 +294,17 @@ class GrinchClient(BizHawkClient):
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) addr_list_to_update.append((addr_to_update.ram_address,
current_bin_value.to_bytes(1, "little"), "MainRAM"))
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) addr_list_to_update.append((addr_to_update.ram_address,
addr_to_update.value.to_bytes(1, "little"), "MainRAM"))
else: else:
await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1) addr_list_to_update.append((addr_to_update.ram_address,
int(0).to_bytes(1, "little"), "MainRAM"))
await bizhawk.write(ctx.bizhawk_ctx, addr_list_to_update)
async def ingame_checker(self, ctx: "BizHawkClientContext"): async def ingame_checker(self, ctx: "BizHawkClientContext"):
from CommonClient import logger from CommonClient import logger
@@ -291,49 +346,43 @@ class GrinchClient(BizHawkClient):
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(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2,"little"), "MainRAM")])
await bizhawk.write(ctx.bizhawk_ctx, [(0x010058, 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") while self.send_ring_link and ctx.slot:
if not current_value == expected_value:
if address_to_validate == 0x010000 or address_to_validate == 0x08FB94: # TODO Temporairly skips teleportation addresses; to be changed later on.
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): try:
# bizhawk.seek(0x010058) current_egg_count = int.from_bytes(
# byte_size = 2 (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little")
# current_eggs = int.from_bytes(byte_size=2, byteorder="little")
# difference = current_eggs - ctx.previous_eggs if (current_egg_count - self.previous_egg_count) != 0:
# ctx.previous_eggs = current_eggs + ctx.ring_link_eggs msg = {
# "cmd": "Bounce",
# if difference != 0: "data": {
# # logger.info("got here with a difference of " + str(difference)) "time": time.time(),
# msg = { "source": ctx.slot,
# "cmd": "Bounce", "amount": current_egg_count - self.previous_egg_count
# "slots": [ctx.slot], },
# "data": { "tags": ["RingLink"]
# "time": time.time(), }
# "source": ctx.slot, await ctx.send_msgs([msg])
# "amount": difference self.previous_egg_count = current_egg_count
# }, # logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.")
# "tags": ["RingLink"] await asyncio.sleep(0.1)
# } except Exception as ex:
# await ctx.send_msgs([msg]) logger.error("While monitoring grinch's egg count ingame, an error occurred. Details:"+ str(ex))
# self.send_ring_link = False
# # here write new ring value back into file
# bizhawk.seek(0x010058) if not ctx.slot:
# if current_eggs + ctx.ring_link_eggs < 0: logger.info("You must be connected to the multi-world in order for RingLink to work properly.")
# ctx.ring_link_eggs = -current_eggs
# bizhawk.write(int(current_eggs + ctx.ring_link_eggs).to_bytes(byte_size=2, byteorder="little")) async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"):
# ctx.ring_link_eggs = 0 from CommonClient import logger
# current_egg_count = int.from_bytes(
# async def ring_link_input(self, data): (await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little")
# amount = data["amount"] current_egg_count = min(current_egg_count + egg_amount, MAX_EGGS)
# source = data["source"] await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR,
# if source == self.slot: int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"), "MainRAM")])
# return self.previous_egg_count = current_egg_count
# else: # logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.")
# self.ring_link_eggs += amount

View File

@@ -183,11 +183,11 @@ grinch_locations = {
"Twin-End Tuba in Submarine World": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1603, [GrinchRamData(0x0101FB, binary_bit_pos=6)]), "Twin-End Tuba in Submarine World": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1603, [GrinchRamData(0x0101FB, binary_bit_pos=6)]),
"GPS in Who Lake": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1604, [GrinchRamData(0x0101FB, binary_bit_pos=5)]), "GPS in Who Lake": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1604, [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
# Mount Crumpit Locations # Mount Crumpit Locations
# "1st Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1700, [GrinchRamData(0x095343, value=1)]), "1st Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1700, [GrinchRamData(0x095343, value=1)]),
# "2nd Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1701, [GrinchRamData(0x095343, value=2)]), "2nd Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1701, [GrinchRamData(0x095343, value=2)]),
# "3rd Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1702, [GrinchRamData(0x095343, value=3)]), "3rd Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1702, [GrinchRamData(0x095343, value=3)]),
# "4th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1703, [GrinchRamData(0x095343, value=4)]), "4th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1703, [GrinchRamData(0x095343, value=4)]),
# "5th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1704, [GrinchRamData(0x095343, value=5)]), "5th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1704, [GrinchRamData(0x095343, value=5)]),
} }
def grinch_locations_to_id() -> dict[str,int]: def grinch_locations_to_id() -> dict[str,int]:

View File

@@ -69,7 +69,7 @@ class UnlimitedEggs(Toggle):
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."""
class TrapLinkOption(Toggle): class TrapLinkOption(Toggle):
"""If a trap is sent from Grinch, traps that are compatible with other games are triggered aswell. [NOT IMPLEMENTED]""" """If a trap is sent from Grinch, traps that are compatible with other games are triggered aswell. [NOT IMPLEMENTED]"""

View File

@@ -494,21 +494,21 @@ rules_dict: dict[str,list[list[str]]] = {
], ],
"GPS in Who Lake": [ "GPS in Who Lake": [
["Who Lake Vacuum Access", "Rotten Egg Launcher"] ["Who Lake Vacuum Access", "Rotten Egg Launcher"]
# ], ],
# "1st Crate Squashed": [ "1st Crate Squashed": [
# [] []
# ], ],
# "2nd Crate Squashed": [ "2nd Crate Squashed": [
# [] []
# ], ],
# "3rd Crate Squashed": [ "3rd Crate Squashed": [
# [] []
# ], ],
# "4th Crate Squashed": [ "4th Crate Squashed": [
# [] []
# ], ],
# "5th Crate Squashed": [ "5th Crate Squashed": [
# [] []
] ]
} }
@@ -542,7 +542,7 @@ access_rules_dict: dict[str,list[list[str]]] = {
# ["Progressive Vacuum Access": 2] # ["Progressive Vacuum Access": 2]
], ],
"Minefield": [ "Minefield": [
["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"], ["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"] ["Rotten Egg Launcher", "Grinch Copter"]
], ],
"Power Plant": [ "Power Plant": [

View File

@@ -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
import Options
from . import Options from .Options import GrinchOptions
from .Rules import access_rules_dict from .Rules import access_rules_dict
@@ -28,7 +29,10 @@ class GrinchWorld(World):
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 Options.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():
@@ -72,7 +76,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:

View File

@@ -8,24 +8,29 @@ BizHawk support.
- [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 supported, but I can't promise if any version is stable or not.
- 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.
## 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.
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
@@ -36,19 +41,4 @@ 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.