21 Commits

Author SHA1 Message Date
MarioSpore
e17895902e Add unique id to compensate for multiple grinch games in one multiworld 2025-09-13 00:58:12 -04:00
MarioSpore
1596550111 Merge pull request #2 from MarioSpore/devjake
Fixed previous fixes for address optimization.
2025-09-12 23:16:01 -04:00
SomeJakeGuy
c888e17845 Fixed previous fixes for address optimization. 2025-09-12 23:10:19 -04:00
MarioSpore
3dee611b51 Fixed double receive eggs to self 2025-09-12 22:22:54 -04:00
MarioSpore
d6c7a04316 Fixed negative int bug 2025-09-12 21:27:11 -04:00
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 197 additions and 132 deletions

View File

@@ -1,8 +1,10 @@
import time
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence
import asyncio
import NetUtils
import copy
import uuid
import Utils
from .Locations import grinch_locations, GrinchLocation
from .Items import ALL_ITEMS_TABLE, MISSION_ITEMS_TABLE, GADGETS_TABLE, KEYS_TABLE, GrinchItemData #, SLEIGH_PARTS_TABLE
import worlds._bizhawk as bizhawk
@@ -24,20 +26,28 @@ MAX_DEMO_MODE_CHECK = 30
# List of Menu Map IDs
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):
game = "The Grinch"
system = "PSX"
patch_suffix = ".apgrinch"
items_handling = 0b111
demo_mode_buffer = 0
last_map_location = -1
ingame_log = False
demo_mode_buffer: int = 0
last_map_location: int = -1
ingame_log: bool = False
previous_egg_count: int = 0
send_ring_link: bool = False
unique_client_id: int = 0
def __init__(self):
super().__init__()
self.last_received_index = 0
self.loading_bios_msg = False
self.loc_unlimited_eggs = False
self.unique_client_id = 0
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
from CommonClient import logger
@@ -66,7 +76,7 @@ class GrinchClient(BizHawkClient):
ctx.game = self.game
ctx.items_handling = self.items_handling
ctx.want_slot_data = True
ctx.watcher_timeout = 0.25
ctx.watcher_timeout = 0.125
self.loading_bios_msg = False
return True
@@ -77,11 +87,29 @@ class GrinchClient(BizHawkClient):
match cmd:
case "Connected": # On Connect
self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"])
self.unique_client_id = self._get_uuid()
logger.info("You are now connected to the client. "+
"There may be a slight delay to check you are not in demo mode before locations start to send.")
# tags = args.get("tags", [])
# if "RingLink" in tags:
# ring_link_input(self, args["data"])
ring_link_enabled = bool(ctx.slot_data["ring_link"])
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"] != self.unique_client_id:
Utils.async_start(self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs")
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
await ctx.get_username()
@@ -101,7 +129,6 @@ class GrinchClient(BizHawkClient):
await self.goal_checker(ctx)
await self.option_handler(ctx)
await self.constant_address_update(ctx)
# await self.ring_link_input(args["args"])
except bizhawk.RequestFailedError as ex:
# The connector didn't respond. Exit handler and return to main loop to reconnect
@@ -117,9 +144,22 @@ class GrinchClient(BizHawkClient):
from CommonClient import logger
# Update the AP Server to know what locations are not checked yet.
local_locations_checked: list[int] = []
addr_list_to_read: list[tuple[int, int, str]] = []
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:
# 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.
# 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)
@@ -130,13 +170,13 @@ class GrinchClient(BizHawkClient):
ram_checked_list: list[bool] = []
for addr_to_update in grinch_loc_ram_data.update_ram_addr:
is_binary = True if not addr_to_update.binary_bit_pos is None else False
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")
orig_index: int = addr_list_to_read.index((addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM"))
value_read_from_bizhawk: int = int.from_bytes(returned_bytes[orig_index], "little")
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:
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):
local_locations_checked.append(GrinchLocation.get_apid(grinch_loc_ram_data.id))
@@ -158,6 +198,7 @@ class GrinchClient(BizHawkClient):
# Ensures we only get the new items that we want to give the player
new_items_only = ctx.items_received[self.last_received_index:]
ram_addr_dict: dict[int, list[int]] = {}
for item_received in new_items_only:
local_item = ctx.item_names.lookup_in_game(item_received.item)
@@ -165,8 +206,11 @@ 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 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.bit_size, "MainRAM")]))[0], "little")
if is_binary:
current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos))
elif addr_to_update.update_existing_value:
@@ -177,12 +221,13 @@ class GrinchClient(BizHawkClient):
current_ram_address_value = addr_to_update.value
# 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)
# await bizhawk.write(ctx.bizhawk_ctx, [(addr_to_update.ram_address,
# current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"), "MainRAM")])
ram_addr_dict[addr_to_update.ram_address] = [current_ram_address_value, addr_to_update.bit_size]
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"):
if not ctx.finished_game:
@@ -199,14 +244,16 @@ class GrinchClient(BizHawkClient):
# This function's entire purpose is to take away items we physically received ingame, but have not received from AP
async def remove_physical_items(self, ctx: "BizHawkClientContext"):
ram_addr_dict: dict[int, list[int]] = {}
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE
heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570))
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
await self.update_and_validate_address(ctx,0x0100F0, 0, 4)
# Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions
ram_addr_dict[0x0100F0] = [0, 4]
for (item_name, item_data) in items_to_check.items():
# If item is an event or already been received, ignore.
@@ -217,15 +264,31 @@ class GrinchClient(BizHawkClient):
for addr_to_update in item_data.update_ram_addr:
is_binary = True if not addr_to_update.binary_bit_pos is None else False
if is_binary:
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")
if addr_to_update.ram_address in ram_addr_dict.keys():
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
else:
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
current_bin_value &= ~(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:
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.bit_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.
async def constant_address_update(self, ctx: "BizHawkClientContext"):
ram_addr_dict: dict[int, list[int]] = {}
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE}
@@ -238,18 +301,24 @@ class GrinchClient(BizHawkClient):
for addr_to_update in item_data.update_ram_addr:
is_binary = True if not addr_to_update.binary_bit_pos is None else False
if is_binary:
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")
if addr_to_update.ram_address in ram_addr_dict.keys():
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
else:
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
current_bin_value |= (1 << addr_to_update.binary_bit_pos)
else:
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:
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.bit_size]
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.bit_size]
await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict))
async def ingame_checker(self, ctx: "BizHawkClientContext"):
from CommonClient import logger
@@ -291,49 +360,51 @@ class GrinchClient(BizHawkClient):
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(2,"little"), "MainRAM")])
await bizhawk.write(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):
await bizhawk.write(ctx.bizhawk_ctx, [(address_to_validate, expected_value.to_bytes(byte_size, "little"), "MainRAM")])
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:
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"):
from CommonClient import logger
while self.send_ring_link and ctx.slot:
# async def ring_link_output(self, ctx: "BizHawkClientContext", byte_size: int):
# bizhawk.seek(0x010058)
# byte_size = 2
# 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
#
# if difference != 0:
# # logger.info("got here with a difference of " + str(difference))
# msg = {
# "cmd": "Bounce",
# "slots": [ctx.slot],
# "data": {
# "time": time.time(),
# "source": ctx.slot,
# "amount": difference
# },
# "tags": ["RingLink"]
# }
# 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
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

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)]),
"GPS in Who Lake": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1604, [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
# Mount Crumpit Locations
# "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)]),
# "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)]),
# "5th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1704, [GrinchRamData(0x095343, value=5)]),
"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)]),
"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)]),
"5th Crate Squashed": GrinchLocationData("Mount Crumpit", "Mount Crumpit", 1704, [GrinchRamData(0x095343, value=5)]),
}
def grinch_locations_to_id() -> dict[str,int]:

