123 Commits
main ... v1.1.0

Author SHA1 Message Date
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
MarioSpore
b3749b7fe3 Somehow, OR conditional logic was STILL not being considered. This should fix it. 2025-09-07 12:56:26 -04:00
MarioSpore
3aaf625282 Comment out mount crumpit checks until v1.1 2025-09-06 21:54:53 -04:00
MarioSpore
9df2360b8b Adds no jump trap 2025-09-06 21:03:08 -04:00
MarioSpore
d61ac9a135 Implement crate tutorial checks and logic 2025-09-06 21:02:54 -04:00
MarioSpore
c8fc56d7c4 No longer requires REL if you have GC for the guardian house right side location 2025-09-06 20:10:52 -04:00
MarioSpore
51aad167cc Update ap connection detection to only after the slot name is entered and you fully connect 2025-09-06 20:05:36 -04:00
MarioSpore
e2def66522 Added comment explaining recently added except block 2025-09-06 19:43:57 -04:00
MarioSpore
73e9d9d577 Added 2nd exception if theres other error types while playing the game 2025-09-06 19:37:46 -04:00
MarioSpore
a5d7ff65c1 Update constant address update to always give or take away mission specific items/keys 2025-09-06 19:22:34 -04:00
MarioSpore
05bf60abf7 Part 2 of fixing test_default_all_state_can_reach_everything to comply with banadium 2025-09-06 17:26:38 -04:00
MarioSpore
7f627e2c07 Remove duplication of "Supadow" option 2025-09-06 17:00:59 -04:00
MarioSpore
19e0fe1286 Temporairly disable supadow regions to comply with banadium to test_default_all_state_can_reach_everything 2025-09-06 16:59:00 -04:00
MarioSpore
b390974019 Fixes
"AssertionError: True is not false : Unexpected assignment to GrinchWorld.options!"
2025-09-06 16:50:38 -04:00
MarioSpore
9da65fab09 psuedocode more traplink 2025-09-05 20:18:33 -04:00
MarioSpore
02d2eab5a4 psuedocode more traplink 2025-09-05 20:18:20 -04:00
MarioSpore
985c8b681b ring link psuedocode part 3 2025-09-05 00:09:25 -04:00
MarioSpore
cf5a4012c0 ring link psuedocode part 2 2025-09-05 00:02:01 -04:00
MarioSpore
c59e75ef7b Ring link psuedocode, thanks for graymondgt for getting this started 2025-09-04 23:48:53 -04:00
MarioSpore
2dbe344348 More trap link psuedo code 2025-09-04 22:51:14 -04:00
MarioSpore
90ba4fbda7 Minor hotfix that reads the correct bit_size for Squashing All Gifts missions 2025-09-01 19:10:04 -04:00
MarioSpore
8ff2fb91d4 Minor fix to prevent trigger from occuring too early 2025-09-01 15:49:09 -04:00
MarioSpore
ee1190cf12 Make Sleigh Room Key no longer skip balancing 2025-08-31 16:29:22 -04:00
MarioSpore
b7315a9991 Make all accesses be set to 0 for cutscene triggers on 3 missions each area 2025-08-31 16:23:46 -04:00
MarioSpore
e76dd67ff6 Made Sleigh parts table in Client.py to not be imported 2025-08-31 13:59:20 -04:00
MarioSpore
a16de9da0a Reworded connection message 2025-08-31 13:47:30 -04:00
MarioSpore
af0527f9a6 Message send upon connection to wait to send locations 2025-08-31 13:45:51 -04:00
MarioSpore
04bb867805 Removes sleigh parts table from constant ram addresses & hardcodes Who Lake mission count to prevent warping to sleigh room without being softlocked 2025-08-31 13:21:54 -04:00
MarioSpore
5cfbf84519 Reworked logic to no longer require Sleigh parts and only to require gadgets and vacuum accesses to get to the sleigh part locations instead. 2025-08-31 13:20:59 -04:00
MarioSpore
a00cd0212a Comment out Sleigh parts for another day 2025-08-31 13:20:12 -04:00
MarioSpore
61885767d5 Adds sleigh part collections as locations 2025-08-31 10:44:30 -04:00
MarioSpore
b0619d5751 Fix removing physical item binary nonsense 2025-08-30 19:59:56 -04:00
MarioSpore
769ab01d19 Correct address found for sleigh room door 2025-08-30 19:24:31 -04:00
MarioSpore
b918d96294 Fix update constant ram address to only trigger when new locations are sent to AP 2025-08-30 17:26:06 -04:00
MarioSpore
dc76761fc8 Updated proper logging & fixed logic for demo mode detection 2025-08-30 16:33:21 -04:00
MarioSpore
c48bca965e Fix location & region logic not considering OR logic via multiple lists of items 2025-08-30 16:32:56 -04:00
MarioSpore
0e294f53ec Commented out 0FBF25 address since it does not work due issues ingame 2025-08-30 09:10:16 -04:00
MarioSpore
cf7f16d36b Changed item received addresses among other removals that became useless 2025-08-30 09:09:35 -04:00
MarioSpore
78bc54d227 Duplicate IDS related to Heart of Stone - Who Lake & Who Dump fix 2025-08-28 15:38:56 -04:00
MarioSpore
30fd16bdb8 Temporairly disables 0x0100BC due to cutscene spam, may implement in the future to skip computer 2025-08-28 00:20:01 -04:00
MarioSpore
95fb26f20c Added heartsanity, finally 2025-08-27 21:34:03 -04:00
MarioSpore
8c07ca8c81 Fix Sleigh Room door issues 2025-08-26 22:38:39 -04:00
MarioSpore
fc31e2f442 Adds delay for demo mode to mitigate visitsanity location sends 2025-08-26 21:28:02 -04:00
MarioSpore
bfca1df83c Comment out keys and gadget tables to prevent cutscene loops 2025-08-25 21:34:41 -04:00
MarioSpore
41e55b169f Remove unused imports 2025-08-25 19:48:45 -04:00
MarioSpore
e8e9c76eda Adds additional ram address for Scissors to allow triggering cutscene for Shaving Dump Guardian mission 2025-08-25 19:45:01 -04:00
MarioSpore
ff9c7480db Specific conditions for handling heart of stones and sleigh room key for preventing vanilla items sent 2025-08-25 18:49:44 -04:00
MarioSpore
5444ea7061 Reset locations ingame through loops that I do not deserve 2025-08-25 18:27:19 -04:00
MarioSpore
069355778d Fixes item validation issues with traps 2025-08-25 17:07:21 -04:00
MarioSpore
9a6f6f7a75 Fixes the loop issue that doesn't check all RAM addresses before it actually marks the location as "checked" 2025-08-25 17:03:48 -04:00
MarioSpore
7105187ad3 Update traps for traplink and maybe Child Trap 2025-08-24 11:01:10 -04:00
MarioSpore
98b971a659 Future-proofing Max logic when movesanity gets implemented 2025-08-24 11:00:31 -04:00
MarioSpore
e6430b2f86 Switched addresses for tents and thistle shorts mission 2025-08-22 22:19:09 -04:00
MarioSpore
4a6f4fce4f Temporarily disable filler & trap items due to unstable behavior ingame 2025-08-22 22:18:30 -04:00
MarioSpore
837e651d7b Forgot to comment out the await function to fully disable opening the sleigh room door if you receive all the sleigh parts 2025-08-19 23:23:11 -04:00
MarioSpore
4d1d728db1 Fix Modifying The Mayor's Statue using wrong bit position 2025-08-19 23:22:29 -04:00
MarioSpore
3dc4802be7 Logic now requires Sleigh Room Key to goal along with other minor changes to reflect this 2025-08-19 22:29:00 -04:00
MarioSpore
08e9df66de Default annoying locations to on (Even though this literally does nothing) 2025-08-19 02:00:33 -04:00
MarioSpore
14d7bdba15 Prepare for reset addresses in locations when items are sent in vanilla 2025-08-19 02:00:11 -04:00
MarioSpore
5eaf551584 Fix locations not sending, also makes unlimited_eggs option officially works. ALSO remembers items sent on disconnect! 2025-08-19 01:59:18 -04:00
MarioSpore
e941e8bdbf Fix issue with having more items than locations, the (-3) is a placeholder until we officially add in code that compensates the 3 added heart of stones 2025-08-18 16:47:12 -04:00
MarioSpore
659ae21fa7 Possible fixes to OverflowError during releases and RuntimeError when certain number of locations are sent. Needs to be tested. 2025-08-17 21:22:12 -04:00
MarioSpore
032dd8712e Move interpret_rule to the top for ease of access 2025-08-17 21:20:54 -04:00
MarioSpore
5caacaac87 Add underscore for "annoyinglocations" option 2025-08-17 00:17:58 -04:00
MarioSpore
95e80227e1 Does not require REL for a blueprint and Shaving the Dump Guardian mission 2025-08-17 00:17:42 -04:00
MarioSpore
dced197dc4 Add max count & allow sleigh room to open 2025-08-16 02:26:25 -04:00
MarioSpore
3549e55c59 Functioning goal 2025-08-16 02:24:19 -04:00
MarioSpore
24d1b96b9e Forgot to rename location in Rules.py to reflect Location.py changes 2025-08-15 22:03:33 -04:00
MarioSpore
76b4ff2a6e Readjusted positioning of options 2025-08-15 22:03:09 -04:00
MarioSpore
d9e300e0fd Location name adjustment to be "Inside" instead of "Front of" for the REL Blueprint before mission completion 2025-08-15 21:03:53 -04:00
MarioSpore
98e2486292 You can logically get Minefield blueprints by just using Max. He is not affected by the mines. 2025-08-15 21:03:12 -04:00
MarioSpore
044fdaa717 Various code changes that handles unlimited rotten eggs option, checks no longer sending during demo/main menu, have certain items, if set to true in Items.py, to add/remove instead of setting, and logs to display when you are in BIOS and you need to wait a bit. 2025-08-14 00:23:40 -04:00
MarioSpore
922232264d Unlimited rotten eggs option is officially implemented, removes comment that initially says "Not Implemented" 2025-08-14 00:21:15 -04:00
MarioSpore
92dafd0a73 Sets items to be added if set to true 2025-08-14 00:20:12 -04:00
MarioSpore
a010080371 Made Who Cloak, Cable Car Access Card, & Scout Clothes progression since they unlock entire regions 2025-08-11 21:37:12 -04:00
MarioSpore
849691b009 New official item classifications for 0.6.3 update 2025-08-11 21:20:11 -04:00
MarioSpore
912c4db021 Merge remote-tracking branch 'origin/dev' into dev 2025-08-11 20:48:06 -04:00
MarioSpore
d91ade58ee Subtle changes to make apworld work with 0.6.3 2025-08-11 20:47:38 -04:00
MarioSpore
90d02672b5 Merge branch 'ArchipelagoMW:main' into dev 2025-08-11 20:31:03 -04:00
MarioSpore
8ba0bbc73a More address and bit adjustments 2025-08-08 20:16:31 -04:00
MarioSpore
59e4a6c1e3 Add new yaml option that allows user to exclude checks that are considered annoying (Not implemented) 2025-08-08 18:27:22 -04:00
MarioSpore
a2b1f885a5 Marine Mobile blueprints readjusted values 2025-08-08 18:13:12 -04:00
MarioSpore
ceec3ed28b Adjusts yaml option positions 2025-08-07 22:49:14 -04:00
MarioSpore
7ad1211960 Minor tweaks that moves comments 2025-08-07 22:49:00 -04:00
MarioSpore
3b7a6554ac Documentation updates 2025-08-07 22:48:24 -04:00
MarioSpore
a49921392b More bit flips and address updates 2025-08-07 20:33:02 -04:00
MarioSpore
f71038d17c Mere typo on recently added "Progressive Gadgets" option 2025-08-07 18:13:19 -04:00
MarioSpore
ea4f03118b Adds progressive gadget option when it's ready 2025-08-07 18:11:56 -04:00
MarioSpore
54d99f5b54 Bit flip 2025-08-07 18:11:32 -04:00
MarioSpore
f38a5fbadd Items are now working! 2025-08-07 00:38:42 -04:00
MarioSpore
4123961e81 Working locations! 2025-08-06 23:34:52 -04:00
MarioSpore
30f800f648 Ram address update change to get locations recognized as well as items to be received 2025-08-06 23:34:30 -04:00
MarioSpore
2c5cb791a6 GPS no longer required to goal, rest of sleigh parts needed for goaling according to speedrunning community 2025-08-05 17:00:27 -04:00
MarioSpore
397693c8a8 Psuedocoding client stuff that includes the ram address & hex for the rom's name. 2025-08-04 23:03:31 -04:00
MarioSpore
3541e13f21 Added Heart of Stone and Supadow logic to prepare for it's eventual implementation 2025-08-03 19:43:54 -04:00
MarioSpore
0f2851e1b3 Various location names to make them shorter, along with adding a few comments and preparing traplink option. Also added "Sqaushing all gifts" missions. 2025-08-03 18:40:12 -04:00
MarioSpore
b0a9831082 Made Progressive Vacuum disabled by default due to not implemented functionality yet.
Also, changed a couple for loops in Regions
2025-08-03 14:37:52 -04:00
MarioSpore
cf921a8f54 Regions now connecting as god intended 2025-08-03 13:39:32 -04:00
MarioSpore
b622953cd0 Current setup of rules that revert back to generation. Currently has the issue of sphere 1 goal 2025-07-29 00:55:06 -04:00
MarioSpore
64cca7fff9 Rules changes for Bootsies to look into 2025-07-29 00:36:06 -04:00
MarioSpore
1762fefba9 Filler is now being placed yippe 2025-07-28 23:12:47 -04:00
MarioSpore
7e06efb1d0 Logic built and literally have an apworld that can generate a yaml and stuff 2025-07-28 00:53:04 -04:00
MarioSpore
c3ddce5b1a Trap changes for planned changes for future traplink 2025-07-26 23:09:39 -04:00
MarioSpore
3c622eefe4 All locations have their affiliated addresses ready! Started region implementation 2025-07-26 20:51:05 -04:00
MarioSpore
17aebc940a Almost every location ram address found :>) 2025-07-26 17:34:43 -04:00
MarioSpore
110da2b524 More addresses added that I found via adjacent addresses. As well as rewording in Credits.md due to sounding like the cheatcodes made it harder to find the addresses. 2025-07-26 14:56:29 -04:00
MarioSpore
10fa86d52c Some removals along with address binary findings for locations. Also credits 2025-07-26 13:22:07 -04:00
MarioSpore
bede861e3d Funny haha inconsistent ram data go brrrrrrrrrrrrrrrrrrrrrrrr 2025-07-26 00:30:19 -04:00
MarioSpore
694ba4c9bb Moar code before thunderstorm 2025-07-25 19:33:51 -04:00
MarioSpore
a7d5d45d14 Initial files 2025-07-25 16:24:08 -04:00
12 changed files with 1791 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Archipelago Unittests" type="tests" factoryName="Unittests">
<module name="Archipelago" />
<module name="Grinch-AP" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />

