182 Commits

Author SHA1 Message Date
MarioSpore
f70b6c4c9c Pseudocode for move_rando pt 2 2025-10-04 23:57:22 -04:00
MarioSpore
79d4d5b10b Renamed inital for Seize 2025-10-04 23:50:44 -04:00
MarioSpore
7fea34adc3 Removed gadget_rando for now 2025-10-04 23:50:29 -04:00
MarioSpore
a3f9e6cbc9 Started psuedocode for moverando logic 2025-10-04 23:37:22 -04:00
MarioSpore
bccc83f864 Fixed Exhaust Pipes not being logically required for WV 2025-10-03 23:07:15 -04:00
SomeJakeGuy
6409721841 Fixed an issue where RingLink could disconnect because BizHawk is paused, so moved it to gamewatcher to be started there instead. 2025-10-03 22:23:16 -04:00
MarioSpore
d3a7b014bd Added gadget_rando & some description adjustments 2025-10-03 16:50:06 -04:00
MarioSpore
3ec8631203 Adjustments for move/gadget rando options 2025-10-03 16:44:34 -04:00
MarioSpore
2081912a39 Fixed Exception error 2025-10-03 16:40:20 -04:00
MarioSpore
67bbde2556 Log for sending locations now retriggers when exiting out of the game 2025-10-02 20:47:32 -04:00
MarioSpore
0503c2ead3 Extra space for newly added initial_cutscene_checker 2025-10-02 20:12:21 -04:00
MarioSpore
7a642cc1a9 Add ram address for additional checking in demo mode checking 2025-10-02 16:27:45 -04:00
MarioSpore
eb8d44e975 Update setup_en.md 2025-10-01 16:44:24 -04:00
MarioSpore
1f35f5fa93 Update setup_en.md 2025-10-01 15:26:16 -04:00
MarioSpore
6bc819f4bc Removed test loggers that was for testing 2025-09-30 01:32:12 -04:00
MarioSpore
bb0c5f5b9a Fixed "WL - South Shore - First Visit" location not generating due to missed space 2025-09-30 01:14:31 -04:00
MarioSpore
0e397c7079 Forgot PC for abbreviation in rules 2025-09-30 00:59:38 -04:00
MarioSpore
2572a25089 Abbreviation of items part 4 w/ rules adjusted with new abbreviations 2025-09-30 00:58:59 -04:00
MarioSpore
dcdf168618 Abbreviation of items part 3 2025-09-30 00:52:38 -04:00
MarioSpore
8a8136a267 Add address for whoville vacuum tube! 2025-09-30 00:50:14 -04:00
MarioSpore
5f158497f9 more inital names added to list 2025-09-29 18:56:02 -04:00
MarioSpore
bc74e67e07 Moverando item renames pt 2 2025-09-29 18:10:18 -04:00
MarioSpore
dbc592dad0 Moverando item renames 2025-09-29 18:05:14 -04:00
MarioSpore
afe1345e34 Added a whole bunch of location groups 2025-09-27 23:09:13 -04:00
MarioSpore
410df2a948 Redone options by various name changes and grammatical changes 2025-09-27 16:11:10 -04:00
MarioSpore
7c6eada7b2 Renamed movesanity to move_rando 2025-09-27 12:15:04 -04:00
MarioSpore
8e1217d1a5 Fixed small issue with location groups & ram watching with goal 2025-09-26 21:40:52 -04:00
MarioSpore
5615277705 Forgot to put Who Dump Vacuum Tube as part of a list for location groups 2025-09-26 18:52:46 -04:00
MarioSpore
2de0f9d766 Crazy I figured this out. Location groups fully works 2025-09-26 18:09:18 -04:00
MarioSpore
4da88cf794 Yeah Jake is right. The ClassVar code is not needed. 2025-09-26 18:05:20 -04:00
MarioSpore
e2cc1b5de7 Possibly implemented location groups? 2025-09-26 17:11:48 -04:00
MarioSpore
103a6f79d1 Maybe fix for compensating for multiple item groups in a list? 2025-09-26 16:52:57 -04:00
MarioSpore
17861c1050 Add abbreviations to various items for ease of typing 2025-09-24 18:56:57 -04:00
MarioSpore
a63c33a711 Added list[str] for location groups for inevitable location group implementation 2025-09-22 23:25:40 -04:00
MarioSpore
c84ef117c8 Removed arbitrary second_item_group variable when we start to implement multiple item groups 2025-09-22 23:24:27 -04:00
MarioSpore
834096f282 Fixed item groups 2025-09-22 23:23:41 -04:00
MarioSpore
0a31d96ee4 Make grinch goal earlier by triggering goal when "Ending (Santa costume)" region is loaded 2025-09-22 21:47:42 -04:00
MarioSpore
3edb733dcb Ringlink toggle command implement 2025-09-22 21:38:06 -04:00
MarioSpore
9c01cc31e0 Found address that handles when grinch is wearing a disguise. Sets address to 0 when "Dump it to Crumpit" is activated and as a failsafe if he is wearing one or not. 2025-09-22 20:36:48 -04:00
MarioSpore
ea8262855e Abbreviated regions & blueprints 2025-09-20 21:41:40 -04:00
MarioSpore
98ea11887e Added Artamiss to credits 2025-09-20 17:21:02 -04:00
MarioSpore
18aefcd3f2 Changed "Access" to "Tube" for planned Progressive vacuum implementation 2025-09-20 15:49:43 -04:00
MarioSpore
3a13332533 Renamed description from "Access" to "Tubes" 2025-09-20 15:48:40 -04:00
MarioSpore
c279ef7bc6 - Major refactoring of location, region, and goal item names 2025-09-20 15:48:24 -04:00
MarioSpore
55ef0cc8c9 Damage is considered "exhaustion" according to the game manual 2025-09-18 22:15:07 -04:00
MarioSpore
babc4f441c Changed "Vacuum Access" to "Vacuum Tube" to reflect the game manual 2025-09-18 22:12:11 -04:00
MarioSpore
92d932da55 Psuedocode generic giftsanity logic 2025-09-14 17:47:03 -04:00
MarioSpore
b1b65a3adf Minor option tweaks 2025-09-14 00:17:32 -04:00
MarioSpore
d6f5e87ccf Option changes based on feedback along with typos and punctuation issues 2025-09-14 00:11:21 -04:00
MarioSpore
079239ea70 Minor tweaks in bios setup 2025-09-13 14:29:03 -04:00
MarioSpore
93bac29e8c Added section for requiring PSX bios and how to set it up 2025-09-13 14:24:55 -04:00
MarioSpore
c9fea29d92 Fixed item groups? 2025-09-13 13:38:08 -04:00
MarioSpore
e17895902e Add unique id to compensate for multiple grinch games in one multiworld 2025-09-13 00:58:12 -04:00
MarioSpore
1596550111 Merge pull request #2 from MarioSpore/devjake
Fixed previous fixes for address optimization.
2025-09-12 23:16:01 -04:00
SomeJakeGuy
c888e17845 Fixed previous fixes for address optimization. 2025-09-12 23:10:19 -04:00
MarioSpore
3dee611b51 Fixed double receive eggs to self 2025-09-12 22:22:54 -04:00
MarioSpore
d6c7a04316 Fixed negative int bug 2025-09-12 21:27:11 -04:00
MarioSpore
900c8a519a Fixed SADX/SA2B crashes? 2025-09-12 05:44:49 -04:00
MarioSpore
43acc9f003 Minefield logical access oversight 2025-09-11 19:35:42 -04:00
MarioSpore
96eb8fcd9a Fix ringlink output 2025-09-11 00:11:25 -04:00
MarioSpore
d4bd682ac9 Fix location send speed code 2025-09-10 23:19:36 -04:00
MarioSpore
61d4783f61 Minor setup doc tweak 2025-09-10 18:49:31 -04:00
MarioSpore
00fff466ff Updated setup docs that actually make more sense 2025-09-10 18:36:38 -04:00
MarioSpore
d8483bef6e Fixes client crash if the emulator is paused with ringlink enabled. Still won't be able to send out ringlink when this occurs 2025-09-09 23:09:56 -04:00
MarioSpore
56a198fcfd Vastly improved speed of remove_physical_items, constant_address_update, & receiving_items_handler 2025-09-09 22:06:07 -04:00
MarioSpore
4e362dc722 Fixed output for ring link to be in a loop 2025-09-09 21:01:56 -04:00
MarioSpore
cfcfc9ecfd Fixed ring link input 2025-09-09 17:44:04 -04:00
MarioSpore
a3a415adfd Merge pull request #1 from MarioSpore/jake_ring-link-fix
Fix some small issues with async on_package and changing things to be async starts instead
2025-09-09 05:46:14 -04:00
SomeJakeGuy
14c95aa85b Fix some small issues with async on_package and changing things to be async starts instead. 2025-09-09 01:29:32 -04:00
MarioSpore
8d941dad6f Adjust import of options to fix AttributeError for previous commit 2025-09-09 00:23:36 -04:00
MarioSpore
8628f6637a Fully implement ringlink 2025-09-09 00:04:09 -04:00
MarioSpore
17b7914c35 Now passing ring link option value to client 2025-09-08 22:48:43 -04:00
MarioSpore
b8dfd5ce4c Adds mount crumpit crate locations 2025-09-08 22:41:22 -04:00
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 1977 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="" />

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