View File

@@ -69,7 +69,7 @@ class UnlimitedEggs(Toggle):
display_name = "Unlimited Rotten Eggs"
class RingLinkOption(Toggle):
"""Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled. [NOT IMPLEMENTED]"""
"""Whenever this is toggled, your ammo is linked with other ringlink-compatible games that also have this enabled."""
class TrapLinkOption(Toggle):
"""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": [
["Who Lake Vacuum Access", "Rotten Egg Launcher"]
# ],
# "1st Crate Squashed": [
# []
# ],
# "2nd Crate Squashed": [
# []
# ],
# "3rd Crate Squashed": [
# []
# ],
# "4th Crate Squashed": [
# []
# ],
# "5th Crate Squashed": [
# []
],
"1st Crate Squashed": [
[]
],
"2nd Crate Squashed": [
[]
],
"3rd Crate Squashed": [
[]
],
"4th Crate Squashed": [
[]
],
"5th Crate Squashed": [
[]
]
}
@@ -542,7 +542,7 @@ access_rules_dict: dict[str,list[list[str]]] = {
# ["Progressive Vacuum Access": 2]
],
"Minefield": [
["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"],
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"Power Plant": [

View File

@@ -8,8 +8,9 @@ from .Client import *
from typing import ClassVar
from worlds.AutoWorld import World
import Options
from . import Options
from .Options import GrinchOptions
from .Rules import access_rules_dict
@@ -28,7 +29,10 @@ class GrinchWorld(World):
super(GrinchWorld, self).__init__(*args, **kwargs)
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
for region_name in access_rules_dict.keys():
@@ -72,7 +76,7 @@ class GrinchWorld(World):
def fill_slot_data(self):
return {
"give_unlimited_eggs": self.options.unlimited_eggs.value,
"ring_link": self.options.ring_link.value,
}
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.
- 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
guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
## Generating a Game
### Where do I get a config file?
The Player options page on the website allows you to configure your personal
options and export a config file from them: [The Grinch Player Options Page](../player-options)
### 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
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).
3. Open `ArchipelagoLauncher.exe`
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.
### 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
press enter (if the server uses a password, type in the bottom text field
`/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.
`/connect <address>:<port> [password]`)