388
worlds/grinch/Client.py Normal file
View File

@@ -0,0 +1,388 @@
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
from worlds._bizhawk.client import BizHawkClient
if TYPE_CHECKING:
from worlds._bizhawk.context import BizHawkClientContext
from CommonClient import logger
# Stores received index of last item received in PS1 memory card save data
# By storing this index, it will remember the last item received and prevent item duplication loops
RECV_ITEM_ADDR = 0x010068
RECV_ITEM_BITSIZE = 4
# Maximum number of times we check if we are in demo mode or not
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: 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__()
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
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:
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
def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
from CommonClient import logger
super().on_package(ctx, cmd, args)
match cmd:
case "Connected": # On Connect
self.loc_unlimited_eggs = bool(ctx.slot_data["give_unlimited_eggs"])
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 not ctx.slot:
return
try:
if not await self.ingame_checker(ctx):
return
await self.location_checker(ctx)
await self.receiving_items_handler(ctx)
await self.goal_checker(ctx)
await self.option_handler(ctx)
await self.constant_address_update(ctx)
except bizhawk.RequestFailedError as ex:
# 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:
# 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)
grinch_loc_ram_data = grinch_locations[grinch_loc_name]
# Grinch ram data may have more than one address to update, so we are going to loop through all addresses in a location
# We use a list here to keep track of all our checks. If they are all true, then and only then do we mark that location as checked.
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
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((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 == 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)
if len(locations_sent_to_ap) > 0:
await self.remove_physical_items(ctx)
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
# If the list says that we have 3 items and we already received items, we will ignore and continue.
# Otherwise, we will get the new items and give them to the player.
self.last_received_index = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
RECV_ITEM_ADDR, RECV_ITEM_BITSIZE, "MainRAM")]))[0], "little")
if len(ctx.items_received) == self.last_received_index:
return
# 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)
grinch_item_ram_data = ALL_ITEMS_TABLE[local_item]
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 = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos))
elif addr_to_update.update_existing_value:
# Grabs minimum value of a list of numbers and makes sure it does not go above max count possible
current_ram_address_value += addr_to_update.value
current_ram_address_value = min(current_ram_address_value, addr_to_update.max_count)
else:
current_ram_address_value = addr_to_update.value
# Write the updated value back into RAM
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
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:
goal_loc = grinch_locations["Neutralizing Santa"]
goal_ram_address = goal_loc.update_ram_addr[0]
current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
goal_ram_address.ram_address, goal_ram_address.bit_size, "MainRAM")]))[0], "little")
if (current_ram_address_value & (1 << goal_ram_address.binary_bit_pos)) > 0:
ctx.finished_game = True
await ctx.send_msgs([{
"cmd": "StatusUpdate",
"status": NetUtils.ClientStatus.CLIENT_GOAL,
}])
# This function's entire purpose is to take away items we physically received ingame, but have not received from AP
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"]
addr_list_to_update.append((heart_item_data.update_ram_addr[0].ram_address,
min(heart_count, 4).to_bytes(1, "little"), "MainRAM"))
# 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.
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
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)
addr_list_to_update.append((addr_to_update.ram_address,
current_bin_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)
# 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:
continue
# 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")
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:
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
ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
0x010000, 1, "MainRAM")]))[0], "little")
#If not in game or at a menu, or loading the publisher logos
if ingame_map_id <= 0x04 or ingame_map_id >= 0x35:
return False
#If grinch has changed maps
if not ingame_map_id == self.last_map_location:
# If the last "map" we were on was a menu or a publisher logo
if self.last_map_location in MENU_MAP_IDS:
# Reset our demo mode checker just in case the game is in demo mode.
self.demo_mode_buffer = 0
self.ingame_log = False
return False
# Update the previous map we were on to be the current map.
self.last_map_location = ingame_map_id
# Use this as a delayed check to make sure we are in game
if not self.demo_mode_buffer == MAX_DEMO_MODE_CHECK:
await asyncio.sleep(0.1)
self.demo_mode_buffer += 1
return False
demo_mode = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
0x01008A, 1, "MainRAM")]))[0], "little")
if demo_mode == 1:
return False
if not self.ingame_log:
logger.info("You can now start sending locations from the Grinch!")
self.ingame_log = True
return True
async def option_handler(self, ctx: "BizHawkClientContext"):
if self.loc_unlimited_eggs:
await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, MAX_EGGS.to_bytes(2,"little"), "MainRAM")])
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.")

246
worlds/grinch/Items.py Normal file
View File

