mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
96eb8fcd9a | ||
![]() |
d4bd682ac9 | ||
![]() |
61d4783f61 | ||
![]() |
00fff466ff | ||
![]() |
d8483bef6e | ||
![]() |
56a198fcfd | ||
![]() |
4e362dc722 | ||
![]() |
cfcfc9ecfd | ||
![]() |
a3a415adfd | ||
![]() |
14c95aa85b | ||
![]() |
8d941dad6f | ||
![]() |
8628f6637a | ||
![]() |
17b7914c35 | ||
![]() |
b8dfd5ce4c | ||
![]() |
b3749b7fe3 | ||
![]() |
3aaf625282 | ||
![]() |
9df2360b8b | ||
![]() |
d61ac9a135 | ||
![]() |
c8fc56d7c4 | ||
![]() |
51aad167cc | ||
![]() |
e2def66522 | ||
![]() |
73e9d9d577 | ||
![]() |
a5d7ff65c1 | ||
![]() |
05bf60abf7 | ||
![]() |
7f627e2c07 | ||
![]() |
19e0fe1286 | ||
![]() |
b390974019 | ||
![]() |
9da65fab09 | ||
![]() |
02d2eab5a4 | ||
![]() |
985c8b681b | ||
![]() |
cf5a4012c0 | ||
![]() |
c59e75ef7b | ||
![]() |
2dbe344348 | ||
![]() |
90ba4fbda7 | ||
![]() |
8ff2fb91d4 |
@@ -1,7 +1,10 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Sequence
|
||||
import asyncio
|
||||
import NetUtils
|
||||
import copy
|
||||
|
||||
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
|
||||
@@ -23,14 +26,20 @@ 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
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -65,7 +74,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
|
||||
@@ -79,13 +88,33 @@ class GrinchClient(BizHawkClient):
|
||||
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.")
|
||||
|
||||
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"] != 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:
|
||||
await ctx.get_username()
|
||||
|
||||
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
||||
from CommonClient import logger
|
||||
#If the player is not connected to an AP Server, or their connection was disconnected.
|
||||
if ctx.server is None or ctx.server.socket.closed or ctx.slot_data is None:
|
||||
if not ctx.slot:
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -102,14 +131,32 @@ class GrinchClient(BizHawkClient):
|
||||
# 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))
|
||||
pass
|
||||
except Exception as genericEx:
|
||||
# For all other errors, catch this and let the client gracefully disconnect
|
||||
logger.error("Unknown error occurred while playing the grinch. Error details: " + str(genericEx))
|
||||
await ctx.disconnect(False)
|
||||
pass
|
||||
|
||||
async def location_checker(self, ctx: "BizHawkClientContext"):
|
||||
from CommonClient import logger
|
||||
# Update the AP Server to know what locations are not checked yet.
|
||||
local_locations_checked: list[int] = []
|
||||
addr_list_to_read: list[tuple[int, int, str]] = []
|
||||
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)
|
||||
@@ -120,15 +167,15 @@ 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)
|
||||
if all(ram_checked_list):
|
||||
local_locations_checked.append(GrinchLocation.get_apid(grinch_loc_ram_data.id))
|
||||
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))
|
||||
|
||||
# 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)
|
||||
@@ -148,6 +195,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:]
|
||||
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
|
||||
|
||||
for item_received in new_items_only:
|
||||
local_item = ctx.item_names.lookup_in_game(item_received.item)
|
||||
@@ -167,12 +215,15 @@ 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")])
|
||||
addr_list_to_update.append((addr_to_update.ram_address,
|
||||
current_ram_address_value.to_bytes(addr_to_update.bit_size, "little"),"MainRAM"))
|
||||
|
||||
|
||||
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"):
|
||||
if not ctx.finished_game:
|
||||
@@ -189,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
|
||||
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]
|
||||
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)
|
||||
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
|
||||
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
|
||||
addr_list_to_update.append((0x0100F0, int(0).to_bytes(4, "little"), "MainRAM"))
|
||||
|
||||
for (item_name, item_data) in items_to_check.items():
|
||||
# If item is an event or already been received, ignore.
|
||||
@@ -210,30 +264,47 @@ class GrinchClient(BizHawkClient):
|
||||
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)
|
||||
addr_list_to_update.append((addr_to_update.ram_address,
|
||||
current_bin_value.to_bytes(1, "little"), "MainRAM"))
|
||||
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.
|
||||
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]
|
||||
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_data.id is None or GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||
if item_data.id is None: # or GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||
continue
|
||||
|
||||
# This assumes we don't have the item so we must set all the data to 0
|
||||
# This will either constantly update the item to ensure you still have it or take it away if you don't deserve it
|
||||
for addr_to_update in item_data.update_ram_addr:
|
||||
is_binary = True if not addr_to_update.binary_bit_pos is None else False
|
||||
if is_binary:
|
||||
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)
|
||||
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)
|
||||
addr_list_to_update.append((addr_to_update.ram_address,
|
||||
current_bin_value.to_bytes(1, "little"), "MainRAM"))
|
||||
else:
|
||||
await self.update_and_validate_address(ctx, addr_to_update.ram_address, 0, 1)
|
||||
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
|
||||
addr_list_to_update.append((addr_to_update.ram_address,
|
||||
addr_to_update.value.to_bytes(1, "little"), "MainRAM"))
|
||||
else:
|
||||
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"):
|
||||
from CommonClient import logger
|
||||
@@ -275,13 +346,43 @@ 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:
|
||||
|
||||
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": ctx.player_names[ctx.slot],
|
||||
"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
|
||||
current_egg_count = int.from_bytes(
|
||||
(await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little")
|
||||
current_egg_count = min(current_egg_count + egg_amount, 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.")
|
@@ -203,6 +203,7 @@ TRAPS_TABLE: dict[str, GrinchItemData] = {
|
||||
# "No Vac Trap": GrinchItemData("Traps", 610, IC.trap, [GrinchRamData(0x0102DA, value=0]),
|
||||
# "Invisible Trap": GrinchItemData("Traps", 611, IC.trap, [GrinchRamData(0x0102DA, value=0, bit_size=4)])
|
||||
# "Child Trap": GrinchItemData("Traps", 612, IC.trap,[GrinchRamData()])
|
||||
# "Disable Jump Trap": GrinchItemData("Traps", 613, IC.trap,[GrinchRamData(0x010026, binary_bit_pos=6)])
|
||||
}
|
||||
|
||||
#Movesets
|
||||
@@ -228,12 +229,15 @@ ALL_ITEMS_TABLE: dict[str, GrinchItemData] = {
|
||||
# Psuedocoding traplink table
|
||||
# BEE_TRAP_EQUIV = ["Army Trap", "Buyon Trap", "Ghost", "Gooey Bag", "OmoTrap", "Police Trap"]
|
||||
# ICE_TRAP_EQUIV = ["Chaos Control Trap", "Freeze Trap", "Frozen Trap", "Honey Trap", "Paralyze Trap", "Stun Trap", "Bubble Trap"]
|
||||
# DAMAGE_TRAP_EQUIV = ["Banana Trap", "Bomb", "Bonk Trap", "Fire Trap", "Laughter Trap", "Nut Trap", "Push Trap", "Squash Trap", "Thwimp Trap", "TNT Barrel Trap", "Meteor Trap"]
|
||||
# DAMAGE_TRAP_EQUIV = ["Banana Trap", "Bomb", "Bonk Trap", "Fire Trap", "Laughter Trap", "Nut Trap", "Push Trap",
|
||||
# "Squash Trap", "Thwimp Trap", "TNT Barrel Trap", "Meteor Trap", "Double Damage", "Spike Ball Trap"]
|
||||
|
||||
# SPRING_TRAP_EQUIV = ["Eject Ability", "Hiccup Trap", "Jump Trap", "Jumping Jacks Trap", "Whoops! Trap"]
|
||||
# HOME_TRAP_EQUIV = ["Blue Balls Curse", "Instant Death Trap"]
|
||||
# HOME_TRAP_EQUIV = ["Blue Balls Curse", "Instant Death Trap", "Get Out Trap"]
|
||||
# SLOWNESS_TRAP_EQUIV = ["Iron Boots Trap", "Slow Trap", "Sticky Floor Trap"]
|
||||
# CUTSCENE_TRAP_EQUIV = ["Phone Trap"]
|
||||
# ELEC_TRAP_EQUIV = []
|
||||
# DEPL_TRAP_EQUIV = ["Dry Trap"]
|
||||
|
||||
def grinch_items_to_id() -> dict[str, int]:
|
||||
item_mappings: dict[str, int] = {}
|
||||
|
@@ -56,14 +56,14 @@ grinch_locations = {
|
||||
"Launching Eggs Into Houses": GrinchLocationData("Whoville", "Whoville Missions", 203, [GrinchRamData(0x0100C7, value=10)]),
|
||||
"Modifying The Mayor's Statue": GrinchLocationData("City Hall", "Whoville Missions", 204, [GrinchRamData(0x0100BE, binary_bit_pos=1)]),
|
||||
"Advancing The Countdown-To-Xmas Clock": GrinchLocationData("Countdown to X-Mas Clock Tower", "Whoville Missions", 205, [GrinchRamData(0x0100BE, binary_bit_pos=2)]),
|
||||
"Squashing All Gifts in Whoville": GrinchLocationData("Whoville", "Whoville Missions", 206, [GrinchRamData(0x01005C, value=500)]),
|
||||
"Squashing All Gifts in Whoville": GrinchLocationData("Whoville", "Whoville Missions", 206, [GrinchRamData(0x01005C, value=500, bit_size=2)]),
|
||||
#Who Forest Missions
|
||||
"Making Xmas Trees Droop": GrinchLocationData("Who Forest", "Who Forest Missions", 300, [GrinchRamData(0x0100C8, value=10)]),
|
||||
"Sabotaging Snow Cannon With Glue": GrinchLocationData("Who Forest", "Who Forest Missions", 301, [GrinchRamData(0x0100BE, binary_bit_pos=3)]),
|
||||
"Putting Beehives In Cabins": GrinchLocationData("Who Forest", "Who Forest Missions", 302, [GrinchRamData(0x0100CA, value=10)]),
|
||||
"Sliming The Mayor's Skis": GrinchLocationData("Ski Resort", "Who Forest Missions", 303, [GrinchRamData(0x0100BE, binary_bit_pos=4)]),
|
||||
"Replacing The Candles On The Cake With Fireworks": GrinchLocationData("Civic Center", "Who Forest Missions", 304, [GrinchRamData(0x0100BE, binary_bit_pos=5)]),
|
||||
"Squashing All Gifts in Who Forest": GrinchLocationData("Who Forest", "Who Forest Missions", 305, [GrinchRamData(0x01005E, value=750)]),
|
||||
"Squashing All Gifts in Who Forest": GrinchLocationData("Who Forest", "Who Forest Missions", 305, [GrinchRamData(0x01005E, value=750, bit_size=2)]),
|
||||
#Who Dump Missions
|
||||
"Stealing Food From Birds": GrinchLocationData("Who Dump", "Who Dump Missions", 400, [GrinchRamData(0x0100CB, value=10)]),
|
||||
"Feeding The Computer With Robot Parts": GrinchLocationData("Who Dump", "Who Dump Missions", 401, [GrinchRamData(0x0100BF, binary_bit_pos=2)]),
|
||||
@@ -71,14 +71,14 @@ grinch_locations = {
|
||||
"Conducting The Stinky Gas To Who-Bris' Shack": GrinchLocationData("Who Dump", "Who Dump Missions", 403, [GrinchRamData(0x0100BE, binary_bit_pos=7)]),
|
||||
"Shaving Who Dump Guardian": GrinchLocationData("Minefield", "Who Dump Missions", 404, [GrinchRamData(0x0100BF, binary_bit_pos=0)]),
|
||||
"Short-Circuiting Power-Plant": GrinchLocationData("Generator Building", "Who Dump Missions", 405, [GrinchRamData(0x0100BF, binary_bit_pos=1)]),
|
||||
"Squashing All Gifts in Who Dump": GrinchLocationData("Who Dump", "Who Dump Missions", 406, [GrinchRamData(0x010060, value=750)]),
|
||||
"Squashing All Gifts in Who Dump": GrinchLocationData("Who Dump", "Who Dump Missions", 406, [GrinchRamData(0x010060, value=750, bit_size=2)]),
|
||||
#Who Lake Missions
|
||||
"Putting Thistles In Shorts": GrinchLocationData("Who Lake", "Who Lake Missions", 500, [GrinchRamData(0x0100E5, value=10)]),
|
||||
"Sabotaging The Tents": GrinchLocationData("Who Lake", "Who Lake Missions", 501, [GrinchRamData(0x0100E6, value=10)]),
|
||||
"Drilling Holes In Canoes": GrinchLocationData("North Shore", "Who Lake Missions", 502, [GrinchRamData(0x0100EE, value=10)]),
|
||||
"Modifying The Marine Mobile": GrinchLocationData("Submarine World", "Who Lake Missions", 503, [GrinchRamData(0x0100BF, binary_bit_pos=4)]),
|
||||
"Hooking The Mayor's Bed To The Motorboat": GrinchLocationData("Mayor's Villa", "Who Lake Missions", 504, [GrinchRamData(0x0100BF, binary_bit_pos=3)]),
|
||||
"Squashing All Gifts in Who Lake": GrinchLocationData("Who Lake", "Who Lake Missions", 505, [GrinchRamData(0x010062, value=1000)]),
|
||||
"Squashing All Gifts in Who Lake": GrinchLocationData("Who Lake", "Who Lake Missions", 505, [GrinchRamData(0x010062, value=1000, bit_size=2)]),
|
||||
#Need to find binary values for individual blueprints, but all ram addresses are found
|
||||
#Blueprints
|
||||
#Binoculars Blueprints
|
||||
@@ -182,6 +182,12 @@ grinch_locations = {
|
||||
"Tires in Who Dump": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1602, [GrinchRamData(0x0101FB, binary_bit_pos=4)]),
|
||||
"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)]),
|
||||
}
|
||||
|
||||
def grinch_locations_to_id() -> dict[str,int]:
|
||||
|
@@ -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]"""
|
||||
@@ -80,7 +80,6 @@ class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin
|
||||
progressive_vacuum: ProgressiveVacuum
|
||||
missionsanity: Missionsanity
|
||||
annoying_locations: AnnoyingLocations
|
||||
minigamesanity: Supadow
|
||||
progressive_gadget: ProgressiveGadget
|
||||
supadow_minigames: Supadow
|
||||
giftsanity: Gifts
|
||||
|
@@ -26,7 +26,7 @@ def interpret_rule(rule_set: list[list[str]], player: int):
|
||||
|
||||
access_list: list[Callable[[CollectionState], bool]] = []
|
||||
for item_set in rule_set:
|
||||
access_list.append(lambda state: state.has_all(item_set, player))
|
||||
access_list.append(lambda state, items=tuple(item_set): state.has_all(items, player))
|
||||
return access_list
|
||||
|
||||
#Each item in the list is a separate list of rules. Each separate list is just an "OR" condition.
|
||||
@@ -295,7 +295,7 @@ rules_dict: dict[str,list[list[str]]] = {
|
||||
# ["Max"]
|
||||
],
|
||||
"OCD Blueprint - Guardian's House - Right Side": [
|
||||
["Rotten Egg Launcher", "Grinch Copter"],
|
||||
["Grinch Copter"],
|
||||
["Slime Shooter", "Rocket Spring"]
|
||||
],
|
||||
"OCD Blueprint - Inside Guardian's House": [
|
||||
@@ -495,6 +495,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": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -560,19 +575,23 @@ access_rules_dict: dict[str,list[list[str]]] = {
|
||||
["Sleigh Room Key"]
|
||||
],
|
||||
"Spin N' Win Supadow": [
|
||||
["Spin N' Win Door Unlock"],
|
||||
[]
|
||||
# ["Spin N' Win Door Unlock"],
|
||||
# ["Progressive Supadow Door Unlock"]
|
||||
],
|
||||
"Dankamania Supadow": [
|
||||
["Dankamania Door Unlock"],
|
||||
[]
|
||||
# ["Dankamania Door Unlock"],
|
||||
# ["Progressive Supadow Door Unlock: 2"]
|
||||
],
|
||||
"The Copter Race Contest Supadow": [
|
||||
["The Copter Race Contest Door Unlock"],
|
||||
[]
|
||||
# ["The Copter Race Contest Door Unlock"],
|
||||
# ["Progressive Supadow Door Unlock: 3"]
|
||||
],
|
||||
"Bike Race": [
|
||||
["Bike Race Access"],
|
||||
[]
|
||||
# ["Bike Race Access"],
|
||||
# ["Progressive Supadow Door Unlock: 4"]
|
||||
]
|
||||
}
|
@@ -8,15 +8,16 @@ 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
|
||||
|
||||
|
||||
class GrinchWorld(World):
|
||||
game: ClassVar[str] = "The Grinch"
|
||||
options_dataclass = Options.GrinchOptions
|
||||
options = Options.GrinchOptions
|
||||
options: Options.GrinchOptions
|
||||
topology_present = True #not an open world game, very linear
|
||||
item_name_to_id: ClassVar[dict[str,int]] = grinch_items_to_id()
|
||||
location_name_to_id: ClassVar[dict[str,int]] = grinch_locations_to_id()
|
||||
@@ -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:
|
||||
|
@@ -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]`)
|
Reference in New Issue
Block a user