@@ -0,0 +1,436 @@
import time
from typing import TYPE_CHECKING, Sequence
import asyncio
import NetUtils
import copy
import uuid
import Utils
from .Locations import grinch_locations, GrinchLocation
from .Items import ALL_ITEMS_TABLE, MISSION_ITEMS_TABLE, GADGETS_TABLE, KEYS_TABLE, GrinchItemData #, SLEIGH_PARTS_TABLE
import worlds._bizhawk as bizhawk
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
unique_client_id: int = 0
ring_link_enabled = False
def __init__(self):
super().__init__()
self.last_received_index = 0
self.loading_bios_msg = False
self.loc_unlimited_eggs = False
self.unique_client_id = 0
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
from CommonClient import logger
# 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.")
ctx.command_processor.commands["ringlink"] = _cmd_ringlink
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"])
self.unique_client_id = self._get_uuid()
logger.info("You are now connected to the client. "+
"There may be a slight delay to check you are not in demo mode before locations start to send.")
self.ring_link_enabled = bool(ctx.slot_data["ring_link"])
tags = copy.deepcopy(ctx.tags)
if self.ring_link_enabled:
ctx.tags.add("RingLink")
else:
ctx.tags -= { "RingLink" }
if tags != ctx.tags:
Utils.async_start(ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}]), "Update RingLink Tags")
case "Bounced":
if "tags" not in args:
return
if "RingLink" in ctx.tags and "RingLink" in args["tags"] and args["data"]["source"] != self.unique_client_id:
Utils.async_start(self.ring_link_input(args["data"]["amount"], ctx), "SyncEggs")
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
await ctx.get_username()
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
if not any(task.get_name() == "Grinch EggLink" for task in asyncio.all_tasks()):
print("EggLink")
self.send_ring_link = True
Utils.async_start(self.ring_link_output(ctx), name="Grinch EggLink")
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:]
ram_addr_dict: dict[int, list[int]] = {}
for item_received in new_items_only:
local_item = ctx.item_names.lookup_in_game(item_received.item)
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
if addr_to_update.ram_address in ram_addr_dict.keys():
current_ram_address_value = ram_addr_dict[addr_to_update.ram_address][0]
else:
current_ram_address_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
if is_binary:
current_ram_address_value = (current_ram_address_value | (1 << addr_to_update.binary_bit_pos))
elif addr_to_update.update_existing_value:
# 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
ram_addr_dict[addr_to_update.ram_address] = [current_ram_address_value, addr_to_update.bit_size]
self.last_received_index += 1
# Update the latest received item index to ram as well.
ram_addr_dict[RECV_ITEM_ADDR] = [self.last_received_index, RECV_ITEM_BITSIZE]
await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict))
async def goal_checker(self, ctx: "BizHawkClientContext"):
if not ctx.finished_game:
goal_loc = grinch_locations["MC - Sleigh Ride - 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:
if current_ram_address_value == goal_ram_address.value:
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"):
ram_addr_dict: dict[int, list[int]] = {}
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
items_to_check: dict[str, GrinchItemData] = {**GADGETS_TABLE} #, **SLEIGH_PARTS_TABLE
heart_count = len(list(item_id for item_id in list_recv_itemids if item_id == 42570))
heart_item_data = ALL_ITEMS_TABLE["Heart of Stone"]
ram_addr_dict[heart_item_data.update_ram_addr[0].ram_address] = [min(heart_count, 4), 1]
# Setting mission count for all accesses back to 0 to prevent warping/unlocking after completing 3 missions
ram_addr_dict[0x0100F0] = [0, 4]
for (item_name, item_data) in items_to_check.items():
# If item is an event or already been received, ignore.
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:
if addr_to_update.ram_address in ram_addr_dict.keys():
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
else:
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1]
else:
ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size]
await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict))
def convert_dict_to_ram_list(self, addr_dict: dict[int, list[int]]) -> list[tuple[int, Sequence[int], str]]:
addr_list_to_update: list[tuple[int, Sequence[int], str]] = []
for (key, val) in addr_dict.items():
addr_list_to_update.append((key, val[0].to_bytes(val[1], "little"), "MainRAM"))
return addr_list_to_update
# Removes the regional access until you actually received it from AP.
async def constant_address_update(self, ctx: "BizHawkClientContext"):
ram_addr_dict: dict[int, list[int]] = {}
list_recv_itemids: list[int] = [netItem.item for netItem in ctx.items_received]
items_to_check: dict[str, GrinchItemData] = {**KEYS_TABLE, **MISSION_ITEMS_TABLE}
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:
if addr_to_update.ram_address in ram_addr_dict.keys():
current_bin_value = ram_addr_dict[addr_to_update.ram_address][0]
else:
current_bin_value = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
addr_to_update.ram_address, addr_to_update.bit_size, "MainRAM")]))[0], "little")
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
current_bin_value |= (1 << addr_to_update.binary_bit_pos)
else:
current_bin_value &= ~(1 << addr_to_update.binary_bit_pos)
ram_addr_dict[addr_to_update.ram_address] = [current_bin_value, 1]
else:
if GrinchLocation.get_apid(item_data.id) in list_recv_itemids:
ram_addr_dict[addr_to_update.ram_address] = [addr_to_update.value, addr_to_update.bit_size]
else:
ram_addr_dict[addr_to_update.ram_address] = [0, addr_to_update.bit_size]
await bizhawk.write(ctx.bizhawk_ctx, self.convert_dict_to_ram_list(ram_addr_dict))
async def ingame_checker(self, ctx: "BizHawkClientContext"):
from CommonClient import logger
ingame_map_id = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
0x010000, 1, "MainRAM")]))[0], "little")
initial_cutscene_checker = int.from_bytes((await bizhawk.read(ctx.bizhawk_ctx, [(
0x010094, 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:
self.ingame_log = False
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
if initial_cutscene_checker != 1:
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": self.unique_client_id,
"amount": current_egg_count - self.previous_egg_count
},
"tags": ["RingLink"]
}
await ctx.send_msgs([msg])
self.previous_egg_count = current_egg_count
# logger.info(f"RingLink: You sent {str(current_egg_count - self.previous_egg_count)} rotten eggs.")
await asyncio.sleep(0.1)
except Exception as ex:
logger.error("While monitoring grinch's egg count ingame, an error occurred. Details:"+ str(ex))
self.send_ring_link = False
if not ctx.slot:
logger.info("You must be connected to the multi-world in order for RingLink to work properly.")
async def ring_link_input(self, egg_amount: int, ctx: "BizHawkClientContext"):
from CommonClient import logger
game_egg_count = int.from_bytes(
(await bizhawk.read(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR, EGG_ADDR_BYTESIZE, "MainRAM")]))[0], "little")
non_neg_eggs = game_egg_count + egg_amount if game_egg_count + egg_amount > 0 else 0
current_egg_count = min(non_neg_eggs, MAX_EGGS)
await bizhawk.write(ctx.bizhawk_ctx, [(EGG_COUNT_ADDR,
int(current_egg_count).to_bytes(EGG_ADDR_BYTESIZE, "little"), "MainRAM")])
self.previous_egg_count = current_egg_count
# logger.info(f"RingLink: You received {str(egg_amount)} rotten eggs.")
def _get_uuid(self) -> int:
string_id = str(uuid.uuid4())
uid: int = 0
for char in string_id:
uid += ord(char)
return uid
def _cmd_ringlink(self):
"""Toggle ringling from client. Overrides default setting."""
if not self.ctx.slot:
return
Utils.async_start(_update_ring_link(self.ctx, not "RingLink" in self.ctx.tags), name="Update RingLink")
async def _update_ring_link(ctx: "BizHawkClientContext", ring_link: bool):
"""Helper function to set Ring Link connection tag on/off and update the connection if already connected."""
old_tags = copy.deepcopy(ctx.tags)
if ring_link:
ctx.tags.add("RingLink")
else:
ctx.tags -= {"RingLink"}
if old_tags != ctx.tags and ctx.server and not ctx.server.socket.closed:
await ctx.send_msgs([{"cmd": "ConnectUpdate", "tags": ctx.tags}])

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

@@ -0,0 +1,275 @@
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: list[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]
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():
for group in data.item_group: # iterate over each category
categories.setdefault(group, set()).add(name)
return categories
REL: str = "Rotten Egg Launcher"
RS: str = "Rocket Spring"
SS: str = "Slime Shooter"
OCD: str = "Octopus Climbing Device"
MM: str = "Marine Mobile"
GC: str = "Grinch Copter"
WV: str = "Whoville Vacuum Tube"
WF: str = "Who Forest Vacuum Tube"
WD: str = "Who Dump Vacuum Tube"
WL: str = "Who Lake Vacuum Tube"
VT: str = "Progressive Vacuum Tube"
PC: str = "Pancake"
SR: str = "Sleigh Room Key"
BB: str = "Bad Breath"
SZ: str = "Seize"
MX: str = "Max"
SN: str = "Sneak"
WC: str = "Who Cloak"
PB: str = "Painting Bucket"
SC: str = "Scissors"
GB: str = "Glue Bucket"
CCAC: str = "Cable Car Access Card"
DRL: str = "Drill"
RP: str = "Rope"
HK: str = "Hook"
ST: str = "Sculpting Tools"
HMR: str = "Hammer"
SCL: str = "Scout Clothes"
#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", "Slime Gun"], 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", "Useful Items"], 200, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=0)]),
"Painting Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 201, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=1)]),
"Scissors": GrinchItemData(["Mission Specific Items", "Useful Items"], 202, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=6), GrinchRamData(0x0100C2, binary_bit_pos=1)]),
"Glue Bucket": GrinchItemData(["Mission Specific Items", "Useful Items"], 203, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=4)]),
"Cable Car Access Card": GrinchItemData(["Mission Specific Items", "Useful Items"], 204, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=5)]),
"Drill": GrinchItemData(["Mission Specific Items", "Useful Items"], 205, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=2)]),
"Rope": GrinchItemData(["Mission Specific Items", "Useful Items"], 206, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=1)]),
"Hook": GrinchItemData(["Mission Specific Items", "Useful Items"], 207, IC.progression_deprioritized,
[GrinchRamData(0x0101FA, binary_bit_pos=0)]),
"Sculpting Tools": GrinchItemData(["Mission Specific Items", "Useful Items"], 208, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=2)]),
"Hammer": GrinchItemData(["Mission Specific Items", "Useful Items"], 209, IC.progression_deprioritized,
[GrinchRamData(0x0101F9, binary_bit_pos=3)]),
"Scout Clothes": GrinchItemData(["Mission Specific Items", "Useful Items"], 210, IC.progression,
[GrinchRamData(0x0101F9, binary_bit_pos=7)])
}
#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 Tube": GrinchItemData(["Vacuum Tubes"], 400, IC.progression,
[GrinchRamData(0x010200, binary_bit_pos=1)]),
"Who Forest Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 401, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=2)]),
"Who Dump Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 402, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=3)]),
"Who Lake Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 403, IC.progression,
[GrinchRamData(0x0100AA, binary_bit_pos=4)]),
# "Progressive Vacuum Tube": GrinchItemData(["Vacuum Tubes"], 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", "Filler"], 500, IC.filler,
# [GrinchRamData(0x0E8FDC, value=120)]),
"5 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 502, IC.filler,
[GrinchRamData(0x010058, value=5, update_existing_value=True, max_count=200, bit_size=2)]),
"10 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 503, IC.filler,
[GrinchRamData(0x010058, value=10, update_existing_value=True, max_count=200, bit_size=2)]),
"20 Rotten Eggs": GrinchItemData(["Rotten Egg Bundles", "Filler"], 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
# alias to Exhaustion Trap
# "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), GrinchRamData(0x0100B4, value=0)]),
#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

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

@@ -0,0 +1,209 @@
from typing import NamedTuple, Optional
from .RamHandler import GrinchRamData
from BaseClasses import Location, Region
class GrinchLocationData(NamedTuple):
region: str
location_group: Optional[list[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
def get_location_names_per_category() -> dict[str, set[str]]:
categories: dict[str, set[str]] = {}
for name, data in grinch_locations.items():
if data.location_group is None:
continue
for group in data.location_group: # iterate over each category
categories.setdefault(group, set()).add(name)
return categories
grinch_locations = {
#Going to use current map id as indicator whether or not you visited a location
#Visitsanity
"WV - First Visit": GrinchLocationData("Whoville", ["Visitsanity", "Whoville"], 100, [GrinchRamData(0x010000, value=0x07)]),
"WV - Post Office - First Visit": GrinchLocationData("Post Office", ["Visitsanity", "Whoville", "Post Office"], 101, [GrinchRamData(0x010000, value=0x0A)]),
"WV - City Hall - First Visit": GrinchLocationData("City Hall", ["Visitsanity", "Whoville", "City Hall"], 102, [GrinchRamData(0x010000, value=0x08)]),
"WV - Clock Tower - First Visit": GrinchLocationData("Clock Tower", ["Visitsanity", "Whoville", "Clock Tower"], 103, [GrinchRamData(0x010000, value=0x09)]),
"WF - First Visit": GrinchLocationData("Who Forest", ["Visitsanity", "Who Forest"], 104, [GrinchRamData(0x010000, value=0x0B)]),
"WF - Ski Resort - First Visit": GrinchLocationData("Ski Resort", ["Visitsanity", "Who Forest", "Ski Resort"], 105, [GrinchRamData(0x010000, value=0x0C)]),
"WF - Civic Center - First Visit": GrinchLocationData("Civic Center", ["Visitsanity", "Who Forest", "Civic Center"], 106, [GrinchRamData(0x010000, value=0x0D)]),
"WD - First Visit": GrinchLocationData("Who Dump", ["Visitsanity", "Who Dump"], 107, [GrinchRamData(0x010000, value=0x0E)]),
"WD - Minefield - First Visit": GrinchLocationData("Minefield", ["Visitsanity", "Who Dump", "Minefield"], 108, [GrinchRamData(0x010000, value=0x11)]),
"WD - Power Plant - First Visit": GrinchLocationData("Power Plant", ["Visitsanity", "Who Dump", "Power Plant"], 109, [GrinchRamData(0x010000, value=0x10)]),
"WD - Generator Building - First Visit": GrinchLocationData("Generator Building", ["Visitsanity", "Who Dump", "Generator Building"], 110, [GrinchRamData(0x010000, value=0x0F)]),
"WL - South Shore - First Visit": GrinchLocationData("Who Lake", ["Visitsanity", "Who Lake", "South Shore"], 111, [GrinchRamData(0x010000, value=0x12)]),
"WL - Submarine World - First Visit": GrinchLocationData("Submarine World", ["Visitsanity", "Who Lake", "Submarine World"], 112, [GrinchRamData(0x010000, value=0x17)]),
"WL - Scout's Hut - First Visit": GrinchLocationData("Scout's Hut", ["Visitsanity", "Who Lake", "Scout's Hut"], 113, [GrinchRamData(0x010000, value=0x13)]),
"WL - North Shore - First Visit": GrinchLocationData("North Shore", ["Visitsanity", "Who Lake", "North Shore"], 114, [GrinchRamData(0x010000, value=0x14)]),
"WL - Mayor's Villa - First Visit": GrinchLocationData("Mayor's Villa", ["Visitsanity", "Who Lake", "Mayor's Villa"], 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
"WV - Post Office - Shuffling The Mail": GrinchLocationData("Post Office", ["Whoville Missions", "Missions", "Whoville", "Post Office"], 201, [GrinchRamData(0x0100BE, binary_bit_pos=0)]),
"WV - Smashing Snowmen": GrinchLocationData("Whoville", ["Whoville Missions", "Missions", "Whoville"], 200, [GrinchRamData(0x0100C5, value=10)]),
"WV - Painting The Mayor's Posters": GrinchLocationData("Whoville", ["Whoville Missions", "Missions", "Whoville"], 202, [GrinchRamData(0x0100C6, value=10)]),
"WV - Launching Eggs Into Houses": GrinchLocationData("Whoville", ["Whoville Missions", "Missions", "Whoville"], 203, [GrinchRamData(0x0100C7, value=10)]),
"WV - City Hall - Modifying The Mayor's Statue": GrinchLocationData("City Hall", ["Whoville Missions", "Missions", "Whoville", "City Hall"], 204, [GrinchRamData(0x0100BE, binary_bit_pos=1)]),
"WV - Clock Tower - Advancing The Countdown-To-Xmas Clock": GrinchLocationData("Clock Tower", ["Whoville Missions", "Missions", "Whoville", "Clock Tower"], 205, [GrinchRamData(0x0100BE, binary_bit_pos=2)]),
"WV - Squashing All Gifts": GrinchLocationData("Whoville", ["Whoville Missions", "Missions", "Giftsanity", "Whoville"], 206, [GrinchRamData(0x01005C, value=500, bit_size=2)]),
#Who Forest Missions
"WF - Making Xmas Trees Droop": GrinchLocationData("Who Forest", ["Who Forest Missions", "Missions", "Who Forest"], 300, [GrinchRamData(0x0100C8, value=10)]),
"WF - Sabotaging Snow Cannon With Glue": GrinchLocationData("Who Forest", ["Who Forest Missions", "Missions", "Who Forest"], 301, [GrinchRamData(0x0100BE, binary_bit_pos=3)]),
"WF - Putting Beehives In Cabins": GrinchLocationData("Who Forest", ["Who Forest Missions", "Missions", "Who Forest"], 302, [GrinchRamData(0x0100CA, value=10)]),
"WF - Ski Resort - Sliming The Mayor's Skis": GrinchLocationData("Ski Resort", ["Who Forest Missions", "Missions", "Who Forest", "Ski Resort"], 303, [GrinchRamData(0x0100BE, binary_bit_pos=4)]),
"WF - Civic Center - Replacing The Candles On The Cake With Fireworks": GrinchLocationData("Civic Center", ["Who Forest Missions", "Missions", "Who Forest", "Civic Center"], 304, [GrinchRamData(0x0100BE, binary_bit_pos=5)]),
"WF - Squashing All Gifts": GrinchLocationData("Who Forest", ["Who Forest Missions", "Missions", "Giftsanity", "Who Forest"], 305, [GrinchRamData(0x01005E, value=750, bit_size=2)]),
#Who Dump Missions
"WD - Stealing Food From Birds": GrinchLocationData("Who Dump", ["Who Dump Missions", "Missions", "Who Dump"], 400, [GrinchRamData(0x0100CB, value=10)]),
"WD - Feeding The Computer With Robot Parts": GrinchLocationData("Who Dump", ["Who Dump Missions", "Missions", "Who Dump"], 401, [GrinchRamData(0x0100BF, binary_bit_pos=2)]),
"WD - Infesting The Mayor's House With Rats": GrinchLocationData("Who Dump", ["Who Dump Missions", "Missions", "Who Dump"], 402, [GrinchRamData(0x0100BE, binary_bit_pos=6)]),
"WD - Conducting The Stinky Gas To Who-Bris' Shack": GrinchLocationData("Who Dump", ["Who Dump Missions", "Missions", "Who Dump"], 403, [GrinchRamData(0x0100BE, binary_bit_pos=7)]),
"WD - Minefield - Shaving Who Dump Guardian": GrinchLocationData("Minefield", ["Who Dump Missions", "Missions", "Who Dump", "Minefield"], 404, [GrinchRamData(0x0100BF, binary_bit_pos=0)]),
"WD - Generator Building - Short-Circuiting Power-Plant": GrinchLocationData("Generator Building", ["Who Dump Missions", "Missions", "Who Dump", "Generator Building"], 405, [GrinchRamData(0x0100BF, binary_bit_pos=1)]),
"WD - Squashing All Gifts": GrinchLocationData("Who Dump", ["Who Dump Missions", "Missions", "Who Dump", "Giftsanity"], 406, [GrinchRamData(0x010060, value=750, bit_size=2)]),
#Who Lake Missions
"WL - South Shore - Putting Thistles In Shorts": GrinchLocationData("Who Lake", ["Who Lake Missions", "Missions", "Who Lake", "South Shore", "South Shore Missions"], 500, [GrinchRamData(0x0100E5, value=10)]),
"WL - South Shore - Sabotaging The Tents": GrinchLocationData("Who Lake", ["Who Lake Missions", "Missions", "Who Lake", "South Shore", "South Shore Missions"], 501, [GrinchRamData(0x0100E6, value=10)]),
"WL - North Shore - Drilling Holes In Canoes": GrinchLocationData("North Shore", ["Who Lake Missions", "Missions", "Who Lake", "North Shore"], 502, [GrinchRamData(0x0100EE, value=10)]),
"WL - Submarine World - Modifying The Marine Mobile": GrinchLocationData("Submarine World", ["Who Lake Missions", "Missions", "Who Lake", "Submarine World"], 503, [GrinchRamData(0x0100BF, binary_bit_pos=4)]),
"WL - Mayor's Villa - Hooking The Mayor's Bed To The Motorboat": GrinchLocationData("Mayor's Villa", ["Who Lake Missions", "Missions", "Who Lake", "Mayor's Villa"], 504, [GrinchRamData(0x0100BF, binary_bit_pos=3)]),
"WL - Squashing All Gifts": GrinchLocationData("Who Lake", ["Who Lake Missions", "Missions", "Who Lake", "Giftsanity"], 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
"WV - Binoculars BP on Post Office Roof": GrinchLocationData("Whoville", ["Binocular Blueprints", "Blueprints", "Whoville", "Whoville Blueprints"], 600, [GrinchRamData(0x01020B, binary_bit_pos=2)]),
"WV - City Hall - Binoculars BP left side of Library": GrinchLocationData("City Hall", ["Binocular Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "City Hall", "City Hall Blueprints"], 601, [GrinchRamData(0x01021F, binary_bit_pos=6)]),
"WV - City Hall - Binoculars BP front side of Library": GrinchLocationData("City Hall", ["Binocular Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "City Hall", "City Hall Blueprints"], 602, [GrinchRamData(0x01021F, binary_bit_pos=5)]),
"WV - City Hall - Binoculars BP right side of Library": GrinchLocationData("City Hall", ["Binocular Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "City Hall", "City Hall Blueprints"], 603, [GrinchRamData(0x01021F, binary_bit_pos=4)]),
#Rotten Egg Launcher Blueprints
"WV - REL BP left of City Hall": GrinchLocationData("Whoville", ["Rotten Egg Launcher Blueprints", "Blueprints", "Whoville", "Whoville Blueprints"], 700, [GrinchRamData(0x01020B, binary_bit_pos=0)]),
"WV - REL BP left of Clock Tower": GrinchLocationData("Whoville", ["Rotten Egg Launcher Blueprints", "Blueprints", "Whoville", "Whoville Blueprints"], 701, [GrinchRamData(0x01020B, binary_bit_pos=1)]),
"WV - Post Office - REL BP inside Silver Room": GrinchLocationData("Post Office", ["Rotten Egg Launcher Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "Post Office", "Post Office Blueprints"], 702, [GrinchRamData(0x01021C, binary_bit_pos=1)]),
"WV - Post Office - REL BP at Entrance Door after Mission Completion": GrinchLocationData("Post Office", ["Rotten Egg Launcher Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "Post Office", "Post Office Blueprints"], 703, [GrinchRamData(0x01021C, binary_bit_pos=2)]),
#Rocket Spring Blueprints
"WF - RS BP behind Vacuum Tube": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 800, [GrinchRamData(0x010243, binary_bit_pos=3)]),
"WF - RS BP in front of 2nd House near Vacuum Tube": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 801, [GrinchRamData(0x010243, binary_bit_pos=1)]),
"WF - RS BP near Tree House on Ground": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 802, [GrinchRamData(0x010243, binary_bit_pos=4)]),
"WF - RS BP behind Cable Car House": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 804, [GrinchRamData(0x010242, binary_bit_pos=7)]),
"WF - RS BP near Who Snowball in Cave": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 805, [GrinchRamData(0x010242, binary_bit_pos=6)]),
"WF - RS BP on Branch Platform closest to Glue Cannon": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 806, [GrinchRamData(0x010243, binary_bit_pos=2)]),
"WF - RS BP on Branch Platform Near Beast": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 807, [GrinchRamData(0x010243, binary_bit_pos=0)]),
"WF - RS BP on Branch Platform Elevated next to House": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 808, [GrinchRamData(0x010243, binary_bit_pos=6)]),
"WF - RS BP on Tree House": GrinchLocationData("Who Forest", ["Rocket Spring Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 809, [GrinchRamData(0x010243, binary_bit_pos=5)]),
#Slime Shooter Blueprints
"WF - SS BP in Branch Platform Elevated House": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 900, [GrinchRamData(0x010244, binary_bit_pos=3)]),
"WF - SS BP in Branch Platform House next to Beast": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 901, [GrinchRamData(0x010243, binary_bit_pos=7)]),
"WF - SS BP in House in front of Civic Center Cave": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 902, [GrinchRamData(0x010244, binary_bit_pos=2)]),
"WF - SS BP in House next to Tree House": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 903, [GrinchRamData(0x010244, binary_bit_pos=1)]),
"WF - SS BP in House across from Tree House": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 904, [GrinchRamData(0x010244, binary_bit_pos=5)]),
"WF - SS BP in 2nd House near Vacuum Tube Right Side": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 905, [GrinchRamData(0x010244, binary_bit_pos=4)]),
"WF - SS BP in 2nd House near Vacuum Tube Left Side": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 906, [GrinchRamData(0x010244, binary_bit_pos=7)]),
"WF - SS BP in 2nd House near Vacuum Tube inbetween Blueprints": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 907, [GrinchRamData(0x010244, binary_bit_pos=6)]),
"WF - SS BP in House near Vacuum Tube": GrinchLocationData("Who Forest", ["Slime Shooter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints"], 908, [GrinchRamData(0x010244, binary_bit_pos=0)]),
#Octopus Climbing Device
"WD - OCD BP inside Middle Pipe": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1001, [GrinchRamData(0x010252, binary_bit_pos=3)]),
"WD - OCD BP inside Right Pipe": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1002, [GrinchRamData(0x010252, binary_bit_pos=5)]),
"WD - OCD BP in Vent to Mayor's House": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1003, [GrinchRamData(0x010252, binary_bit_pos=1)]),
"WD - OCD BP inside Left Pipe": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1004, [GrinchRamData(0x010252, binary_bit_pos=4)]),
"WD - OCD BP near Right Side of Power Plant Wall": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1005, [GrinchRamData(0x010252, binary_bit_pos=0)]),
"WD - OCD BP near Who-Bris' Shack": GrinchLocationData("Who Dump", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints"], 1006, [GrinchRamData(0x010252, binary_bit_pos=2)]),
"WD - Minefield - OCD BP on Left Side of House": GrinchLocationData("Minefield", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Minefield", "Minefield Blueprints"], 1007, [GrinchRamData(0x01026E, binary_bit_pos=2)]),
"WD - Minefield - OCD BP on Right Side of Shack": GrinchLocationData("Minefield", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Minefield", "Minefield Blueprints"], 1008, [GrinchRamData(0x01026E, binary_bit_pos=4)]),
"WD - Minefield - OCD BP inside Guardian's House": GrinchLocationData("Minefield", ["Octopus Climbing Device Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Minefield", "Minefield Blueprints"], 1009, [GrinchRamData(0x01026E, binary_bit_pos=3)]),
#Marine Mobile Blueprints
"WL - South Shore - MM BP on Bridge to Scout's Hut": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1100, [GrinchRamData(0x010281, binary_bit_pos=5)]),
"WL - South Shore - MM BP across from Tent near Porcupine": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1101, [GrinchRamData(0x010281, binary_bit_pos=6)]),
"WL - South Shore - MM BP near Outhouse": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1102, [GrinchRamData(0x010281, binary_bit_pos=7)]),
"WL - South Shore - MM BP near Hill Bridge": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1103, [GrinchRamData(0x010282, binary_bit_pos=0)]),
"WL - South Shore - MM BP on Scout's Hut Roof": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1104, [GrinchRamData(0x010281, binary_bit_pos=4)]),
"WL - South Shore - MM BP on Grass Platform": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1105, [GrinchRamData(0x010281, binary_bit_pos=2)]),
"WL - South Shore - MM BP across Zipline Platform": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1106, [GrinchRamData(0x010281, binary_bit_pos=3)]),
"WL - South Shore - MM BP behind Summer Beast": GrinchLocationData("Who Lake", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "South Shore", "South Shore Blueprints"], 1107, [GrinchRamData(0x010282, binary_bit_pos=1)]),
"WL - North Shore - MM BP below Bridge": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1108, [GrinchRamData(0x010293, binary_bit_pos=0)]),
"WL - North Shore - MM BP behind Skunk Hut": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1109, [GrinchRamData(0x010293, binary_bit_pos=2)]),
"WL - North Shore - MM BP inside Skunk Hut": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1110, [GrinchRamData(0x010292, binary_bit_pos=6)]),
"WL - North Shore - MM BP inside House's Fence": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1111, [GrinchRamData(0x010292, binary_bit_pos=7)]),
"WL - North Shore - MM BP inside Boulder Box near Bridge": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1112, [GrinchRamData(0x010293, binary_bit_pos=3)]),
"WL - North Shore - MM BP inside Boulder Box behind Skunk Hut": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1113, [GrinchRamData(0x010293, binary_bit_pos=4)]),
"WL - North Shore - MM BP inside Drill House": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1114, [GrinchRamData(0x010292, binary_bit_pos=5)]),
"WL - North Shore - MM BP on Crow Platform near Drill House": GrinchLocationData("North Shore", ["Marine Mobile Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "North Shore", "North Shore Blueprints"], 1115, [GrinchRamData(0x010293, binary_bit_pos=1)]),
#Grinch Copter Blueprints
"WV - City Hall - GC BP in Safe Room": GrinchLocationData("City Hall", ["Grinch Copter Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "City Hall", "City Hall Blueprints"], 1200, [GrinchRamData(0x01021F, binary_bit_pos=7)]),
"WV - City Hall - GC BP in Statue Room": GrinchLocationData("City Hall", ["Grinch Copter Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "City Hall", "City Hall Blueprints"], 1201, [GrinchRamData(0x010220, binary_bit_pos=0)]),
"WV - Clock Tower - GC BP in Bedroom": GrinchLocationData("Clock Tower", ["Grinch Copter Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "Clock Tower", "Clock Tower Blueprints"], 1202, [GrinchRamData(0x010216, binary_bit_pos=3)]),
"WV - Clock Tower - GC BP in Bell Room": GrinchLocationData("Clock Tower", ["Grinch Copter Blueprints", "Blueprints", "Whoville", "Whoville Blueprints", "Clock Tower", "Clock Tower Blueprints"], 1203, [GrinchRamData(0x010216, binary_bit_pos=2)]),
"WF - Ski Resort - GC BP inside Dog's Fence": GrinchLocationData("Ski Resort", ["Grinch Copter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints", "Ski Resort", "Ski Resort Blueprints"], 1204, [GrinchRamData(0x010234, binary_bit_pos=7)]),
"WF - Ski Resort - GC BP in Max Cave": GrinchLocationData("Ski Resort", ["Grinch Copter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints", "Ski Resort", "Ski Resort Blueprints"], 1205, [GrinchRamData(0x010234, binary_bit_pos=6)]),
"WF - Civic Center - GC BP on Left Side in Bat Cave Wall": GrinchLocationData("Civic Center", ["Grinch Copter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints", "Civic Center", "Civic Center Blueprints"], 1206, [GrinchRamData(0x01022A, binary_bit_pos=7)]),
"WF - Civic Center - GC BP in Frozen Ice": GrinchLocationData("Civic Center", ["Grinch Copter Blueprints", "Blueprints", "Who Forest", "Who Forest Blueprints", "Civic Center", "Civic Center Blueprints"], 1207, [GrinchRamData(0x01022B, binary_bit_pos=0)]),
"WD - Power Plant - GC BP in Max Cave": GrinchLocationData("Power Plant", ["Grinch Copter Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Power Plant", "Power Plant Blueprints"], 1208, [GrinchRamData(0x010265, binary_bit_pos=1)]),
"WD - Power Plant - GC BP After First Gate": GrinchLocationData("Power Plant", ["Grinch Copter Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Power Plant", "Power Plant Blueprints"], 1209, [GrinchRamData(0x010265, binary_bit_pos=2)]),
"WD - Generator Building - GC BP on the Highest Platform": GrinchLocationData("Generator Building", ["Grinch Copter Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Generator Building", "Generator Building Blueprints"], 1210, [GrinchRamData(0x01026B, binary_bit_pos=0)]),
"WD - Generator Building - GC BP at the Entrance after Mission Completion": GrinchLocationData("Generator Building", ["Grinch Copter Blueprints", "Blueprints", "Who Dump", "Who Dump Blueprints", "Generator Building", "Generator Building Blueprints"], 1211, [GrinchRamData(0x01026B, binary_bit_pos=1)]),
"WL - Submarine World - GC BP Just Below Water Surface": GrinchLocationData("Submarine World", ["Grinch Copter Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "Submarine World", "Submarine World Blueprints"], 1212, [GrinchRamData(0x010289, binary_bit_pos=3)]),
"WL - Submarine World - GC BP Underwater": GrinchLocationData("Submarine World", ["Grinch Copter Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "Submarine World", "Submarine World Blueprints"], 1213, [GrinchRamData(0x010289, binary_bit_pos=4)]),
"WL - Mayor's Villa - GC BP on Tree Branch": GrinchLocationData("Mayor's Villa", ["Grinch Copter Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "Mayor's Villa", "Mayor's Villa Blueprints"], 1214, [GrinchRamData(0x010275, binary_bit_pos=7)]),
"WL - Mayor's Villa - GC BP in Pirate's Cave": GrinchLocationData("Mayor's Villa", ["Grinch Copter Blueprints", "Blueprints", "Who Lake", "Who Lake Blueprints", "Mayor's Villa", "Mayor's Villa Blueprints"], 1215, [GrinchRamData(0x010275, binary_bit_pos=6)]),
#Sleigh Room Locations
"MC - Sleigh Ride - Stealing All Gifts": GrinchLocationData("Sleigh Room", ["Sleigh Ride"], 1300, [GrinchRamData(0x0100BF, binary_bit_pos=6)]),
"MC - Sleigh Ride - Neutralizing Santa": GrinchLocationData("Sleigh Room", None, None, [GrinchRamData(0x010000, value=0x3E)]),#[GrinchRamData(0x0100BF, binary_bit_pos=7)]),
#Heart of Stones
"WV - Post Office - Heart of Stone": GrinchLocationData("Post Office", ["Heart of Stones", "Whoville", "Post Office"], 1400, [GrinchRamData(0x0101FA, binary_bit_pos=6)]),
"WF - Ski Resort - Heart of Stone": GrinchLocationData("Ski Resort", ["Heart of Stones", "Who Forest", "Ski Resort"], 1401, [GrinchRamData(0x0101FA, binary_bit_pos=7)]),
"WD - Minefield - Heart of Stone": GrinchLocationData("Minefield", ["Heart of Stones", "Who Dump", "Minefield"], 1402, [GrinchRamData(0x0101FB, binary_bit_pos=0)]),
"WL - North Shore - Heart of Stone": GrinchLocationData("North Shore", ["Heart of Stones", "Who Lake", "North Shore"], 1403, [GrinchRamData(0x0101FB, binary_bit_pos=1)]),
#Supadow Minigames
# "Spin N' Win - Easy": GrinchLocationData("Spin N' Win", ["Supadow Minigames", "Spin N' Win"], 1500, [GrinchRamData()]),
# "Spin N' Win - Hard": GrinchLocationData("Spin N' Win", ["Supadow Minigames", "Spin N' Win"], 1501, [GrinchRamData()]),
# "Spin N' Win - Real Tough": GrinchLocationData("Spin N' Win", ["Supadow Minigames", "Spin N' Win"], 1502, [GrinchRamData()]),
# "Dankamania - Easy - 15 Points": GrinchLocationData("Dankamania", ["Supadow Minigames", "Dankamania"], 1503, [GrinchRamData()]),
# "Dankamania - Hard - 15 Points": GrinchLocationData("Dankamania", ["Supadow Minigames", "Dankamania"], 1504, [GrinchRamData()]),
# "Dankamania - Real Tough - 15 Points": GrinchLocationData("Dankamania", ["Supadow Minigames", "Dankamania"], 1505, [GrinchRamData()]),
# "The Copter Race Contest - Easy": GrinchLocationData("The Copter Race Contest", ["Supadow Minigames", "The Copter Race Contest"], 1506, [GrinchRamData()]),
# "The Copter Race Contest - Hard": GrinchLocationData("The Copter Race Contest", ["Supadow Minigames", "The Copter Race Contest"], 1507, [GrinchRamData()]),
# "The Copter Race Contest - Real Tough": GrinchLocationData("The Copter Race Contest", ["Supadow Minigames", "The Copter Race Contest"], 1508, [GrinchRamData()]),
# "Bike Race - 1st Place": GrinchLocationData("Bike Race", ["Supadow Minigames", "Bike Race"], 1509, [GrinchRamData()]),
# "Bike Race - Top 2": GrinchLocationData("Bike Race", ["Supadow Minigames", "Bike Race"], 1510, [GrinchRamData()]),
# "Bike Race - Top 3": GrinchLocationData("Bike Race", ["Supadow Minigames", "Bike Race"], 1511, [GrinchRamData()]),
# Sleigh Part Locations
"WV - Exhaust Pipes": GrinchLocationData("Sleigh Room", ["Sleigh Ride", "Whoville"], 1600, [GrinchRamData(0x0101FB, binary_bit_pos=2)]),
"WF - Skis": GrinchLocationData("Sleigh Room", ["Sleigh Ride", "Who Forest"], 1601, [GrinchRamData(0x0101FB, binary_bit_pos=3)]),
"WD - Tires": GrinchLocationData("Sleigh Room", ["Sleigh Ride", "Who Dump"], 1602, [GrinchRamData(0x0101FB, binary_bit_pos=4)]),
"WL - Submarine World - Twin-End Tuba": GrinchLocationData("Sleigh Room", ["Sleigh Ride", "Who Lake", "South Shore"], 1603, [GrinchRamData(0x0101FB, binary_bit_pos=6)]),
"WL - South Shore - GPS": GrinchLocationData("Sleigh Room", ["Sleigh Ride", "Who Lake", "Submarine World"], 1604, [GrinchRamData(0x0101FB, binary_bit_pos=5)]),
# Mount Crumpit Locations
"MC - 1st Crate Squashed": GrinchLocationData("Mount Crumpit", ["Mount Crumpit"], 1700, [GrinchRamData(0x095343, value=1)]),
"MC - 2nd Crate Squashed": GrinchLocationData("Mount Crumpit", ["Mount Crumpit"], 1701, [GrinchRamData(0x095343, value=2)]),
"MC - 3rd Crate Squashed": GrinchLocationData("Mount Crumpit", ["Mount Crumpit"], 1702, [GrinchRamData(0x095343, value=3)]),
"MC - 4th Crate Squashed": GrinchLocationData("Mount Crumpit", ["Mount Crumpit"], 1703, [GrinchRamData(0x095343, value=4)]),
"MC - 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

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

@@ -0,0 +1,108 @@
from dataclasses import dataclass
from Options import FreeText, NumericOption, Toggle, DefaultOnToggle, Choice, TextChoice, Range, NamedRange, OptionList, \
PerGameCommonOptions, OptionSet
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 Tubes"
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 ExcludeEnvironments(OptionSet):
"""
Allows entire environments to be an excluded location to ensure you are not logically required to enter the environment along
with any and all checks that are in that environment too.
WARNING: Excluding too many environments may cause generation to fail.
[NOT IMPLEMENTED]
Valid keys: "Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall",
"Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut",
"North Shore", "Mayor's Villa", "Sleigh Ride"
"""
display_name = "Exclude Environments"
valid_keys = {"Whoville", "Who Forest", "Who Dump", "Who Lake", "Post Office", "Clock Tower", "City Hall",
"Ski Resort", "Civic Center", "Minefield", "Power Plant", "Generator Building", "Scout's Hut",
"North Shore", "Mayor's Villa", "Sleigh Ride"}
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. NOT IMPLEMENTED]"""
display_name = "Supadow Minigames"
class Gifts(Range):
"""
Considers how many gifts must be squashed per check.
Enabling this will also enable squashing all gifts in a region mission along side this. [NOT IMPLEMENTED]
"""
display_name = "Gifts Squashed per Check"
range_start = 0
range_end = 300
default = 0
class Moverando(Toggle):
"""Randomizes Grinch's moveset along with randomizing max into the pool. [NOT IMPLEMENTED]
"""
display_name = "Moves Randomized"
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."""
display_name = "Ring Link"
class TrapLinkOption(Toggle):
"""If a trap is sent from Grinch, traps that are compatible with other games are triggered as well. [NOT IMPLEMENTED]"""
display_name = "Trap Link"
@dataclass
class GrinchOptions(PerGameCommonOptions):#DeathLinkMixin
starting_area: StartingArea
progressive_vacuum: ProgressiveVacuum
missionsanity: Missionsanity
exclude_environments: ExcludeEnvironments
progressive_gadget: ProgressiveGadget
supadow_minigames: Supadow
giftsanity: Gifts
move_rando: Moverando
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",
"Clock 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")
grinchconnect(world, "Mount Crumpit", "Dankamania")
grinchconnect(world, "Mount Crumpit", "The Copter Race Contest")
grinchconnect(world, "Whoville", "Post Office")
grinchconnect(world, "Whoville", "City Hall")
grinchconnect(world, "Whoville", "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")

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

@@ -0,0 +1,664 @@
from typing import Callable
import Utils
from BaseClasses import CollectionState
from worlds.AutoWorld import World
from worlds.generic.Rules import add_rule
from .Items import *
#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]]] = {
"WV - First Visit": [
[]
],
"WV - Post Office - First Visit": [
[]
],
"WV - City Hall - First Visit": [
[]
],
"WV - Clock Tower - First Visit": [
[]
],
"WF - First Visit": [
[]
],
"WF - Ski Resort - First Visit": [
[]
],
"WF - Civic Center - First Visit": [
[]
],
"WD - First Visit": [
[]
],
"WD - Minefield - First Visit": [
[]
],
"WD - Power Plant - First Visit": [
[]
],
"WD - Generator Building - First Visit": [
[]
],
"WL - South Shore - First Visit": [
[]
],
"WL - Submarine World - First Visit": [
[]
],
"WL - Scout's Hut - First Visit": [
[]
],
"WL - North Shore - First Visit": [
[]
],
"WL - Mayor's Villa - First Visit": [
[]
],
"WV - Post Office - Shuffling The Mail": [
[]
],
"WV - Smashing Snowmen": [
[]
# "move_rando"
# [PC]
],
"WV - Painting The Mayor's Posters": [
[PB]
],
"WV - Launching Eggs Into Houses": [
[REL]
],
"WV - City Hall - Modifying The Mayor's Statue": [
[ST]
# "move_rando"
# [ST, SN],
# [ST, SS]
],
"WV - Clock Tower - Advancing The Countdown-To-Xmas Clock": [
[HMR, RS]
],
"WV - Squashing All Gifts": [
[GC, SS, REL, WC, RS]
],
"WF - Making Xmas Trees Droop": [
[REL]
# "move_rando"
# [REL, BB]
],
"WF - Sabotaging Snow Cannon With Glue": [
[GB, RS],
[GB, GC]
],
"WF - Putting Beehives In Cabins": [
[REL, RS],
[REL, GC]
],
"WF - Ski Resort - Sliming The Mayor's Skis": [
[SS, REL]
],
"WF - Civic Center - Replacing The Candles On The Cake With Fireworks": [
[REL, GC],
[REL, OCD, RS]
# "move_rando"
# [REL, GC],
# [REL, OCD, RS, SN],
# [REL, OCD, RS, SS]
],
"WF - Squashing All Gifts": [
[GC, CCAC, SS, REL],
[OCD, RS, CCAC, SS, REL]
],
"WD - Stealing Food From Birds": [
[RS, REL]
# "move_rando"
# [RS, REL, PC]
],
"WD - Feeding The Computer With Robot Parts": [
[RS, REL]
# "move_rando"
# [RS, REL, PC]
],
"WD - Infesting The Mayor's House With Rats": [
[REL, RS],
[REL, GC]
# "move_rando"
# [REL, RS, PC],
# [REL, GC, PC]
],
"WD - Conducting The Stinky Gas To Who-Bris' Shack": [
[RS, REL]
# "move_rando"
# [RS, REL, PC]
],
"WD - Minefield - Shaving Who Dump Guardian": [
[SC, GC],
[SC, SS, RS]
# "move_rando"
# [SC, GC, SN],
# [SC, SS, RS, SN]
],
"WD - Generator Building - Short-Circuiting Power-Plant": [
[REL, GC],
[REL, OCD, SS, RS]
],
"WD - Squashing All Gifts": [
[GC, RS, SS, REL],
[OCD, RS, SS, REL]
],
"WL - South Shore - Putting Thistles In Shorts": [
[REL, OCD],
[REL, GC]
],
"WL - South Shore - Sabotaging The Tents": [
[OCD, RS],
[GC]
],
"WL - North Shore - Drilling Holes In Canoes": [
[DRL]
],
"WL - Submarine World - Modifying The Marine Mobile": [
[]
],
"WL - Mayor's Villa - Hooking The Mayor's Bed To The Motorboat": [
[RP, HK, REL, SCL]
],
"WL - Squashing All Gifts": [
[GC, MM, SCL, REL, HK, RP],
[OCD, RS, MM, SCL, REL, HK, RP]
],
"WV - Binoculars BP on Post Office Roof": [
[]
],
"WV - City Hall - Binoculars BP left side of Library": [
[]
],
"WV - City Hall - Binoculars BP front side of Library": [
[]
],
"WV - City Hall - Binoculars BP right side of Library": [
[]
],
"WV - REL BP left of City Hall": [
[]
],
"WV - REL BP left of Clock Tower": [
[]
],
"WV - Post Office - REL BP inside Silver Room": [
[WC]
# "move_rando"
# [WC, MX]
],
"WV - Post Office - REL BP at Entrance Door after Mission Completion": [
[WC]
# "move_rando"
# [WC, MX]
],
"WF - RS BP behind Vacuum Tube": [
[]
],
"WF - RS BP in front of 2nd House near Vacuum Tube": [
[]
],
"WF - RS BP near Tree House on Ground": [
[]
],
"WF - RS BP behind Cable Car House": [
[]
],
"WF - RS BP near Who Snowball in Cave": [
[]
],
"WF - RS BP on Branch Platform closest to Glue Cannon": [
[]
],
"WF - RS BP on Branch Platform Near Beast": [
[]
],
"WF - RS BP on Branch Platform Elevated next to House": [
[]
],
"WF - RS BP on Tree House": [
[REL],
[GC]
],
"WF - SS BP in Branch Platform Elevated House": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in Branch Platform House next to Beast": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in House in front of Civic Center Cave": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in House next to Tree House": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in House across from Tree House": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in 2nd House near Vacuum Tube Right Side": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in 2nd House near Vacuum Tube Left Side": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in 2nd House near Vacuum Tube inbetween Blueprints": [
[REL, RS],
[REL, GC]
],
"WF - SS BP in House near Vacuum Tube": [
[REL, RS],
[REL, GC]
],
"WD - OCD BP inside Middle Pipe": [
[REL, RS],
[REL, GC],
[SS, RS],
[SS, GC]
],
"WD - OCD BP inside Right Pipe": [
[REL, RS],
[REL, GC]
],
"WD - OCD BP in Vent to Mayor's House": [
[REL, RS],
[REL, GC]
],
"WD - OCD BP inside Left Pipe": [
[REL, RS],
[REL, GC],
[SS, RS],
[SS, GC]
],
"WD - OCD BP near Right Side of Power Plant Wall": [
[REL, RS],
[REL, GC],
[SS, RS],
[SS, GC]
],
"WD - OCD BP near Who-Bris' Shack": [
[REL, RS]
],
"WD - Minefield - OCD BP on Left Side of House": [
[]
# "move_rando"
# [REL, GC],
# [REL, SS, RS]
# [MX]
],
"WD - Minefield - OCD BP on Right Side of Shack": [
[GC],
[SS, RS]
],
"WD - Minefield - OCD BP inside Guardian's House": [
[]
# "move_rando"
# [REL, GC],
# [REL, SS, RS]
# [MX]
],
"WL - South Shore - MM BP on Bridge to Scout's Hut": [
[]
],
"WL - South Shore - MM BP across from Tent near Porcupine": [
[]
],
"WL - South Shore - MM BP near Outhouse": [
[]
],
"WL - South Shore - MM BP near Hill Bridge": [
[]
],
"WL - South Shore - MM BP on Scout's Hut Roof": [
[RS],
[GC]
],
"WL - South Shore - MM BP on Grass Platform": [
[RS],
[GC]
],
"WL - South Shore - MM BP across Zipline Platform": [
[RS, OCD],
[GC]
],
"WL - South Shore - MM BP behind Summer Beast": [
[REL, OCD],
[GC]
],
"WL - North Shore - MM BP below Bridge": [
[]
],
"WL - North Shore - MM BP behind Skunk Hut": [
[]
],
"WL - North Shore - MM BP inside Skunk Hut": [
[]
# "move_rando"
# [MX]
],
"WL - North Shore - MM BP inside House's Fence": [
[]
# "move_rando"
# [MX]
],
"WL - North Shore - MM BP inside Boulder Box near Bridge": [
[]
],
"WL - North Shore - MM BP inside Boulder Box behind Skunk Hut": [
[]
],
"WL - North Shore - MM BP inside Drill House": [
[]
],
"WL - North Shore - MM BP on Crow Platform near Drill House": [
[]
],
"WV - City Hall - GC BP in Safe Room": [
[]
],
"WV - City Hall - GC BP in Statue Room": [
[]
],
"WV - Clock Tower - GC BP in Bedroom": [
[RS]
# "move_rando"
# [MX, RS]
],
"WV - Clock Tower - GC BP in Bell Room": [
[RS]
],
"WF - Ski Resort - GC BP inside Dog's Fence": [
[]
],
"WF - Ski Resort - GC BP in Max Cave": [
[]
# "move_rando"
# [MX]
],
"WF - Civic Center - GC BP on Left Side in Bat Cave Wall": [
[GC],
[OCD, RS]
],
"WF - Civic Center - GC BP in Frozen Ice": [
[REL, GC],
[REL, OCD, RS],
[SS, GC],
[SS, OCD, RS]
],
"WD - Power Plant - GC BP in Max Cave": [
[]
# "move_rando"
# [MX]
],
"WD - Power Plant - GC BP After First Gate": [
[REL, RS],
[GC]
# "move_rando"
# [MX, REL, RS]
],
"WD - Generator Building - GC BP on the Highest Platform": [
[REL, GC],
[REL, OCD, SS, RS]
],
"WD - Generator Building - GC BP at the Entrance after Mission Completion": [
[REL, GC],
[REL, OCD, SS, RS]
],
"WL - Submarine World - GC BP Just Below Water Surface": [
[MM]
],
"WL - Submarine World - GC BP Underwater": [
[MM]
],
"WL - Mayor's Villa - GC BP on Tree Branch": [
[GC],
[REL, RS]
],
"WL - Mayor's Villa - GC BP in Pirate's Cave": [
[GC],
[REL, RS]
],
"MC - Sleigh Ride - Stealing All Gifts": [
# ["Exhaust Pipes", "Tires", "Skis", "Twin-End Tuba"]
[REL, WV, WF, WD, WL, RS, MM]
],
"MC - Sleigh Ride - Neutralizing Santa": [
# ["Exhaust Pipes", "Tires", "Skis", "Twin-End Tuba"]
[REL, WV, WF, WD, WL, RS, MM]
],
"WV - Post Office - Heart of Stone": [
[]
],
"WF - Ski Resort - Heart of Stone": [
[]
],
"WD - Minefield - Heart of Stone": [
[GC],
[REL, SS, RS]
],
"WL - North Shore - Heart of Stone": [
[]
# "move_rando"
# [MX]
],
"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": [
[]
],
"WV - Exhaust Pipes": [
[WV, REL]
],
"WF - Skis": [
[WF]
],
"WD - Tires": [
[WD, RS, REL]
],
"WL - Submarine World - Twin-End Tuba": [
[WL, MM]
],
"WL - South Shore - GPS": [
[WL, REL]
],
"MC - 1st Crate Squashed": [
[]
# "move_rando"
# [PC]
],
"MC - 2nd Crate Squashed": [
[]
# "move_rando"
# [PC]
],
"MC - 3rd Crate Squashed": [
[]
# "move_rando"
# [PC]
],
"MC - 4th Crate Squashed": [
[]
# "move_rando"
# [PC]
],
"MC - 5th Crate Squashed": [
[]
# "move_rando"
# [PC]
]
# "Green Present": [
# []
# ],
# "Red Present": [
# []
# ],
# "Pink Present": [
# [REL],
# [move_rando]
# [PC]
# ],
# "Yellow Present": [
# []
# "move_rando"
# [PC]
# ]
}
access_rules_dict: dict[str,list[list[str]]] = {
"Whoville": [
[WV]
],
"Post Office": [
[WC]
],
"City Hall": [
[REL]
],
"Clock Tower": [
[]
# "move_rando"
# [SN]
],
"Who Forest": [
[WF],
# [VT: 1]
],
"Ski Resort": [
[CCAC]
],
"Civic Center": [
[GC],
[OCD]
],
"Who Dump": [
[WD],
# [VT: 2]
],
"Minefield": [
[REL, RS],
[REL, GC]
# "move_rando"
# [REL, RS, PC],
# [REL, GC, PC]
],
"Power Plant": [
[REL, GC],
[SS, GC],
[REL, OCD, SS, RS]
# "move_rando"
# [REL, GC, PC],
# [SS, GC, PC],
# [REL, OCD, SS, RS, PC]
],
"Generator Building": [
[REL, GC],
[REL, OCD, SS, RS]
],
"Who Lake": [
[WL],
# [VT: 3]
],
"Scout's Hut": [
[GC],
[RS]
],
"North Shore": [
[SCL]
],
"Mayor's Villa": [
[SCL]
],
"Submarine World": [
[MM]
],
"Sleigh Room": [
[SR]
],
"Spin N' Win": [
[]
# ["Spin N' Win Door Unlock"],
# ["Progressive Supadow Door Unlock"]
],
"Dankamania": [
[]
# ["Dankamania Door Unlock"],
# ["Progressive Supadow Door Unlock: 2"]
],
"The Copter Race Contest": [
[]
# ["The Copter Race Contest Door Unlock"],
# ["Progressive Supadow Door Unlock: 3"]
],
"Bike Race": [
[]
# ["Bike Race Access"],
# ["Progressive Supadow Door Unlock: 4"]
]
}

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

@@ -0,0 +1,85 @@
from BaseClasses import Region, Item, ItemClassification
from .Locations import grinch_locations_to_id, grinch_locations, GrinchLocation, get_location_names_per_category
from .Items import grinch_items_to_id, GrinchItem, ALL_ITEMS_TABLE, MISC_ITEMS_TABLE, get_item_names_per_category
from .Regions import connect_regions
from .Rules import set_location_rules
from .Client import *
from typing import ClassVar
from worlds.AutoWorld import World
from Options import OptionError
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)
item_name_groups = get_item_names_per_category()
location_name_groups = get_location_names_per_category()
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 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 == "MC - Sleigh Ride - 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,7 @@
- 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 along with starting decompilation of the game
into motion
- 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.
- Credit to Artamiss for the vast majority of BZZ decompilation and general backend coding.

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,52 @@
# The Grinch (PS1) - Setup Guide
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Please use version 0.6.3 or later for integrated
BizHawk support.
- Legally obtained NTSC Bin ROM file, probably named something like `Grinch, The (USA) (En,Fr,Es).bin`.
The game's CUE file should also work aswell along side the BIN file if you have troubles opening the BIN file.
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) Versions 2.9.1 & 2.10 is supported, but I can't promise if any version is stable or not. As of September 2025, the Grinch may not be compatible with 2.11 yet.
- 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.
- PSX BIOS Firmware bin file, which is required to run the game through Bizhawk. The file you need should be
named something like `SCPH-5501.BIN`.
## 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.
- You are required to legally obtain a PSX Bios BIN firmware file for the game to be opened. To import this, you go to
`Config > Firmware... > Tools` and scrolling until you see the PlayStation tab. You might right click on the bios region
and click `Set Customization` or `Import` on the top of the window. Then a window should open, telling you what file to
import, which is the BIN file required. The bios should be recognized if Bizhawk displays a checkmark beside it, saying
the bios version you have is stable and should run without issues. If a bios is already been imported and the game
runs fine by itself, you may skip this step.
## 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]`)