@@ -0,0 +1,246 @@
from typing import NamedTuple, Optional
from .RamHandler import GrinchRamData
from BaseClasses import Item
from BaseClasses import ItemClassification as IC #IC can be any name, saves having to type the whole word in code
class GrinchItemData(NamedTuple):
item_group: str #arbituary that can be whatever it can be, basically the field/property for item groups
id: Optional[int]
classification: IC
update_ram_addr: list[GrinchRamData]
second_item_group: Optional[str] = None
class GrinchItem(Item):
game: str = "The Grinch"
#Tells server what item id it is
@staticmethod
def get_apid(id: int):
#If you give me an input id, I will return the Grinch equivalent server/ap id
base_id: int = 42069
return base_id + id if id is not None else None
def __init__(self, name: str, player: int, data: GrinchItemData):
super(GrinchItem, self).__init__(name,data.classification, GrinchItem.get_apid(data.id), player)
self.type = data.item_group
self.item_id = data.id
#allows hinting of items via category
def get_item_names_per_category() -> dict[str, set[str]]:
categories: dict[str, set[str]] = {}
for name, data in ALL_ITEMS_TABLE.items():
categories.setdefault(data.item_group, set()).add(name)
return categories
#Gadgets
#All gadgets require at least 4 different blueprints to be unlocked in the computer in Mount Crumpit.
GADGETS_TABLE: dict[str, GrinchItemData] = {
"Binoculars": GrinchItemData("Gadgets", 100, IC.useful,
[GrinchRamData(0x0102B6, value=0x40), GrinchRamData(0x0102B7, value=0x41),
GrinchRamData(0x0102B8, value=0x44), GrinchRamData(0x0102B9, value=0x45),
# GrinchRamData(0x0100BC, binary_bit_pos=0)
]),
"Rotten Egg Launcher": GrinchItemData("Gadgets", 101, IC.progression,
[GrinchRamData(0x0102BA, value=0x40), GrinchRamData(0x0102BB, value=0x41),
GrinchRamData(0x0102BC, value=0x44), GrinchRamData(0x0102BD, value=0x45),
# GrinchRamData(0x0100BC, binary_bit_pos=1)
]),
"Rocket Spring": GrinchItemData("Gadgets", 102, IC.progression,
[GrinchRamData(0x0102BE, value=0x40), GrinchRamData(0x0102BF, value=0x41),
GrinchRamData(0x0102C0, value=0x42), GrinchRamData(0x0102C1, value=0x44),
GrinchRamData(0x0102C2, value=0x45), GrinchRamData(0x0102C3, value=0x46),
GrinchRamData(0x0102C4, value=0x48), GrinchRamData(0x0102C5, value=0x49),
GrinchRamData(0x0102C6, value=0x4A),
# GrinchRamData(0x0100BC, binary_bit_pos=2)
]),
"Slime Shooter": GrinchItemData("Gadgets", 103, IC.progression,
[GrinchRamData(0x0102C7, value=0x40), GrinchRamData(0x0102C8, value=0x41),
GrinchRamData(0x0102C9, value=0x42), GrinchRamData(0x0102CA, value=0x44),
GrinchRamData(0x0102CB, value=0x45), GrinchRamData(0x0102CC, value=0x46),
GrinchRamData(0x0102CD, value=0x48), GrinchRamData(0x0102CE, value=0x49),
GrinchRamData(0x0102CF, value=0x4A),
# GrinchRamData(0x0100BC, binary_bit_pos=3)
]),
"Octopus Climbing Device": GrinchItemData("Gadgets", 104, IC.progression,
[GrinchRamData(0x0102D0, value=0x40), GrinchRamData(0x0102D1, value=0x41),
GrinchRamData(0x0102D2, value=0x42), GrinchRamData(0x0102D3, value=0x44),
GrinchRamData(0x0102D4, value=0x45), GrinchRamData(0x0102D5, value=0x46),
GrinchRamData(0x0102D6, value=0x48), GrinchRamData(0x0102D7, value=0x49),
GrinchRamData(0x0102D8, value=0x4A),
# GrinchRamData(0x0100BC, binary_bit_pos=4)
]),
"Marine Mobile": GrinchItemData("Gadgets", 105, IC.progression,
[GrinchRamData(0x0102D9, value=0x40), GrinchRamData(0x0102DA, value=0x41),
GrinchRamData(0x0102DB, value=0x42), GrinchRamData(0x0102DC, value=0x43),
GrinchRamData(0x0102DD, value=0x44), GrinchRamData(0x0102DE, value=0x45),
GrinchRamData(0x0102DF, value=0x46), GrinchRamData(0x0102E0, value=0x47),
GrinchRamData(0x0102E1, value=0x48), GrinchRamData(0x0102E2, value=0x49),
GrinchRamData(0x0102E3, value=0x4A), GrinchRamData(0x0102E4, value=0x4B),
GrinchRamData(0x0102E5, value=0x4C), GrinchRamData(0x0102E6, value=0x4D),
GrinchRamData(0x0102E7, value=0x4E), GrinchRamData(0x0102E8, value=0x4F),
# GrinchRamData(0x0100BC, binary_bit_pos=5)
]),
"Grinch Copter": GrinchItemData("Gadgets", 106, IC.progression,
[GrinchRamData(0x0102E9, value=0x40), GrinchRamData(0x0102EA, value=0x41),
GrinchRamData(0x0102EB, value=0x42), GrinchRamData(0x0102EC, value=0x43),
GrinchRamData(0x0102ED, value=0x44), GrinchRamData(0x0102EE, value=0x45),
GrinchRamData(0x0102EF, value=0x46), GrinchRamData(0x0102F0, value=0x47),
GrinchRamData(0x0102F1, value=0x48), GrinchRamData(0x0102F2, value=0x49),
GrinchRamData(0x0102F3, value=0x4A), GrinchRamData(0x0102F4, value=0x4B),
GrinchRamData(0x0102F5, value=0x4C), GrinchRamData(0x0102F6, value=0x4D),
GrinchRamData(0x0102F7, value=0x4E), GrinchRamData(0x0102F8, value=0x4F),
# GrinchRamData(0x0100BC, binary_bit_pos=6)
])
}
#Mission Specific Items
MISSION_ITEMS_TABLE: dict[str, GrinchItemData] = {
"Who Cloak": GrinchItemData("Mission Specific Items", 200, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=0)], second_item_group="Useful Items"),
"Painting Bucket": GrinchItemData("Mission Specific Items", 201, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=1)], second_item_group="Useful Items"),
"Scissors": GrinchItemData("Mission Specific Items", 202, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)],
second_item_group="Useful Items"),
"Glue Bucket": GrinchItemData("Mission Specific Items", 203, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=4)], second_item_group="Useful Items"),
"Cable Car Access Card": GrinchItemData("Mission Specific Items", 204, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=5)], second_item_group="Useful Items"),
"Drill": GrinchItemData("Mission Specific Items", 205, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=2)], second_item_group="Useful Items"),
"Rope": GrinchItemData("Mission Specific Items", 206, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=1)], second_item_group="Useful Items"),
"Hook": GrinchItemData("Mission Specific Items", 207, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=0)], second_item_group="Useful Items"),
"Sculpting Tools": GrinchItemData("Mission Specific Items", 208, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=2)], second_item_group="Useful Items"),
"Hammer": GrinchItemData("Mission Specific Items", 209, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=3)], second_item_group="Useful Items"),
"Scout Clothes": GrinchItemData("Mission Specific Items", 210, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=7)], second_item_group="Useful Items")
}
#Sleigh Parts
# SLEIGH_PARTS_TABLE: dict[str, GrinchItemData] = {
# "Exhaust Pipes": GrinchItemData("Sleigh Parts", 300, IC.progression_skip_balancing,
# [GrinchRamData(0x0101FB, binary_bit_pos=2)]),
# "GPS": GrinchItemData("Sleigh Parts", 301, IC.useful,
# [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
# "Tires": GrinchItemData("Sleigh Parts", 302, IC.progression_skip_balancing,
# [GrinchRamData(0x0101FB, binary_bit_pos=4)]),
# "Skis": GrinchItemData("Sleigh Parts", 303, IC.progression_skip_balancing,
# [GrinchRamData(0x0101FB, binary_bit_pos=3)]),
# "Twin-End Tuba": GrinchItemData("Sleigh Parts", 304, IC.progression_skip_balancing,
# [GrinchRamData(0x0101FB, binary_bit_pos=6)])
# }
#Access Keys
KEYS_TABLE: dict[str, GrinchItemData] = {
# "Whoville Vacuum Access": GrinchItemData("Vacuum Access", 400, IC.progression,
# [GrinchRamData()]),
"Who Forest Vacuum Access": GrinchItemData("Vacuum Access", 401, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=2)]),
"Who Dump Vacuum Access": GrinchItemData("Vacuum Access", 402, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=3)]),
"Who Lake Vacuum Access": GrinchItemData("Vacuum Access", 403, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=4)]),
# "Progressive Vacuum Access": GrinchItemData("Vacuum Access", 404, IC.progression,
# [GrinchRamData()]),
# "Spin N' Win Door Unlock": GrinchItemData("Supadow Door Unlocks", 405, IC.progression,
# [GrinchRamData()]),
# "Dankamania Door Unlock": GrinchItemData("Supadow Door Unlocks", 406, IC.progression,
# [GrinchRamData()]),
# "The Copter Race Contest Door Unlock": GrinchItemData("Supadow Door Unlocks", 407, IC.progression,
# [GrinchRamData()]),
# "Progressive Supadow Door Unlock": GrinchItemData("Supadow Door Unlocks", 408, IC.progression,
# [GrinchRamData()]),
# "Bike Race Access": GrinchItemData("Supadow Door Unlocks", 409, IC.progression,
# [GrinchRamData()])
"Sleigh Room Key": GrinchItemData("Sleigh Room", 410, IC.progression,
[GrinchRamData(0x010200, binary_bit_pos=6), GrinchRamData(0x0100AA, binary_bit_pos=5)])
}
#Misc Items
MISC_ITEMS_TABLE: dict[str, GrinchItemData] = {
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
# "Fully Healed Grinch": GrinchItemData("Health Items", 500, IC.filler,
# [GrinchRamData(0x0E8FDC, value=120)]),
"5 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 502, IC.filler,
[GrinchRamData(0x010058, value=5, update_existing_value=True, max_count=200, bit_size=2)]),
"10 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 503, IC.filler,
[GrinchRamData(0x010058, value=10, update_existing_value=True, max_count=200, bit_size=2)]),
"20 Rotten Eggs": GrinchItemData("Rotten Egg Bundles", 504, IC.filler,
[GrinchRamData(0x010058, value=20, update_existing_value=True, max_count=200, bit_size=2)])
}
USEFUL_IC_TABLE: dict[str, GrinchItemData] = {
"Heart of Stone": GrinchItemData("Health Items", 501, IC.useful,
[GrinchRamData(0x0100ED, value=1, update_existing_value=True, max_count=4)])
}
#Traps
TRAPS_TABLE: dict[str, GrinchItemData] = {
# alias to Ice Trap for traplink
# "Freeze Trap": GrinchItemData("Traps", 600, IC.trap, [GrinchRamData()]),
# "Bee Trap": GrinchItemData("Traps", 601, IC.trap, [GrinchRamData()]),
# "Electrocution Trap": GrinchItemData("Traps", 602, IC.trap, [GrinchRamData()]),
# alias to Slowness Trap for traplink
# "Tip Toe Trap": GrinchItemData("Traps", 603, IC.trap, [GrinchRamData()]),
# This item may not function properly if you receive it during a loading screen or in Mount Crumpit
# "Damage Trap": GrinchItemData("Traps", 604, IC.trap, [GrinchRamData(0x0E8FDC, value=-20, update_existing_value=True)]),
"Depletion Trap": GrinchItemData("Traps", 605, IC.trap, [GrinchRamData(0x010058, value=0, bit_size=2)]),
"Dump it to Crumpit": GrinchItemData("Traps", 606, IC.trap, #Alias to Home Trap for traplink
[GrinchRamData(0x010000, value=0x05), GrinchRamData(0x08FB94, value=1)]),
#alias to Spring Trap for traplink
# "Rocket Spring Trap": GrinchItemData("Traps", 607, IC.trap, [GrinchRamData()]),
#alias to Home Trap for traplink
"Who sent me back?": GrinchItemData("Traps", 608, IC.trap, [GrinchRamData(0x08FB94, value=1)]),
# "Cutscene Trap": GrinchItemData("Traps", 609, IC.trap, [GrinchRamData()]),
# "No Vac Trap": GrinchItemData("Traps", 610, IC.trap, [GrinchRamData(0x0102DA, value=0]),
# "Invisible Trap": GrinchItemData("Traps", 611, IC.trap, [GrinchRamData(0x0102DA, value=0, 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
# MOVES_TABLE: dict[str, GrinchItemData] = {
# "Bad Breath": GrinchItemData("Movesets", 700, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=1)]),
# "Pancake": GrinchItemData("Movesets", 701, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=2)]),
# "Push & Pull": GrinchItemData("Movesets", 702, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=3)]),
# "Max": GrinchItemData("Movesets", 703, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=4)]),
# "Tip Toe": GrinchItemData("Movesets", 704, IC.progression, [GrinchRamData(0x0100BB, binary_bit_pos=5)])
# }
#Double star combines all dictionaries from each individual list together
ALL_ITEMS_TABLE: dict[str, GrinchItemData] = {
**GADGETS_TABLE,
**MISSION_ITEMS_TABLE,
**KEYS_TABLE,
**MISC_ITEMS_TABLE,
**TRAPS_TABLE,
**USEFUL_IC_TABLE,
# **SLEIGH_PARTS_TABLE,
# **MOVES_TABLE,
}
# 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", "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", "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] = {}
for ItemName, ItemData in ALL_ITEMS_TABLE.items():
item_mappings.update({ItemName: GrinchItem.get_apid(ItemData.id)})
return item_mappings

197
worlds/grinch/Locations.py Normal file
View File

@@ -0,0 +1,197 @@
from typing import NamedTuple, Optional
from .RamHandler import GrinchRamData
from BaseClasses import Location, Region
class GrinchLocationData(NamedTuple):
region: str
location_group: str
id: Optional[int]
update_ram_addr: list[GrinchRamData]
reset_addr: Optional[list[GrinchRamData]] = None # Addresses to update once we find the item
class GrinchLocation(Location):
game: str = "The Grinch"
@staticmethod
def get_apid(id: int):
base_id: int = 42069
return base_id + id if id is not None else None
def __init__(self, player: int, name: str, parent: Region, data: GrinchLocationData):
address = None if data.id is None else GrinchLocation.get_apid(data.id)
super(GrinchLocation, self).__init__(player, name, address=address, parent=parent)
self.code = data.id
self.region = data.region
self.type = data.location_group
self.address = self.address
grinch_locations = {
#Going to use current map id as indicator whether or not you visited a location
#Visitsanity
"Enter Whoville": GrinchLocationData("Whoville", "Visitsanity", 100, [GrinchRamData(0x010000, value=0x07)]),
"Enter the Post Office": GrinchLocationData("Post Office", "Visitsanity", 101, [GrinchRamData(0x010000, value=0x0A)]),
"Enter the Town Hall": GrinchLocationData("City Hall", "Visitsanity", 102, [GrinchRamData(0x010000, value=0x08)]),
"Enter the Countdown-To-Xmas Clock Tower": GrinchLocationData("Countdown to X-Mas Clock Tower", "Visitsanity", 103, [GrinchRamData(0x010000, value=0x09)]),
"Enter Who Forest": GrinchLocationData("Who Forest", "Visitsanity", 104, [GrinchRamData(0x010000, value=0x0B)]),
"Enter the Ski Resort": GrinchLocationData("Ski Resort", "Visitsanity", 105, [GrinchRamData(0x010000, value=0x0C)]),
"Enter the Civic Center": GrinchLocationData("Civic Center", "Visitsanity", 106, [GrinchRamData(0x010000, value=0x0D)]),
"Enter Who Dump": GrinchLocationData("Who Dump", "Visitsanity", 107, [GrinchRamData(0x010000, value=0x0E)]),
"Enter the Minefield": GrinchLocationData("Minefield", "Visitsanity", 108, [GrinchRamData(0x010000, value=0x11)]),
"Enter the Power Plant": GrinchLocationData("Power Plant", "Visitsanity", 109, [GrinchRamData(0x010000, value=0x10)]),
"Enter the Generator Building": GrinchLocationData("Generator Building", "Visitsanity", 110, [GrinchRamData(0x010000, value=0x0F)]),
"Enter Who Lake": GrinchLocationData("Who Lake", "Visitsanity", 111, [GrinchRamData(0x010000, value=0x12)]),
"Enter the Submarine World": GrinchLocationData("Submarine World", "Visitsanity", 112, [GrinchRamData(0x010000, value=0x17)]),
"Enter the Scout's Hut": GrinchLocationData("Scout's Hut", "Visitsanity", 113, [GrinchRamData(0x010000, value=0x13)]),
"Enter the North Shore": GrinchLocationData("North Shore", "Visitsanity", 114, [GrinchRamData(0x010000, value=0x14)]),
"Enter the Mayor's Villa": GrinchLocationData("Mayor's Villa", "Visitsanity", 115, [GrinchRamData(0x010000, value=0x16)]),
#Need to find mission completion address for handful of locations that are not documented.
#Missions that have value are those ones we need to find the check for
#Whoville Missions
"Shuffling The Mail": GrinchLocationData("Post Office", "Whoville Missions", 201, [GrinchRamData(0x0100BE, binary_bit_pos=0)]),
"Smashing Snowmen": GrinchLocationData("Whoville", "Whoville Missions", 200, [GrinchRamData(0x0100C5, value=10)]),
"Painting The Mayor's Posters": GrinchLocationData("Whoville", "Whoville Missions", 202, [GrinchRamData(0x0100C6, value=10)]),
"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, 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, 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)]),
"Infesting The Mayor's House With Rats": GrinchLocationData("Who Dump", "Who Dump Missions", 402, [GrinchRamData(0x0100BE, binary_bit_pos=6)]),
"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, 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, bit_size=2)]),
#Need to find binary values for individual blueprints, but all ram addresses are found
#Blueprints
#Binoculars Blueprints
"Binoculars Blueprint - Post Office Roof": GrinchLocationData("Whoville", "Binocular Blueprints", 600, [GrinchRamData(0x01020B, binary_bit_pos=2)]),
"Binoculars Blueprint - City Hall Library - Left Side": GrinchLocationData("City Hall", "Binocular Blueprints", 601, [GrinchRamData(0x01021F, binary_bit_pos=6)]),
"Binoculars Blueprint - City Hall Library - Front Side": GrinchLocationData("City Hall", "Binocular Blueprints", 602, [GrinchRamData(0x01021F, binary_bit_pos=5)]),
"Binoculars Blueprint - City Hall Library - Right Side": GrinchLocationData("City Hall", "Binocular Blueprints", 603, [GrinchRamData(0x01021F, binary_bit_pos=4)]),
#Rotten Egg Launcher Blueprints
"REL Blueprint - Outside City Hall": GrinchLocationData("Whoville", "Rotten Egg Launcher Blueprints", 700, [GrinchRamData(0x01020B, binary_bit_pos=0)]),
"REL Blueprint - Outside Clock Tower": GrinchLocationData("Whoville", "Rotten Egg Launcher Blueprints", 701, [GrinchRamData(0x01020B, binary_bit_pos=1)]),
"REL Blueprint - Post Office - Inside Silver Room": GrinchLocationData("Post Office", "Rotten Egg Launcher Blueprints", 702, [GrinchRamData(0x01021C, binary_bit_pos=1)]),
"REL Blueprint - Post Office - After Mission Completion": GrinchLocationData("Post Office", "Rotten Egg Launcher Blueprints", 703, [GrinchRamData(0x01021C, binary_bit_pos=2)]),
#Rocket Spring Blueprints
"RS Blueprint - Behind Vacuum": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 800, [GrinchRamData(0x010243, binary_bit_pos=3)]),
"RS Blueprint - Front of 2nd House near entrance": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 801, [GrinchRamData(0x010243, binary_bit_pos=1)]),
"RS Blueprint - Near Tree House on Ground": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 802, [GrinchRamData(0x010243, binary_bit_pos=4)]),
"RS Blueprint - Near Cable Car House": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 804, [GrinchRamData(0x010242, binary_bit_pos=7)]),
"RS Blueprint - Near Who Snowball in Cave": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 805, [GrinchRamData(0x010242, binary_bit_pos=6)]),
"RS Blueprint - Branch Platform Closest to Glue Cannon": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 806, [GrinchRamData(0x010243, binary_bit_pos=2)]),
"RS Blueprint - Branch Platform Near Beast": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 807, [GrinchRamData(0x010243, binary_bit_pos=0)]),
"RS Blueprint - Branch Platform Ledge Grab House": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 808, [GrinchRamData(0x010243, binary_bit_pos=6)]),
"RS Blueprint - On Tree House": GrinchLocationData("Who Forest", "Rocket Spring Blueprints", 809, [GrinchRamData(0x010243, binary_bit_pos=5)]),
#Slime Shooter Blueprints
"SS Blueprint - Branch Platform Elevated House": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 900, [GrinchRamData(0x010244, binary_bit_pos=3)]),
"SS Blueprint - Branch Platform House next to Beast": GrinchLocationData("Who Forest", "Slime Shooter Blueprint", 901, [GrinchRamData(0x010243, binary_bit_pos=7)]),
"SS Blueprint - House near Civic Center Cave": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 902, [GrinchRamData(0x010244, binary_bit_pos=2)]),
"SS Blueprint - House next to Tree House": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 903, [GrinchRamData(0x010244, binary_bit_pos=1)]),
"SS Blueprint - House across from Tree House": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 904, [GrinchRamData(0x010244, binary_bit_pos=5)]),
"SS Blueprint - 2nd House near entrance right side": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 905, [GrinchRamData(0x010244, binary_bit_pos=4)]),
"SS Blueprint - 2nd House near entrance left side": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 906, [GrinchRamData(0x010244, binary_bit_pos=7)]),
"SS Blueprint - 2nd House near entrance inbetween blueprints": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 907, [GrinchRamData(0x010244, binary_bit_pos=6)]),
"SS Blueprint - House near entrance": GrinchLocationData("Who Forest", "Slime Shooter Blueprints", 908, [GrinchRamData(0x010244, binary_bit_pos=0)]),
#Octopus Climbing Device
"OCD Blueprint - Middle Pipe": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1001, [GrinchRamData(0x010252, binary_bit_pos=3)]),
"OCD Blueprint - Right Pipe": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1002, [GrinchRamData(0x010252, binary_bit_pos=5)]),
"OCD Blueprint - Mayor's House Rat Vent": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1003, [GrinchRamData(0x010252, binary_bit_pos=1)]),
"OCD Blueprint - Left Pipe": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1004, [GrinchRamData(0x010252, binary_bit_pos=4)]),
"OCD Blueprint - Near Power Plant Wall on right side": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1005, [GrinchRamData(0x010252, binary_bit_pos=0)]),
"OCD Blueprint - Near Who-Bris' Shack": GrinchLocationData("Who Dump", "Octopus Climbing Device Blueprints", 1006, [GrinchRamData(0x010252, binary_bit_pos=2)]),
"OCD Blueprint - Guardian's House - Left Side": GrinchLocationData("Minefield", "Octopus Climbing Device Blueprints", 1007, [GrinchRamData(0x01026E, binary_bit_pos=2)]),
"OCD Blueprint - Guardian's House - Right Side": GrinchLocationData("Minefield", "Octopus Climbing Device Blueprints", 1008, [GrinchRamData(0x01026E, binary_bit_pos=4)]),
"OCD Blueprint - Inside Guardian's House": GrinchLocationData("Minefield", "Octopus Climbing Device Blueprints", 1009, [GrinchRamData(0x01026E, binary_bit_pos=3)]),
#Marine Mobile Blueprints
"MM Blueprint - South Shore - Bridge to Scout's Hut": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1100, [GrinchRamData(0x010281, binary_bit_pos=5)]),
"MM Blueprint - South Shore - Tent near Porcupine": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1101, [GrinchRamData(0x010281, binary_bit_pos=6)]),
"MM Blueprint - South Shore - Near Outhouse": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1102, [GrinchRamData(0x010281, binary_bit_pos=7)]),
"MM Blueprint - South Shore - Near Hill Bridge": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1103, [GrinchRamData(0x010282, binary_bit_pos=0)]),
"MM Blueprint - South Shore - Scout's Hut Roof": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1104, [GrinchRamData(0x010281, binary_bit_pos=4)]),
"MM Blueprint - South Shore - Grass Platform": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1105, [GrinchRamData(0x010281, binary_bit_pos=2)]),
"MM Blueprint - South Shore - Zipline by Beast": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1106, [GrinchRamData(0x010281, binary_bit_pos=3)]),
"MM Blueprint - South Shore - Behind Summer Beast": GrinchLocationData("Who Lake", "Marine Mobile Blueprints", 1107, [GrinchRamData(0x010282, binary_bit_pos=1)]),
"MM Blueprint - North Shore - Below Bridge": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1108, [GrinchRamData(0x010293, binary_bit_pos=0)]),
"MM Blueprint - North Shore - Behind Skunk Hut": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1109, [GrinchRamData(0x010293, binary_bit_pos=2)]),
"MM Blueprint - North Shore - Inside Skunk Hut": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1110, [GrinchRamData(0x010292, binary_bit_pos=6)]),
"MM Blueprint - North Shore - Fenced in Area": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1111, [GrinchRamData(0x010292, binary_bit_pos=7)]),
"MM Blueprint - North Shore - Boulder Box near Bridge": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1112, [GrinchRamData(0x010293, binary_bit_pos=3)]),
"MM Blueprint - North Shore - Boulder Box behind Skunk Hut": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1113, [GrinchRamData(0x010293, binary_bit_pos=4)]),
"MM Blueprint - North Shore - Inside Drill House": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1114, [GrinchRamData(0x010292, binary_bit_pos=5)]),
"MM Blueprint - North Shore - Crow Platform near Drill House": GrinchLocationData("North Shore", "Marine Mobile Blueprints", 1115, [GrinchRamData(0x010293, binary_bit_pos=1)]),
#Grinch Copter Blueprints
"GC Blueprint - Whoville City Hall - Safe Room": GrinchLocationData("City Hall", "Grinch Copter Blueprints", 1200, [GrinchRamData(0x01021F, binary_bit_pos=7)]),
"GC Blueprint - Whoville City Hall - Statue Room": GrinchLocationData("City Hall", "Grinch Copter Blueprints", 1201, [GrinchRamData(0x010220, binary_bit_pos=0)]),
"GC Blueprint - Whoville Clock Tower - Before Bells": GrinchLocationData("Countdown to X-Mas Clock Tower", "Grinch Copter Blueprints", 1202, [GrinchRamData(0x010216, binary_bit_pos=3)]),
"GC Blueprint - Whoville Clock Tower - After Bells": GrinchLocationData("Countdown to X-Mas Clock Tower", "Grinch Copter Blueprints", 1203, [GrinchRamData(0x010216, binary_bit_pos=2)]),
"GC Blueprint - Who Forest Ski Resort - Inside Dog's Fence": GrinchLocationData("Ski Resort", "Grinch Copter Blueprints", 1204, [GrinchRamData(0x010234, binary_bit_pos=7)]),
"GC Blueprint - Who Forest Ski Resort - Max Cave": GrinchLocationData("Ski Resort", "Grinch Copter Blueprints", 1205, [GrinchRamData(0x010234, binary_bit_pos=6)]),
"GC Blueprint - Who Forest Civic Center - Climb across Bat Cave wall": GrinchLocationData("Civic Center", "Grinch Copter Blueprints", 1206, [GrinchRamData(0x01022A, binary_bit_pos=7)]),
"GC Blueprint - Who Forest Civic Center - Shoot Icicle in Bat Entrance": GrinchLocationData("Civic Center", "Grinch Copter Blueprints", 1207, [GrinchRamData(0x01022B, binary_bit_pos=0)]),
"GC Blueprint - Who Dump Power Plant - Max Cave": GrinchLocationData("Power Plant", "Grinch Copter Blueprints", 1208, [GrinchRamData(0x010265, binary_bit_pos=1)]),
"GC Blueprint - Who Dump Power Plant - After First Gate": GrinchLocationData("Power Plant", "Grinch Copter Blueprints", 1209, [GrinchRamData(0x010265, binary_bit_pos=2)]),
"GC Blueprint - Who Dump Generator Building - Before Mission": GrinchLocationData("Generator Building", "Grinch Copter Blueprints", 1210, [GrinchRamData(0x01026B, binary_bit_pos=0)]),
"GC Blueprint - Who Dump Generator Building - After Mission": GrinchLocationData("Generator Building", "Grinch Copter Blueprints", 1211, [GrinchRamData(0x01026B, binary_bit_pos=1)]),
"GC Blueprint - Who Lake South Shore - Submarine World - Above Surface": GrinchLocationData("Submarine World", "Grinch Copter Blueprints", 1212, [GrinchRamData(0x010289, binary_bit_pos=3)]),
"GC Blueprint - Who Lake South Shore - Submarine World - Underwater": GrinchLocationData("Submarine World", "Grinch Copter Blueprints", 1213, [GrinchRamData(0x010289, binary_bit_pos=4)]),
"GC Blueprint - Who Lake North Shore - Mayor's Villa - Tree Branch": GrinchLocationData("Mayor's Villa", "Grinch Copter Blueprints", 1214, [GrinchRamData(0x010275, binary_bit_pos=7)]),
"GC Blueprint - Who Lake North Shore - Mayor's Villa - Cave": GrinchLocationData("Mayor's Villa", "Grinch Copter Blueprints", 1215, [GrinchRamData(0x010275, binary_bit_pos=6)]),
#Sleigh Room Locations
"Stealing All Gifts": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1300, [GrinchRamData(0x0100BF, binary_bit_pos=6)]),
"Neutralizing Santa": GrinchLocationData("Sleigh Room", "Sleigh Ride", None, [GrinchRamData(0x0100BF, binary_bit_pos=7)]),
#Heart of Stones
"Heart of Stone - Whoville's Post Office": GrinchLocationData("Post Office", "Heart of Stones", 1400, [GrinchRamData(0x0101FA, binary_bit_pos=6)]),
"Heart of Stone - Who Forest's Ski Resort": GrinchLocationData("Ski Resort", "Heart of Stones", 1401, [GrinchRamData(0x0101FA, binary_bit_pos=7)]),
"Heart of Stone - Who Dump's Minefield": GrinchLocationData("Minefield", "Heart of Stones", 1402, [GrinchRamData(0x0101FB, binary_bit_pos=0)]),
"Heart of Stone - Who Lake's North Shore": GrinchLocationData("North Shore", "Heart of Stones", 1403, [GrinchRamData(0x0101FB, binary_bit_pos=1)]),
#Supadow Minigames
# "Spin N' Win - Easy": GrinchLocationData("Spin N' Win Supadow", "Supadow Minigames", 1500, [GrinchRamData()]),
# "Spin N' Win - Hard": GrinchLocationData("Spin N' Win Supadow", "Supadow Minigames", 1501, [GrinchRamData()]),
# "Spin N' Win - Real Tough": GrinchLocationData("Spin N' Win Supadow", "Supadow Minigames", 1502, [GrinchRamData()]),
# "Dankamania - Easy - 15 Points": GrinchLocationData("Dankamania Supadow", "Supadow Minigames", 1503, [GrinchRamData()]),
# "Dankamania - Hard - 15 Points": GrinchLocationData("Dankamania Supadow", "Supadow Minigames", 1504, [GrinchRamData()]),
# "Dankamania - Real Tough - 15 Points": GrinchLocationData("Dankamania Supadow", "Supadow Minigames", 1505, [GrinchRamData()]),
# "The Copter Race Contest - Easy": GrinchLocationData("The Copter Race Contest Supadow", "Supadow Minigames", 1506, [GrinchRamData()]),
# "The Copter Race Contest - Hard": GrinchLocationData("The Copter Race Contest Supadow", "Supadow Minigames", 1507, [GrinchRamData()]),
# "The Copter Race Contest - Real Tough": GrinchLocationData("The Copter Race Contest Supadow", "Supadow Minigames", 1508, [GrinchRamData()]),
# "Bike Race - 1st Place": GrinchLocationData("Bike Race", "Supadow Minigames", 1509, [GrinchRamData()]),
# "Bike Race - Top 2": GrinchLocationData("Bike Race", "Supadow Minigames", 1510, [GrinchRamData()]),
# "Bike Race - Top 3": GrinchLocationData("Bike Race", "Supadow Minigames", 1511, [GrinchRamData()]),
# Sleigh Part Locations
"Exhaust Pipes in Whoville": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1600, [GrinchRamData(0x0101FB, binary_bit_pos=2)]),
"Skis in Who Forest": GrinchLocationData("Sleigh Room", "Sleigh Ride", 1601, [GrinchRamData(0x0101FB, binary_bit_pos=3)]),
"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]:
location_mappings: dict[str, int] = {}
for LocationName, LocationData in grinch_locations.items():
location_mappings.update({LocationName: GrinchLocation.get_apid(LocationData.id)})
return location_mappings

89
worlds/grinch/Options.py Normal file
View File

@@ -0,0 +1,89 @@
from dataclasses import dataclass
from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, OptionList, \
PerGameCommonOptions
class StartingArea(Choice):
"""
Here, you can select which area you'll start the game with. [NOT IMPLEMENTED]
Whichever one you pick is the region you'll have access to at the start of the Multiworld.
"""
option_whoville = 0
option_who_forest = 1
option_who_dump = 2
option_who_lake = 3
default = 0
display_name = "Starting Area"
class ProgressiveVacuum(Toggle):#DefaultOnToggle
"""
Determines whether you get access to main areas progressively [NOT IMPLEMENTED]
Enabled: Whoville > Who Forest > Who Dump > Who Lake
"""
display_name = "Progressive Vacuum Access"
class Missionsanity(Choice):
"""
How mission checks are randomized in the pool [NOT IMPLEMENTED]
None: Does not add mission checks
Completion: Only completing the mission gives you a check
Individual: Individual tasks for one mission, such as individual snowmen squashed, are checks.
Both: Both individual tasks and mission completion are randomized.
"""
display_name = "Mission Locations"
option_none = 0
option_completion = 1
option_individual = 2
option_both = 3
default = 1
class AnnoyingLocations(DefaultOnToggle):
"""Makes certain long, annoying, and tedious checks to be excluded [NOT IMPLEMENTED]"""
display_name = "Annoying Locations"
class ProgressiveGadget(Toggle):#DefaultOnToggle
"""
Determines whether you get access to a gadget as individual blueprint count [NOT IMPLEMENTED]
"""
display_name = "Progressive Gadgets"
class Supadow(Toggle):
"""Enables completing minigames through the Supadows in Mount Crumpit as checks. (9 locations) [NOT IMPLEMENTED]"""
display_name = "Supadow Minigame Locations"
class Gifts(Toggle):
"""Missions that require you to squash every present in a level. (4 locations) [NOT IMPLEMENTED]"""
display_name = "Gift Collection Locations"
class Movesanity(Toggle):
"""Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]"""
display_name = "Movesanity"
class UnlimitedEggs(Toggle):
"""Determine whether or not you run out of rotten eggs when you utilize your gadgets."""
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."""
class TrapLinkOption(Toggle):
"""If a trap is sent from Grinch, traps that are compatible with other games are triggered aswell. [NOT IMPLEMENTED]"""
@dataclass
class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin
starting_area: StartingArea
progressive_vacuum: ProgressiveVacuum
missionsanity: Missionsanity
annoying_locations: AnnoyingLocations
progressive_gadget: ProgressiveGadget
supadow_minigames: Supadow
giftsanity: Gifts
movesanity: Movesanity
unlimited_eggs: UnlimitedEggs
ring_link: RingLinkOption
trap_link: TrapLinkOption

View File

@@ -0,0 +1,12 @@
from typing import NamedTuple, Optional
class GrinchRamData(NamedTuple):
ram_address: int
value: Optional[int] = None #none is empty/null
# Either set or add either hex or unsigned values through Client.py
# Hex uses 0x00, unsigned are base whole numbers
binary_bit_pos: Optional[int] = None
bit_size: int = 1
update_existing_value: bool = False
max_count: int = 0

101
worlds/grinch/Regions.py Normal file
View File

@@ -0,0 +1,101 @@
from typing import TYPE_CHECKING
from BaseClasses import Region
from .Rules import access_rules_dict, interpret_rule
from ..generic.Rules import add_rule
if TYPE_CHECKING:
from . import GrinchWorld
mainareas_list = [
"Whoville",
"Who Forest",
"Who Dump",
"Who Lake"
]
subareas_list = [
"Post Office",
"City Hall",
"Countdown to X-Mas Tower",
"Ski Resort",
"Civic Center",
"Minefield",
"Power Plant",
"Generator Building",
"Submarine World",
"Scout's Hut",
"North Shore",
"Mayor's Villa",
"Sleigh Room"
]
supadow_list = [
"Spin N' Win Supadow",
"Dankamania Supadow",
"The Copter Race Contest Supadow",
"Bike Race"
]
def create_regions(world: "GrinchWorld"):
for mainarea in mainareas_list:
#Each area in mainarea, create a region for the given player
world.multiworld.regions.append(Region(mainarea, world.player, world.multiworld))
for subarea in subareas_list:
#Each area in subarea, create a region for the given player
world.multiworld.regions.append(Region(subarea, world.player, world.multiworld))
for supadow in supadow_list:
#Each area in supadow, create a region for the given player
world.multiworld.regions.append(Region(supadow, world.player, world.multiworld))
# TODO Optimize this function
def grinchconnect(world: "GrinchWorld", current_region_name: str, connected_region_name: str):
current_region = world.get_region(current_region_name)
connected_region = world.get_region(connected_region_name)
required_items: list[list[str]] = access_rules_dict[connected_region.name]
rule_list = interpret_rule(required_items, world.player)
# Goes from current to connected
current_region.connect(connected_region)
# Goes from connected to current
connected_region.connect(current_region)
for access_rule in rule_list:
for region_entrance in current_region.entrances:
if region_entrance.connected_region.name == current_region_name and \
region_entrance.parent_region.name == connected_region_name:
if rule_list.index(access_rule) == 0:
add_rule(region_entrance, access_rule)
else:
add_rule(region_entrance, access_rule, combine="or")
for region_entrance in connected_region.entrances:
if region_entrance.connected_region.name == connected_region_name and \
region_entrance.parent_region.name == current_region_name:
if rule_list.index(access_rule) == 0:
add_rule(region_entrance, access_rule)
else:
add_rule(region_entrance, access_rule, combine="or")
#What regions are connected to each other
def connect_regions(world: "GrinchWorld"):
grinchconnect(world, "Mount Crumpit", "Whoville")
grinchconnect(world, "Mount Crumpit", "Who Forest")
grinchconnect(world, "Mount Crumpit", "Who Dump")
grinchconnect(world, "Mount Crumpit", "Who Lake")
grinchconnect(world, "Mount Crumpit", "Sleigh Room")
grinchconnect(world, "Mount Crumpit", "Spin N' Win Supadow")
grinchconnect(world, "Mount Crumpit", "Dankamania Supadow")
grinchconnect(world, "Mount Crumpit", "The Copter Race Contest Supadow")
grinchconnect(world, "Whoville", "Post Office")
grinchconnect(world, "Whoville", "City Hall")
grinchconnect(world, "Whoville", "Countdown to X-Mas Clock Tower")
grinchconnect(world, "Who Forest", "Ski Resort")
grinchconnect(world, "Who Forest", "Civic Center")
grinchconnect(world, "Who Dump", "Minefield")
grinchconnect(world, "Who Dump", "Power Plant")
grinchconnect(world, "Power Plant", "Generator Building")
grinchconnect(world, "Who Lake", "Submarine World")
grinchconnect(world, "Who Lake", "Scout's Hut")
grinchconnect(world, "Who Lake", "North Shore")
grinchconnect(world, "North Shore", "Mayor's Villa")
grinchconnect(world, "Sleigh Room", "Bike Race")

597
worlds/grinch/Rules.py Normal file
View File

@@ -0,0 +1,597 @@
from typing import Callable
import Utils
from BaseClasses import CollectionState
from worlds.AutoWorld import World
from worlds.generic.Rules import add_rule
#Adds all rules from access_rules_dict to locations
def set_location_rules(world: World):
all_locations = world.get_locations()
for location in all_locations:
loc_rules = rules_dict[location.name]
rule_list = interpret_rule(loc_rules, world.player)
for access_rule in rule_list:
if rule_list.index(access_rule) == 0:
add_rule(location, access_rule)
else:
add_rule(location, access_rule, "or")
def interpret_rule(rule_set: list[list[str]], player: int):
# If a region/location does not have any items required, make the section(s) return no logic.
if len(rule_set) < 1:
return True
# Otherwise, if a region/location DOES have items required, make the section(s) return list of logic.
access_list: list[Callable[[CollectionState], bool]] = []
for item_set in rule_set:
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.
rules_dict: dict[str,list[list[str]]] = {
"Enter Whoville": [
[]
],
"Enter the Post Office": [
[]
],
"Enter the Town Hall": [
[]
],
"Enter the Countdown-To-Xmas Clock Tower": [
[]
],
"Enter Who Forest": [
[]
],
"Enter the Ski Resort": [
[]
],
"Enter the Civic Center": [
[]
],
"Enter Who Dump": [
[]
],
"Enter the Minefield": [
[]
],
"Enter the Power Plant": [
[]
],
"Enter the Generator Building": [
[]
],
"Enter Who Lake": [
[]
],
"Enter the Submarine World": [
[]
],
"Enter the Scout's Hut": [
[]
],
"Enter the North Shore": [
[]
],
"Enter the Mayor's Villa": [
[]
],
"Shuffling The Mail": [
[]
],
"Smashing Snowmen": [
[]
],
"Painting The Mayor's Posters": [
["Painting Bucket"]
],
"Launching Eggs Into Houses": [
["Rotten Egg Launcher"]
],
"Modifying The Mayor's Statue": [
["Sculpting Tools"]
],
"Advancing The Countdown-To-Xmas Clock": [
["Hammer", "Rocket Spring"]
],
"Squashing All Gifts in Whoville": [
["Grinch Copter", "Slime Shooter", "Rotten Egg Launcher", "Who Cloak", "Rocket Spring"]
],
"Making Xmas Trees Droop": [
["Rotten Egg Launcher"]
],
"Sabotaging Snow Cannon With Glue": [
["Glue Bucket", "Rocket Spring"],
["Glue Bucket", "Grinch Copter"]
],
"Putting Beehives In Cabins": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"Sliming The Mayor's Skis": [
["Slime Shooter", "Rotten Egg Launcher"]
],
"Replacing The Candles On The Cake With Fireworks": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Rocket Spring"]
],
"Squashing All Gifts in Who Forest": [
["Grinch Copter", "Cable Car Access Card", "Slime Shooter", "Rotten Egg Launcher"],
["Octopus Climbing Device", "Rocket Spring", "Cable Car Access Card", "Slime Shooter", "Rotten Egg Launcher"]
],
"Stealing Food From Birds": [
["Rocket Spring", "Rotten Egg Launcher"]
],
"Feeding The Computer With Robot Parts": [
["Rocket Spring", "Rotten Egg Launcher"]
],
"Infesting The Mayor's House With Rats": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"Conducting The Stinky Gas To Who-Bris' Shack": [
["Rocket Spring", "Rotten Egg Launcher"]
],
"Shaving Who Dump Guardian": [
["Scissors", "Grinch Copter"],
["Scissors", "Slime Shooter", "Rocket Spring"]
],
"Short-Circuiting Power-Plant": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Slime Shooter", "Rocket Spring"]
],
"Squashing All Gifts in Who Dump": [
["Grinch Copter", "Rocket Spring", "Slime Shooter", "Rotten Egg Launcher"],
["Octopus Climbing Device", "Rocket Spring", "Slime Shooter", "Rotten Egg Launcher"]
],
"Putting Thistles In Shorts": [
["Rotten Egg Launcher", "Octopus Climbing Device"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"Sabotaging The Tents": [
["Octopus Climbing Device", "Rocket Spring"],
["Grinch Copter"]
],
"Drilling Holes In Canoes": [
["Drill"]
# ["Drill", "Max"]
],
"Modifying The Marine Mobile": [
[]
],
"Hooking The Mayor's Bed To The Motorboat": [
["Rope", "Hook", "Rotten Egg Launcher", "Scout Clothes"]
],
"Squashing All Gifts in Who Lake": [
["Grinch Copter", "Marine Mobile", "Scout Clothes", "Rotten Egg Launcher", "Hook", "Rope"],
["Octopus Climbing Device", "Rocket Spring", "Marine Mobile", "Scout Clothes", "Rotten Egg Launcher", "Hook", "Rope"]
],
"Binoculars Blueprint - Post Office Roof": [
[]
],
"Binoculars Blueprint - City Hall Library - Left Side": [
[]
],
"Binoculars Blueprint - City Hall Library - Front Side": [
[]
],
"Binoculars Blueprint - City Hall Library - Right Side": [
[]
],
"REL Blueprint - Outside City Hall": [
[]
],
"REL Blueprint - Outside Clock Tower": [
[]
],
"REL Blueprint - Post Office - Inside Silver Room": [
["Who Cloak"]
# ["Who Cloak", "Max"]
],
"REL Blueprint - Post Office - After Mission Completion": [
["Who Cloak"]
# ["Who Cloak", "Max"]
],
"RS Blueprint - Behind Vacuum": [
[]
],
"RS Blueprint - Front of 2nd House near entrance": [
[]
],
"RS Blueprint - Near Tree House on Ground": [
[]
],
"RS Blueprint - Near Cable Car House": [
[]
],
"RS Blueprint - Near Who Snowball in Cave": [
[]
],
"RS Blueprint - Branch Platform Closest to Glue Cannon": [
[]
],
"RS Blueprint - Branch Platform Near Beast": [
[]
],
"RS Blueprint - Branch Platform Ledge Grab House": [
[]
],
"RS Blueprint - On Tree House": [
["Rotten Egg Launcher"],
["Grinch Copter"]
],
"SS Blueprint - Branch Platform Elevated House": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - Branch Platform House next to Beast": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - House near Civic Center Cave": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - House next to Tree House": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - House across from Tree House": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - 2nd House near entrance right side": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - 2nd House near entrance left side": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - 2nd House near entrance inbetween blueprints": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"SS Blueprint - House near entrance": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"OCD Blueprint - Middle Pipe": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"],
["Slime Shooter", "Rocket Spring"],
["Slime Shooter", "Grinch Copter"]
],
"OCD Blueprint - Right Pipe": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"OCD Blueprint - Mayor's House Rat Vent": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"OCD Blueprint - Left Pipe": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"],
["Slime Shooter", "Rocket Spring"],
["Slime Shooter", "Grinch Copter"]
],
"OCD Blueprint - Near Power Plant Wall on right side": [
["Rotten Egg Launcher", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"],
["Slime Shooter", "Rocket Spring"],
["Slime Shooter", "Grinch Copter"]
],
"OCD Blueprint - Near Who-Bris' Shack": [
["Rotten Egg Launcher", "Rocket Spring"]
],
"OCD Blueprint - Guardian's House - Left Side": [
[]
# ["Rotten Egg Launcher", "Grinch Copter"],
# ["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"]
# ["Max"]
],
"OCD Blueprint - Guardian's House - Right Side": [
["Grinch Copter"],
["Slime Shooter", "Rocket Spring"]
],
"OCD Blueprint - Inside Guardian's House": [
[]
# ["Rotten Egg Launcher", "Grinch Copter"],
# ["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"]
# ["Max"]
],
"MM Blueprint - South Shore - Bridge to Scout's Hut": [
[]
],
"MM Blueprint - South Shore - Tent near Porcupine": [
[]
],
"MM Blueprint - South Shore - Near Outhouse": [
[]
],
"MM Blueprint - South Shore - Near Hill Bridge": [
[]
],
"MM Blueprint - South Shore - Scout's Hut Roof": [
["Rocket Spring"],
["Grinch Copter"]
],
"MM Blueprint - South Shore - Grass Platform": [
["Rocket Spring"],
["Grinch Copter"]
],
"MM Blueprint - South Shore - Zipline by Beast": [
["Rocket Spring", "Octopus Climbing Device"],
["Grinch Copter"]
],
"MM Blueprint - South Shore - Behind Summer Beast": [
["Rotten Egg Launcher", "Octopus Climbing Device"],
["Grinch Copter"]
],
"MM Blueprint - South Shore - Below Bridge": [
[]
],
"MM Blueprint - North Shore - Below Bridge": [
[]
],
"MM Blueprint - North Shore - Behind Skunk Hut": [
[]
],
"MM Blueprint - North Shore - Inside Skunk Hut": [
[]
# ["Max"]
],
"MM Blueprint - North Shore - Fenced in Area": [
[]
# ["Max"]
],
"MM Blueprint - North Shore - Boulder Box near Bridge": [
[]
],
"MM Blueprint - North Shore - Boulder Box behind Skunk Hut": [
[]
],
"MM Blueprint - North Shore - Inside Drill House": [
[]
],
"MM Blueprint - North Shore - Crow Platform near Drill House": [
[]
],
"GC Blueprint - Whoville City Hall - Safe Room": [
[]
],
"GC Blueprint - Whoville City Hall - Statue Room": [
[]
],
"GC Blueprint - Whoville Clock Tower - Before Bells": [
["Rocket Spring"]
# ["Max", "Rocket Spring"]
],
"GC Blueprint - Whoville Clock Tower - After Bells": [
["Rocket Spring"]
],
"GC Blueprint - Who Forest Ski Resort - Inside Dog's Fence": [
[]
],
"GC Blueprint - Who Forest Ski Resort - Max Cave": [
[]
# ["Max"]
],
"GC Blueprint - Who Forest Civic Center - Climb across Bat Cave wall": [
["Grinch Copter"],
["Octopus Climbing Device", "Rocket Spring"]
],
"GC Blueprint - Who Forest Civic Center - Shoot Icicle in Bat Entrance": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Rocket Spring"],
["Slime Shooter", "Grinch Copter"],
["Slime Shooter", "Octopus Climbing Device", "Rocket Spring"]
],
"GC Blueprint - Who Dump Power Plant - Max Cave": [
[]
# ["Max"]
],
"GC Blueprint - Who Dump Power Plant - After First Gate": [
["Rotten Egg Launcher", "Rocket Spring"],
["Grinch Copter"]
# ["Max", "Rotten Egg Launcher", "Rocket Spring"]
],
"GC Blueprint - Who Dump Generator Building - Before Mission": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Slime Shooter", "Rocket Spring"]
],
"GC Blueprint - Who Dump Generator Building - After Mission": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Slime Shooter", "Rocket Spring"]
],
"GC Blueprint - Who Lake South Shore - Submarine World - Above Surface": [
["Marine Mobile"]
],
"GC Blueprint - Who Lake South Shore - Submarine World - Underwater": [
["Marine Mobile"]
],
"GC Blueprint - Who Lake North Shore - Mayor's Villa - Tree Branch": [
["Grinch Copter"],
["Rotten Egg Launcher", "Rocket Spring"]
],
"GC Blueprint - Who Lake North Shore - Mayor's Villa - Cave": [
["Grinch Copter"],
["Rotten Egg Launcher", "Rocket Spring"]
],
"Stealing All Gifts": [
# ["Exhaust Pipes", "Tires", "Skis", "Twin-End Tuba"]
["Rotten Egg Launcher", "Who Forest Vacuum Access", "Who Dump Vacuum Access", "Who Lake Vacuum Access", "Rocket Spring", "Marine Mobile"]
],
"Neutralizing Santa": [
# ["Exhaust Pipes", "Tires", "Skis", "Twin-End Tuba"]
["Rotten Egg Launcher", "Who Forest Vacuum Access", "Who Dump Vacuum Access", "Who Lake Vacuum Access", "Rocket Spring", "Marine Mobile"]
],
"Heart of Stone - Whoville's Post Office": [
[]
],
"Heart of Stone - Who Forest's Ski Resort": [
[]
],
"Heart of Stone - Who Dump's Minefield": [
["Grinch Copter"],
["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"]
],
"Heart of Stone - Who Lake's North Shore": [
[]
# ["Max"]
],
"Spin N' Win - Easy": [
[]
],
"Spin N' Win - Hard": [
[]
],
"Spin N' Win - Real Tough": [
[]
],
"Dankamania - Easy - 15 Points": [
[]
],
"Dankamania - Hard - 15 Points": [
[]
],
"Dankamania - Real Tough - 15 Points": [
[]
],
"The Copter Race Contest - Easy": [
[]
],
"The Copter Race Contest - Hard": [
[]
],
"The Copter Race Contest - Real Tough": [
[]
],
"Bike Race - 1st Place": [
[]
],
"Bike Race - Top 2": [
[]
],
"Bike Race - Top 3": [
[]
],
"Exhaust Pipes in Whoville": [
["Rotten Egg Launcher"]
],
"Skis in Who Forest": [
["Who Forest Vacuum Access"]
],
"Tires in Who Dump": [
["Who Dump Vacuum Access", "Rocket Spring", "Rotten Egg Launcher"]
],
"Twin-End Tuba in Submarine World": [
["Who Lake Vacuum Access", "Marine Mobile"]
],
"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": [
[]
]
}
access_rules_dict: dict[str,list[list[str]]] = {
"Whoville": [
[]
],
"Post Office": [
["Who Cloak"]
],
"City Hall": [
["Rotten Egg Launcher"]
],
"Countdown to X-Mas Clock Tower": [
[]
],
"Who Forest": [
["Who Forest Vacuum Access"],
# ["Progressive Vacuum Access": 1]
],
"Ski Resort": [
["Cable Car Access Card"]
],
"Civic Center": [
["Grinch Copter"],
["Octopus Climbing Device"]
],
"Who Dump": [
["Who Dump Vacuum Access"],
# ["Progressive Vacuum Access": 2]
],
"Minefield": [
["Rotten Egg Launcher", "Slime Shooter", "Rocket Spring"],
["Rotten Egg Launcher", "Grinch Copter"]
],
"Power Plant": [
["Rotten Egg Launcher", "Grinch Copter"],
["Slime Shooter", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Slime Shooter", "Rocket Spring"]
],
"Generator Building": [
["Rotten Egg Launcher", "Grinch Copter"],
["Rotten Egg Launcher", "Octopus Climbing Device", "Slime Shooter", "Rocket Spring"]
],
"Who Lake": [
["Who Lake Vacuum Access"],
# ["Progressive Vacuum Access": 3]
],
"Scout's Hut": [
["Grinch Copter"],
["Rocket Spring"]
],
"North Shore": [
["Scout Clothes"]
],
"Mayor's Villa": [
["Scout Clothes"]
],
"Submarine World": [
["Marine Mobile"]
],
"Sleigh Room": [
["Sleigh Room Key"]
],
"Spin N' Win Supadow": [
[]
# ["Spin N' Win Door Unlock"],
# ["Progressive Supadow Door Unlock"]
],
"Dankamania Supadow": [
[]
# ["Dankamania Door Unlock"],
# ["Progressive Supadow Door Unlock: 2"]
],
"The Copter Race Contest Supadow": [
[]
# ["The Copter Race Contest Door Unlock"],
# ["Progressive Supadow Door Unlock: 3"]
],
"Bike Race": [
[]
# ["Bike Race Access"],
# ["Progressive Supadow Door Unlock: 4"]
]
}

84
worlds/grinch/__init__.py Normal file
View File

@@ -0,0 +1,84 @@
from BaseClasses import Region, Item, ItemClassification
from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation
from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE
from .Regions import connect_regions
from .Rules import set_location_rules
from .Client import *
from typing import ClassVar
from worlds.AutoWorld import World
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
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()
required_client_version = (0, 6, 3)
def __init__(self, *args, **kwargs): #Pulls __init__ function and takes control from there in BaseClasses.py
self.origin_region_name: str = "Mount Crumpit"
super(GrinchWorld, self).__init__(*args, **kwargs)
def generate_early(self) -> None: #Special conditions changed before generation occurs
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():
self.multiworld.regions.append(Region(region_name, self.player, self.multiworld))
self.multiworld.regions.append(Region("Mount Crumpit", self.player, self.multiworld))
for location, data in grinch_locations.items():
region = self.get_region(data.region)
entry = GrinchLocation(self.player, location, region, data)
if location == "Neutralizing Santa":
entry.place_locked_item(Item("Goal", ItemClassification.progression, None, self.player))
region.locations.append(entry)
connect_regions(self)
def create_item(self, item: str) -> GrinchItem: #Creates specific items on demand
if item in ALL_ITEMS_TABLE.keys():
return GrinchItem(item, self.player, ALL_ITEMS_TABLE[item])
raise Exception(f"Invalid item name: {item}")
def create_items(self): #Generates all items for the multiworld
self_itempool: list[GrinchItem] = []
for item, data in ALL_ITEMS_TABLE.items():
self_itempool.append(self.create_item(item))
if item == "Heart of Stone":
for _ in range(3):
self_itempool.append(self.create_item(item))
#Get number of current unfilled locations
unfilled_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) - len(ALL_ITEMS_TABLE.keys()) - 3
for _ in range(unfilled_locations):
self_itempool.append(self.create_item((self.get_other_filler_item(list(MISC_ITEMS_TABLE.keys())))))
self.multiworld.itempool += self_itempool
def set_rules(self):
self.multiworld.completion_condition[self.player] = lambda state: state.has("Goal", self.player)
set_location_rules(self)
def get_other_filler_item(self, other_filler: list[str]) -> str:
return self.random.choices(other_filler)[0]
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:
# print("")
pass

View File

@@ -0,0 +1,5 @@
- Credit to Raven-187 & Hacc on gamehacking.org for providing the addresses for various cheat codes. Without them, this
would of made RAM searching much more tedious.
- Shoutouts to SomeJakeGuy for basically teaching me how to code in general.
- Shoutouts to BootsinSoots for helping with the implementation of the logic rules code itself.
- Thanks to the Grinch PS1 speedrunning discord community server for encouraging the production of this randomizer.

View File

@@ -0,0 +1,26 @@
# The Grinch
## Where is the options page?
The [player options page for this game](../player-options) contains all the
options you need to configure and export a config file.
## What items and locations get shuffled?
Items such as gadgets, sleigh parts, heart of stones, vacuum accesses, and useful items (or mission specific items) are
shuffled. Locations that players can do to send items out include visiting an area for the first time, collecting a blueprint,
and completing a mission.
## What is the goal of The Grinch when randomized?
The player must obtain the Skis, Twin-End Tuba, Exhaust Pipes, and the Tires to be able to build the sleigh and to finally
stop Christmas from coming once and for all by neutralizing Santa Claus!
## Which items can be in another player's world?
- All gadgets such as the binoculars, Rotten Egg Launcher, Rocket Spring, Slime Shooter, Octopus Climbing Device, Marine
Mobile, and the Grinch Copter.
- 4 Heart of Stones that increases the player's max health by one.
- Useful items, or mission specific items, that allow the player to access certain subareas or complete missions that
require that specific item.
- Vacuum accesses that give the player access to Who Forest, Who Dump, and Who Lake respectively.

View File

@@ -0,0 +1,44 @@
# The Grinch - Setup Guide
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.2 or later for integrated
BizHawk support.
- Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.
- CUE files may work, but I have not tested this.
- [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 BizHawk
Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings:
- 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.
## Generating a 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
Once both the client and the emulator are started, they must be connected. **This should happen automatically.**
However, if the lua script window doesn't appear, then within the emulator click
on the "Tools" menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua
script. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
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]`)