Compare commits

..

110 Commits

Author SHA1 Message Date
NewSoupVi
8d04239504 use url_for with _anchor (#3279) 2024-05-08 08:34:17 +02:00
NewSoupVi
59a000033e Merge branch 'main' into NewSoupVi-patch-1 2024-05-07 12:47:50 +02:00
Bryce Wilson
76962b8b3b Pokemon Emerald: Fix incorrect access to slateport water encounters (#3243) 2024-05-07 12:43:35 +02:00
NewSoupVi
e04db57dce Core: Add has_list and count_list and improve (?) count_group (#2934)
* Add has_list and count_list and improve (?) count_group

* MESSENGER STOP

* Add docstrings to has_list and count_list

* Add docstrings for has_group and count_group as well

* oops

* Rename to has_from

* docstrings improvement again

* Docstring
2024-05-07 09:23:25 +02:00
digiholic
12b8fef1aa Adds a canary byte check before sending game completion (#3217) 2024-05-07 09:22:11 +02:00
NewSoupVi
0ac8844f6f Core: Add "has_all_counts" and "has_any_count" functions to CollectionState (#2933)
* Add has_all_counts and has_any_counts

* Messenger gave me a red x and I'm mad about it

* Update BaseClasses.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update BaseClasses.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Mapping instead of Dict

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-07 08:15:09 +02:00
black-sliver
23eca7d747 SoE: Docs: rework some styling (#3268)
* Docs: SoE: move header and fix header level

* Docs: SoE: be pedantic for required software
2024-05-06 10:55:25 +02:00
Alchav
1a563a14fc LTTP: Yet more LTTP logic fixes (#3270) 2024-05-06 09:36:08 +02:00
jamesbrq
5935093615 Mario & Luigi: Superstar Saga: Implement New Game (#2754)
* Commit for PR

* Commit for PR

* Update worlds/mlss/Client.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/docs/setup_en.md

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Remove deprecated import. Updated settings and romfile syntax

* Updated Options to new system. Changed all references from MultiWorld to World

* Changed switch statements to if else

* Update en_Mario & Luigi Superstar Saga.md

* Updated client.py

* Update Client.py

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Updated logic, Updated patch implementation, Removed unused imports, Cleaned up Code

* Update __init__.py

* Changed reference from world to mlssworld

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Fix merge conflict + update prep

* v1.2

* Leftover print commands

* Update basepatch.bsdiff

* Update basepatch.bsdiff

* v1.3

* Update Rom.py

* Change tracker locations to serverside, no longer locations. Various code cleanup and logic changes.

* Event removal continuation.

* Partial Implementation of APPP (Incomplete))

* v1.4 Implemented APPP

* Docs Updated

* Update Rom.py

* Update setup_en.md

* Update Rom.py

* Update Rules.py

* Fix for APPP being broken on webhost

* Update Rom.py

* Update Rom.py

* Location name fixes + pants color fixes

* Update Rules.py

* Fix for ultra hammer cutscene

* Fixed compat. issues with python ver. 3.8

* Updated hidden block yaml option

* pre-v1.5

* Update Client.py

* Update basepatch.bsdiff

* v1.5

* Update XP multiplier to have a minimum of 0

* Update 'Beanfruit' to 'Bean Fruit'

* v1.6

* Update Rom.py

* Update basepatch.bsdiff

* Initial review refactor

* Revert state logic changes. Continuation of refactor.

* Fixed failed generations. Finished refactor.

* Reworked colors. Removed all .txt files

* Actually removed the .txt files this time

* Update Rom.py

* Update README.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Options.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Client.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Data.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Review refactor.

* Update README.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Rules.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Add coin blocks to LocationName

* Refactor.

* Update Items.py

* Delete mlss.apworld

* Small asm bugfix

* Update basepatch.bsdiff

* Client sends less messages to server

* Update basepatch.bsdiff

---------

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-06 09:15:06 +02:00
Fabian Dill
2aa3ef372d WebHost: use redirect for /room form submission (#3271) 2024-05-05 22:59:51 +02:00
Bryce Wilson
d94cf8dcb2 Pokemon Emerald: Add event ticket locations to client data store flags (#3177)
* Pokemon Emerald: Add event ticket locations to client data store flags

* Pokemon Emerald: Add regi doors event flag

* Pokemon Emerald: Add more tracker flags
2024-05-05 10:46:11 +02:00
PoryGone
5fae1c087e Celeste 64: v1.2 Content Update (#3210)
* Cleanup and new option support

* Handle new locations

* Support higher Strawberry counts

* Don't add start inventory items to the pool

* Support Move Shuffle functionality and items

* Hard and Move Shuffle Logic

* Fix Options

* Update CHANGELOG.md

* Add standard moves logic for signs 3 and 4

* Fix Option Tooltip

* Add tracker link to setup guide

* Fix unit test

* Fix option tooltips

* Missing Space

* Move option checking out of rule function

* Delete just_gen500.bat
2024-05-05 08:58:49 +02:00
NewSoupVi
baca95d49d WebHost: Fix setup guide link not working for games with special characters 2024-05-05 08:20:45 +02:00
Bryce Wilson
7e61211365 Pokemon Emerald: Convert to procedure patch (#2995)
* Pokemon Emerald: Convert to procedure patch

* Pokemon Emerald: Remove assertion for vanilla rom's existence

* Pokemon Emerald: Add APPP implication to changelog

* Pokemon Emerald: Move procedure patch changelog line to new version

* Pokemon Emerald: Modify changelog versions

* Pokemon Emerald: Fix patch file download not appearing

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-05 07:08:24 +02:00
Bryce Wilson
7603b4a88f Pokemon Emerald: Change dexsanity to not create locations for blacklisted wilds (#3056) 2024-05-04 21:44:38 +02:00
Aaron Wagener
005fc4e864 Fill: allow for single player fill restrictive placement and sweeping (#2415)
* Core: allow for single player state sweeping

* Fill: have distribute items use single_player fill when it can

* oop

* pass locations to sweep_for_events instead of the player

* finally found the diff that was breaking swap

* LTTP fills everyone's dungeons at once, not just a single player's

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-04 12:42:36 +02:00
Star Rauchenberger
28262a31b8 Lingo: Started using OptionError (#3251) 2024-05-04 08:40:17 +02:00
Bryce Wilson
660b068f5a Pokemon Emerald: Use OptionError (#3264) 2024-05-04 08:38:24 +02:00
Thorsten Horberth
879c3407d8 Yoshi's World: Fixed minor logic inconsistincy in Rules.py (#3241)
* Fixed Logic in Rules.py

As of easy logic of this goal is
    set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state)))
normal logic shouldn't need any collectable.

* Corrected Logic Rules.py
2024-05-04 04:29:12 +02:00
Scipio Wright
d5683c4326 Core: Make output when hinting something with multiple copies show up in a better order (#3245)
* Make the hint info show up in a better order

* Change how old_hints is modified/done

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-04 04:28:09 +02:00
Fabian Dill
f27d1d635b SNIClient: restore old operands header (#3242) 2024-05-03 22:00:05 +02:00
Nicholas Saylor
298c9fc159 Fixed typo and odd capitalization (#3233) 2024-05-03 12:23:08 +02:00
Scipio Wright
26188230b7 TUNIC: Better seed groups for Entrance Rando (#2998)
* Update entrance rando description to discuss seed groups

* Starting off, setting up some names

* It lives

* Some preliminary plando connection handling, probably has errors

* Add missed comma

* if -> elif

* I think this is working properly to handle plando connections

* Update comments

* Fix up shop -> shop portal stuff

* Add back comma that got removed for no reason in the ladder PR

* Remove unnecessary if else

* add back the actually necessary if but not the else

* okay they were both necessary

* Update entrance rando description

* blasphemy

Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>

* Rename other instances of tunc -> tunic

* Update per Vi's review (thank you)

* Fix a not that shouldn't have been

* Rearrange, update per Vi's comments (thank you)

* Fix indent

* Add a .value

* Add .values

* Fix bad comparison

* Add a not that was supposed to be there

* Replace another isinstance

* Revise option description

* Fix per Kaito's comment

Co-authored-by: Kaito Sinclaire <ks@rosenthalcastle.org>

---------

Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>
Co-authored-by: Kaito Sinclaire <ks@rosenthalcastle.org>
2024-05-03 07:21:27 +02:00
t3hf1gm3nt
b68be7360c [TLOZ]: Remove use of per_slot_randoms (#3255)
We only used it in two spots for randomizing the secret rupee cave values. Uses proper world random now.
2024-05-03 02:56:20 +02:00
t3hf1gm3nt
255e52642e TLOZ: Fix rings classification, so they are actually considered for logic (#3253) 2024-05-02 16:49:39 -05:00
qwint
49862dca1f move godhome events to create_regions with the others to not try and make them non-events when unshuffled is on (#3221) 2024-05-02 15:26:17 +02:00
Star Rauchenberger
0d586a4467 Lingo: Fix broken good item in panelsanity (#3249) 2024-05-02 15:14:30 +02:00
Ziktofel
8c8b29ae92 SC2: For non-campaign order pick one of the hardest missions as goal (#3180)
This allows End Game as the goal even if long campaigns are present
2024-05-02 12:20:57 +02:00
Natalie Weizenbaum
9d478ba2bc Rules: Verify the default values of Options. (#2403)
* Verify the default values of `Option`s.

Since `Option.verify()` can handle normalization of option names, this allows options  to define defaults which rely on that normalization. For example, it allows a world to exclude certain locations by default.

This also makes it easier to catch errors if a world author accidentally sets an invalid default.

* Update Generate.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 12:19:15 +02:00
ken
3cc434cd78 Core: organize files on ingest via alpha, not ascii (#3029)
* organize files on ingest via alpha, not ascii

* Change from lower() to casefold()
2024-05-02 12:14:50 +02:00
Scipio Wright
31a5696526 Noita: Add more location groups, capitalize existing ones (#3141)
* Add location groups for each region

* Capitalize existing location groups

* Capitalize new boss location group names

* Update comment with capitalization

* Capitalize location_type in reigons.py
2024-05-02 12:02:14 +02:00
palex00
7bdf9a643c Updating Poptracker-Pack-Link for Pokémon Emerald as the old one was no longer maintained and did not work with 0.4.5 (#3193)
* Replaced the outdated Tracker Pack with a new one that is also pinned in the Discord channel

* Same change but for Spanish

* Update setup_en.md

* catching the bottom link as well

* See English Setupguide
2024-05-02 11:56:35 +02:00
Scipio Wright
c64c80aac0 TUNIC: Location groups for each area of the game (#3024)
* huzzah, location groups

* scope creep pog

* Apply suggestion to the other spot it is applicable at too

* apply berserker's suggestion

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Remove extra location group for shops

* Fire rod for magic wand

* Capitalize itme name groups

* Update docs to capitalize item name groups, remove the little section on aliases

since the aliases bit is really more for someone misremembering the name than anything else, like "fire rod" is because you played a lot of LttP, or Orb instead of Magic Orb is clear.

* Fix rule with item group name

* Capitalization is cool

* Fix merge mistake

* Add Flask group, remove Potions group

* Update docs to detail how to find item and location groups

* Revise per Vi's comment

* Fix test

* fuzzy matching please stop

* Remove test change that was meant for a different branch

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-05-02 10:02:59 +02:00
Aaron Wagener
07d9d6165e Tests: Clean up some of the fill test helpers a bit (#2935)
* Tests: Clean up some of the fill test helpers a bit

* fix some formatting

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 10:01:59 +02:00
Star Rauchenberger
fc571ba356 Lingo: Expand sphere 1 under restrictive conditions (#3190) 2024-05-02 09:52:16 +02:00
Emily
ea6235e0d9 Core: Improve join/leave messages, add "HintGame" tag (#2859)
* Add better "verbs" on joining msg, and improve leaving msgs

* Add 'HintGame' tag, for projects like BKSudoku/APSudoku/HintMachine

* data in one place instead of 3

* Clean up 'ignore_game' loop to use any() instead

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
2024-05-02 09:38:49 +02:00
Aaron Wagener
6f8b8fc9c9 Options: Add an OptionError to specify bad options caused the failure (#2343)
* Options: Add an OptionError to specify bad options caused the failure

* inherit from ValueError instead of RuntimeError since this error should be thrown via bad input

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 09:22:50 +02:00
black-sliver
0ed0de3daa Setup: update cx_freeze to 7.x (#3195) 2024-05-01 01:48:32 +02:00
Star Rauchenberger
487a067d10 Lingo: Fix world load on frozen 3.8 (#3220)
* Lingo: Fix world load on frozen 3.8

* Fixed absolute imports in unit test

* Made unpickling safer
2024-04-29 20:38:29 +02:00
Doug Hoskisson
fc4e6adff5 Core: some CommonContext typing (#3227) 2024-04-29 07:26:30 +02:00
t3hf1gm3nt
9cdc90513b [TLOZ]: Dark Rooms and Level 8 Logic Fixes (#3222)
Properly name the Book to Book of Magic in Rules.py so you can actually possibly be expected to use Magical Rod plus Book of Magic to get through dark rooms. No wonder we tend to see candles so early oops.

Also adding a rule that you need candles for access to Level 8 so you aren't required to time a Rod+Book shot against a moblin to burn the bush. Might make this a logic trick or something later.
2024-04-28 01:49:59 +02:00
Alchav
9afe45166c ALTTP: 0.4.6 fixes (#3215)
* Fix randomizer room logic

* Fix Triforce Hunt HUD always present

* Fix Circle of Pots enemy byte

* treasure_hunt_total for Murahdala text
2024-04-28 01:48:59 +02:00
LiquidCat64
9e20fa48e1 CV64: fix import that shouldn't be relative (#3223) 2024-04-28 01:41:30 +02:00
Zach Parks
e76ba928a8 WebHost: Prevent committing data packages with invalid checksums to database and prevent 500 error from invalid zip files. (#3206) 2024-04-26 22:18:12 -04:00
Aaron Wagener
4f1e696243 The Messenger: fix import that shouldn't be relative (#3219) 2024-04-26 21:29:01 +02:00
Fabian Dill
4756c76541 WebHost: remove JSON_AS_ASCII (#3209) 2024-04-24 06:36:35 +02:00
Fabian Dill
2f78860d8c Core/SNIClient/LttP/Factorio: switch to get_settings (#3208) 2024-04-24 06:24:44 +02:00
Scipio Wright
cca9778871 Delete worlds/sc2wol directory (#3202) 2024-04-23 12:58:38 -05:00
Fabian Dill
bb16fe284a Core: make open_filename log that it's asking (#3199) 2024-04-23 19:05:03 +02:00
Fabian Dill
daccb30e3d Factorio: fix client compatibility with Windows 7/Python 3.8 (#3196) 2024-04-22 14:32:31 +02:00
black-sliver
747b48183c Setup: update cx_freeze to latest 6.x and exclude 7.x (#3194)
7.x has a breaking API change for us, so that needs to be resolved separately.
2024-04-22 13:54:35 +02:00
NewSoupVi
e22ac85e15 The Witness: More door renames (#3131) 2024-04-21 11:45:39 -05:00
Kaito Sinclaire
ee69fa6a8c SMZ3: Use correct font tiles for cross-world items in SM (#3095) 2024-04-21 11:44:17 -05:00
Scipio Wright
ec18254e9e TUNIC: Minor edits to game page (#3182)
* Minor edits to game page

* in -> of
2024-04-21 11:39:37 -05:00
Zach Parks
49c0268a84 MultiServer: Fix /alias <name> not removing aliases. (#3186) 2024-04-21 11:37:24 -05:00
Scipio Wright
6aafa6ff04 TUNIC: Fix minimal Heir access in ladder shuffle (#3189) 2024-04-21 17:00:16 +02:00
Silvris
3e27b93c37 SNIClient: set SNESState to SNES_DISCONNECTED when disconnected (#3188) 2024-04-21 16:59:19 +02:00
Fabian Dill
392c47dcef MultiServer: add datastore list command to MultiServer (#3181) 2024-04-21 03:47:01 +02:00
Scipio Wright
442c7d04db Core: Silently fix invalid yaml option for Toggles (#3179) 2024-04-21 03:37:28 +02:00
Chris Wilson
ad4451276d WebHost: Add robots.txt to WebHost (#3157)
* Add a `robots.txt` file to prevent crawlers from scraping the site

* Added `ASSET_RIGHTS` entry to config.yaml to control whether `/robots.txt` is served or not

* Always import robots.py, determine config in route function

* Finish writing a comment

* Remove unnecessary redundant import and config
2024-04-20 20:58:56 -04:00
Silvris
915ad61ecf Core: fix unfilled items "Per-Player Count" (#2661) 2024-04-20 19:57:55 -05:00
Bryce Wilson
5d8aca1b4e Pokemon Emerald: Temporary fix for missing items (#3162) 2024-04-20 19:56:08 -05:00
Aaron Wagener
d4c8083be5 Docs: Mention /check in the advanced yaml guide (#3092)
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-04-20 19:49:48 -05:00
SunCat
a3702abe38 CODEOWNERS: Update owner for ChecksFinder (#3089) 2024-04-20 19:49:00 -05:00
Danaël V
5fcc1aa83f Launcher: Adding Unrated mention to the launcher (#3097) 2024-04-20 19:48:14 -05:00
Flore
b8e0d4c4ee DS3: Update the setup docs to be more up to date (#2932)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Co-authored-by: Moonlington <Moonlington@users.noreply.github.com>
2024-04-20 19:45:20 -05:00
Doug Hoskisson
fc18f9caf9 Zillion: Docs: add information to skill option documentation (#3118) 2024-04-20 19:43:13 -05:00
Doug Hoskisson
a50e68acd1 Docs: style: multiline brackets (#3143) 2024-04-20 19:42:27 -05:00
agilbert1412
0624ba5e81 Stardew Valley: Options page documentation improvements (#3155) 2024-04-20 19:41:00 -05:00
Zach Parks
a45fa84382 Factorio: Fix 500 error on Factorio multi-tracker. (#3184)
* Factorio: Fix 500 error on Factorio multi-tracker.

* Hopefully this also fixes the webhost test failures.
2024-04-20 19:39:33 -04:00
Zach Parks
532cff1334 ALTTP: Updates and refactors to multi-tracker and player tracker. (#3183)
* ALTTP: Massive game tracker update.

* Adds dropdowns separated by region for each location and its checked status.
* Adds Bombs for bombless start seeds.
* Adds Triforce Pieces to track.
* Update icon image URLs to match in-game closer.
* Fix issue with grouped progressive items.

* Couple missed points.

* Another edge case with details being refreshed.

* Remove old commented out CSS

* Consolidate a table and move an erroneous location in wrong region.

* ALTTP: Updates and refactors to multi-tracker and player tracker.

* Removed some missed commented out code.

* Add triforce to prepare inventory logic.
2024-04-20 18:29:41 -04:00
Zach Parks
0a1ce5b7d8 ALTTP: Updates to player-specific game tracker. (#3133) 2024-04-20 13:18:06 -05:00
David St-Louis
a4acdb6ddf DOOM II: var world->multiworld fix and minor doc fix (#3140) 2024-04-19 23:11:12 +02:00
Fabian Dill
7a004de9a0 LttP: remove glitch triforce setting (#3174) 2024-04-19 23:10:29 +02:00
Silvris
8021ec744f LttP: fix percentage Triforce Pieces and missed cleanup from #3160 (#3178) 2024-04-19 23:10:10 +02:00
Silvris
a06bca95ad CV64: Include player in APPP constructor (#3175) 2024-04-19 01:24:00 +02:00
Alchav
b372b9da20 LTTP: ToH Crystal Switch Logic (#3172) 2024-04-18 21:33:41 +02:00
black-sliver
6087ec539b Settings: disable automatic yaml line breaks (#3096)
* Settings: disable automatic yaml line breaks

* Tests: add settings formatting checks

* Tests: fix typing in test_host_yaml
2024-04-18 19:13:43 +02:00
digiholic
f89cee4b15 MMBN3: Modernizations and Minor Bugfixes (#2991) 2024-04-18 19:02:01 +02:00
JusticePS
727915040d Adventure: Remove runtime changes to location templates (#3010) 2024-04-18 19:01:12 +02:00
PoryGone
c4c4069022 SA2B: Update Setup guide for new Mod Manager (#3085) 2024-04-18 19:00:01 +02:00
Aaron Wagener
5711d2c309 The Messenger: Hotfix item links filler gen (#3078) 2024-04-18 18:59:30 +02:00
Aaron Wagener
580c9c3943 Core: fix item_name_groups unfolding in item links (#3088) 2024-04-18 18:58:18 +02:00
NewSoupVi
fbfe82f57f The Witness: Make item links work properly with the hint system (#3110) 2024-04-18 18:57:22 +02:00
el-u
233eba6681 lufia2ac: prevent double checks (#3154) 2024-04-18 18:56:32 +02:00
David St-Louis
ffff63e6f3 DOOM 1993: Update to use new Options + logic fixes + Doc fix (#3138) 2024-04-18 18:56:10 +02:00
JaredWeakStrike
2704015eef KH2: Docs updates and Excluded Locations Bugfix (#3150) 2024-04-18 18:55:27 +02:00
lordlou
3c70621f1b SM: getitem cheat fix (#3102) 2024-04-18 18:54:46 +02:00
Scipio Wright
5ec342abf4 Noita: Add Meat Realm (#3119) 2024-04-18 18:54:03 +02:00
David St-Louis
2c80a9b8f1 Heretic: Update to use new Options + logic fixes + Doc fix (#3139) 2024-04-18 18:53:09 +02:00
Silvris
6f7c2fa25f KDL3: Fix invalid animal placements and fill error (#3152) 2024-04-18 18:52:23 +02:00
Bryce Wilson
8b8df9fa33 Pokemon Emerald: Fix missing region for water encounters in Dewford (#3103) 2024-04-18 18:51:49 +02:00
Doug Hoskisson
3343d4e364 SM: clean post_fill function (#2863) 2024-04-18 18:50:17 +02:00
NewSoupVi
1faaa0d941 The Witness: Increase variety of the starting item (#3047) 2024-04-18 18:49:15 +02:00
Nicholas Brochu
fec533b65e Zork Grand Inquisitor: Fix Determinism Issues on Fixed Seeds (#3134) 2024-04-18 18:47:27 +02:00
Zach Parks
21184b59d2 MultiServer: Prevent invalid *_mode option values. (#3149) 2024-04-18 18:46:48 +02:00
qwint
444178171b Docs: adds new commonclient commands to webhost docs (#3151) 2024-04-18 18:46:00 +02:00
Star Rauchenberger
740b76ebd5 Lingo: The Pilgrim Update (#2884)
* An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room.
* The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible.
* Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client.
* An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle.
* An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one.
* Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber.
2024-04-18 18:45:33 +02:00
Magnemania
6b50c91ce2 SM64ex: Logic and Generation Fixes (#3135) 2024-04-18 18:43:19 +02:00
wildham
a91105c958 FFMQ: Update FFMQ setup_en.md (#3091) 2024-04-18 18:42:28 +02:00
Trevor L
c3060a8b66 Hylics 2: Update to new options API (#3147) 2024-04-18 18:40:53 +02:00
PinkSwitch
5996a8163d Yoshi's Island: Minor Fixes (#3142) 2024-04-18 18:40:00 +02:00
Bryce Wilson
ee1e578201 Pokemon Emerald: Fix client crash if 0.4.6 client connects to 0.4.5 seed (#3146) 2024-04-18 18:39:28 +02:00
Bryce Wilson
2d3f3fcc2d Pokemon Emerald: Fix terra/marine caves bugged internal id (#3161) 2024-04-18 18:38:41 +02:00
LiquidCat64
437843bf53 CV64: Use AP Procedure Patch (#3159) 2024-04-18 18:37:51 +02:00
Scipio Wright
7fd7d5e492 Noita: Add the new bosses to the check pool (#3170) 2024-04-18 18:34:57 +02:00
el-u
52c1b04fc8 lufia2ac: prevent 0 byte reads (#3168) 2024-04-18 18:34:26 +02:00
Fabian Dill
6e56f31398 LttP/Core: more ripping and tearing (#3160) 2024-04-18 18:33:16 +02:00
Alchav
f19a84222e ALTTP: Triforce Pieces and Condense Items fixes (#3166) 2024-04-17 19:29:32 +02:00
Alchav
801d1223ac LTTP: Thieves Town KDS key fix (#3145) 2024-04-17 19:22:03 +02:00
203 changed files with 16530 additions and 5206 deletions

View File

@@ -7,14 +7,12 @@ import logging
import random
import secrets
import typing # this can go away when Python 3.8 support is dropped
import threading
import time
from argparse import Namespace
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \
Type, ClassVar
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, NamedTuple, Optional, Set, Tuple, \
TypedDict, Union, Type, ClassVar
import NetUtils
import Options
@@ -53,10 +51,6 @@ class ThreadBarrierProxy:
class MultiWorld():
debug_types = False
player_name: Dict[int, str]
difficulty_requirements: dict
required_medallions: dict
dark_room_logic: Dict[int, str]
restrict_dungeon_item_on_boss: Dict[int, bool]
plando_texts: List[Dict[str, str]]
plando_items: List[List[Dict[str, Any]]]
plando_connections: List
@@ -97,42 +91,6 @@ class MultiWorld():
def __getitem__(self, player) -> bool:
return self.rule(player)
class Observer(threading.Thread):
current_function: str
entered: float
shutdown: bool = False
def __init__(self):
self.current_function = ""
self.entered = 0.0
super().__init__(name="Observer", daemon=True)
def __call__(self, function: typing.Callable, entered: float):
# use str of function to avoid having a reference to a bound method
self.current_function = str(function)
self.entered = entered
return self
def __enter__(self):
assert self.current_function, "Entered Observer Context without current method."
def __exit__(self, exc_type, exc_val, exc_tb):
self.current_function = ""
def run(self):
while not self.shutdown:
time.sleep(1)
if self.current_function:
now = time.perf_counter()
elapsed = now - self.entered
if elapsed > 60:
logging.info(f"Generation stalling in {self.current_function}, "
f"running since {elapsed:.0f} seconds ago.")
self.current_function = ""
observer = Observer()
observer.start()
class RegionManager:
region_cache: Dict[int, Dict[str, Region]]
entrance_cache: Dict[int, Dict[str, Entrance]]
@@ -175,7 +133,6 @@ class MultiWorld():
self.random = ThreadBarrierProxy(random.Random())
self.players = players
self.player_types = {player: NetUtils.SlotType.player for player in self.player_ids}
self.glitch_triforce = False
self.algorithm = 'balanced'
self.groups = {}
self.regions = self.RegionManager(players)
@@ -202,49 +159,10 @@ class MultiWorld():
for player in range(1, players + 1):
def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val
set_player_attr('shuffle', "vanilla")
set_player_attr('logic', "noglitches")
set_player_attr('mode', 'open')
set_player_attr('difficulty', 'normal')
set_player_attr('item_functionality', 'normal')
set_player_attr('timer', False)
set_player_attr('goal', 'ganon')
set_player_attr('required_medallions', ['Ether', 'Quake'])
set_player_attr('swamp_patch_required', False)
set_player_attr('powder_patch_required', False)
set_player_attr('ganon_at_pyramid', True)
set_player_attr('ganonstower_vanilla', True)
set_player_attr('can_access_trock_eyebridge', None)
set_player_attr('can_access_trock_front', None)
set_player_attr('can_access_trock_big_chest', None)
set_player_attr('can_access_trock_middle', None)
set_player_attr('fix_fake_world', True)
set_player_attr('difficulty_requirements', None)
set_player_attr('boss_shuffle', 'none')
set_player_attr('enemy_health', 'default')
set_player_attr('enemy_damage', 'default')
set_player_attr('beemizer_total_chance', 0)
set_player_attr('beemizer_trap_chance', 0)
set_player_attr('escape_assist', [])
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
set_player_attr('treasure_hunt_count', 0)
set_player_attr('clock_mode', False)
set_player_attr('countdown_start_time', 10)
set_player_attr('red_clock_time', -2)
set_player_attr('blue_clock_time', 2)
set_player_attr('green_clock_time', 4)
set_player_attr('can_take_damage', True)
set_player_attr('triforce_pieces_available', 30)
set_player_attr('triforce_pieces_required', 20)
set_player_attr('shop_shuffle', 'off')
set_player_attr('shuffle_prizes', "g")
set_player_attr('sprite_pool', [])
set_player_attr('dark_room_logic', "lamp")
set_player_attr('plando_items', [])
set_player_attr('plando_texts', {})
set_player_attr('plando_connections', [])
set_player_attr('game', "A Link to the Past")
set_player_attr('game', "Archipelago")
set_player_attr('completion_condition', lambda state: True)
self.worlds = {}
self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the "
@@ -789,6 +707,14 @@ class CollectionState():
"""Returns True if at least one item name of items is in state at least once."""
return any(self.prog_items[player][item] for item in items)
def has_all_counts(self, item_counts: Mapping[str, int], player: int) -> bool:
"""Returns True if each item name is in the state at least as many times as specified."""
return all(self.prog_items[player][item] >= count for item, count in item_counts.items())
def has_any_count(self, item_counts: Mapping[str, int], player: int) -> bool:
"""Returns True if at least one item name is in the state at least as many times as specified."""
return any(self.prog_items[player][item] >= count for item, count in item_counts.items())
def count(self, item: str, player: int) -> int:
return self.prog_items[player][item]
@@ -796,8 +722,23 @@ class CollectionState():
Utils.deprecate("Use count instead.")
return self.count(item, player)
def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list."""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in items:
found += player_prog_items[item_name]
if found >= count:
return True
return False
def count_from_list(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state."""
return sum(self.prog_items[player][item_name] for item_name in items)
# item name group related
def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
"""Returns True if the state contains at least `count` items present in a specified item group."""
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
@@ -807,11 +748,12 @@ class CollectionState():
return False
def count_group(self, item_name_group: str, player: int) -> int:
found: int = 0
"""Returns the cumulative count of items from an item group present in state."""
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
found += player_prog_items[item_name]
return found
return sum(
player_prog_items[item_name]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]
)
# Item related
def collect(self, item: Item, event: bool = False, location: Optional[Location] = None) -> bool:

View File

@@ -207,6 +207,8 @@ class CommonContext:
finished_game: bool
ready: bool
team: typing.Optional[int]
slot: typing.Optional[int]
auth: typing.Optional[str]
seed_name: typing.Optional[str]

25
Fill.py
View File

@@ -19,11 +19,12 @@ def _log_fill_progress(name: str, placed: int, total_items: int) -> None:
logging.info(f"Current fill step ({name}) at {placed}/{total_items} items placed.")
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple()) -> CollectionState:
def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] = tuple(),
locations: typing.Optional[typing.List[Location]] = None) -> CollectionState:
new_state = base_state.copy()
for item in itempool:
new_state.collect(item, True)
new_state.sweep_for_events()
new_state.sweep_for_events(locations=locations)
return new_state
@@ -66,7 +67,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
item_pool.pop(p)
break
maximum_exploration_state = sweep_from_pool(
base_state, item_pool + unplaced_items)
base_state, item_pool + unplaced_items, multiworld.get_filled_locations(item.player)
if single_player_placement else None)
has_beaten_game = multiworld.has_beaten_game(maximum_exploration_state)
@@ -112,7 +114,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
location.item = None
placed_item.location = None
swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool)
swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool,
multiworld.get_filled_locations(item.player)
if single_player_placement else None)
# unsafe means swap_state assumes we can somehow collect placed_item before item_to_place
# by continuing to swap, which is not guaranteed. This is unsafe because there is no mechanic
# to clean that up later, so there is a chance generation fails.
@@ -170,7 +174,9 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
if cleanup_required:
# validate all placements and remove invalid ones
state = sweep_from_pool(base_state, [])
state = sweep_from_pool(
base_state, [], multiworld.get_filled_locations(item.player)
if single_player_placement else None)
for placement in placements:
if multiworld.worlds[placement.item.player].options.accessibility != "minimal" and not placement.can_reach(state):
placement.item.location = None
@@ -456,14 +462,16 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
if prioritylocations:
# "priority fill"
fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool,
single_player_placement=multiworld.players == 1, swap=False, on_place=mark_for_locking,
name="Priority")
accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool)
defaultlocations = prioritylocations + defaultlocations
if progitempool:
# "advancement/progression fill"
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression")
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, single_player_placement=multiworld.players == 1,
name="Progression")
if progitempool:
raise FillError(
f"Not enough locations for progression items. "
@@ -495,10 +503,9 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
if unplaced or unfilled:
logging.warning(
f"Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}")
items_counter = Counter(location.item.player for location in multiworld.get_locations() if location.item)
items_counter = Counter(location.item.player for location in multiworld.get_filled_locations())
locations_counter = Counter(location.player for location in multiworld.get_locations())
items_counter.update(item.player for item in unplaced)
locations_counter.update(location.player for location in unfilled)
print_data = {"items": items_counter, "locations": locations_counter}
logging.info(f"Per-Player counts: {print_data})")

View File

@@ -120,7 +120,7 @@ def main(args=None, callback=ERmain):
raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e
# sort dict for consistent results across platforms:
weights_cache = {key: value for key, value in sorted(weights_cache.items())}
weights_cache = {key: value for key, value in sorted(weights_cache.items(), key=lambda k: k[0].casefold())}
for filename, yaml_data in weights_cache.items():
if filename not in {args.meta_file_path, args.weights_file_path}:
for yaml in yaml_data:
@@ -147,7 +147,6 @@ def main(args=None, callback=ERmain):
erargs = parse_arguments(['--multi', str(args.multi)])
erargs.seed = seed
erargs.plando_options = args.plando
erargs.glitch_triforce = options.generator.glitch_triforce_room
erargs.spoiler = args.spoiler
erargs.race = args.race
erargs.outputname = seed_name
@@ -354,7 +353,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
return category_dict[option_key]
raise Exception(f"Error generating meta option {option_key} for {game}.")
raise Options.OptionError(f"Error generating meta option {option_key} for {game}.")
def roll_linked_options(weights: dict) -> dict:
@@ -410,19 +409,19 @@ def roll_triggers(weights: dict, triggers: list) -> dict:
def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option), plando_options: PlandoOptions):
if option_key in game_weights:
try:
try:
if option_key in game_weights:
if not option.supports_weighting:
player_option = option.from_any(game_weights[option_key])
else:
player_option = option.from_any(get_choice(option_key, game_weights))
setattr(ret, option_key, player_option)
except Exception as e:
raise Exception(f"Error generating option {option_key} in {ret.game}") from e
else:
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
player_option = option.from_any(option.default) # call the from_any here to support default "random"
setattr(ret, option_key, player_option)
except Exception as e:
raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e
else:
setattr(ret, option_key, option.from_any(option.default)) # call the from_any here to support default "random"
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses):

View File

@@ -102,7 +102,7 @@ components.extend([
Component("Open Patch", func=open_patch),
Component("Generate Template Options", func=generate_yamls),
Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2")),
Component("18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")),
Component("Unrated/18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")),
Component("Browse Files", func=browse_files),
])

27
Main.py
View File

@@ -36,38 +36,13 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
logger = logging.getLogger()
multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
multiworld.plando_options = args.plando_options
multiworld.shuffle = args.shuffle.copy()
multiworld.logic = args.logic.copy()
multiworld.mode = args.mode.copy()
multiworld.difficulty = args.difficulty.copy()
multiworld.item_functionality = args.item_functionality.copy()
multiworld.timer = args.timer.copy()
multiworld.goal = args.goal.copy()
multiworld.boss_shuffle = args.shufflebosses.copy()
multiworld.enemy_health = args.enemy_health.copy()
multiworld.enemy_damage = args.enemy_damage.copy()
multiworld.beemizer_total_chance = args.beemizer_total_chance.copy()
multiworld.beemizer_trap_chance = args.beemizer_trap_chance.copy()
multiworld.countdown_start_time = args.countdown_start_time.copy()
multiworld.red_clock_time = args.red_clock_time.copy()
multiworld.blue_clock_time = args.blue_clock_time.copy()
multiworld.green_clock_time = args.green_clock_time.copy()
multiworld.dungeon_counters = args.dungeon_counters.copy()
multiworld.triforce_pieces_available = args.triforce_pieces_available.copy()
multiworld.triforce_pieces_required = args.triforce_pieces_required.copy()
multiworld.shop_shuffle = args.shop_shuffle.copy()
multiworld.shuffle_prizes = args.shuffle_prizes.copy()
multiworld.sprite_pool = args.sprite_pool.copy()
multiworld.dark_room_logic = args.dark_room_logic.copy()
multiworld.plando_items = args.plando_items.copy()
multiworld.plando_texts = args.plando_texts.copy()
multiworld.plando_connections = args.plando_connections.copy()
multiworld.required_medallions = args.required_medallions.copy()
multiworld.game = args.game.copy()
multiworld.player_name = args.name.copy()
multiworld.sprite = args.sprite.copy()
multiworld.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
multiworld.sprite_pool = args.sprite_pool.copy()
multiworld.set_options(args)
multiworld.set_item_links()

View File

@@ -688,7 +688,7 @@ class Context:
clients = self.clients[team].get(slot)
if not clients:
continue
client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)]
client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player != slot)]
for client in clients:
async_start(self.send_msgs(client, client_hints))
@@ -803,14 +803,25 @@ async def on_client_disconnected(ctx: Context, client: Client):
await on_client_left(ctx, client)
_non_game_messages = {"HintGame": "hinting", "Tracker": "tracking", "TextOnly": "viewing"}
""" { tag: ui_message } """
async def on_client_joined(ctx: Context, client: Client):
if ctx.client_game_state[client.team, client.slot] == ClientStatus.CLIENT_UNKNOWN:
update_client_status(ctx, client, ClientStatus.CLIENT_CONNECTED)
version_str = '.'.join(str(x) for x in client.version)
verb = "tracking" if "Tracker" in client.tags else "playing"
for tag, verb in _non_game_messages.items():
if tag in client.tags:
final_verb = verb
break
else:
final_verb = "playing"
ctx.broadcast_text_all(
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) "
f"{verb} {ctx.games[client.slot]} has joined. "
f"{final_verb} {ctx.games[client.slot]} has joined. "
f"Client({version_str}), {client.tags}.",
{"type": "Join", "team": client.team, "slot": client.slot, "tags": client.tags})
ctx.notify_client(client, "Now that you are connected, "
@@ -825,8 +836,19 @@ async def on_client_left(ctx: Context, client: Client):
if len(ctx.clients[client.team][client.slot]) < 1:
update_client_status(ctx, client, ClientStatus.CLIENT_UNKNOWN)
ctx.client_connection_timers[client.team, client.slot] = datetime.datetime.now(datetime.timezone.utc)
version_str = '.'.join(str(x) for x in client.version)
for tag, verb in _non_game_messages.items():
if tag in client.tags:
final_verb = f"stopped {verb}"
break
else:
final_verb = "left"
ctx.broadcast_text_all(
"%s (Team #%d) has left the game" % (ctx.get_aliased_name(client.team, client.slot), client.team + 1),
f"{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has {final_verb} the game. "
f"Client({version_str}), {client.tags}.",
{"type": "Part", "team": client.team, "slot": client.slot})
@@ -1507,15 +1529,13 @@ class ClientMessageProcessor(CommonCommandProcessor):
if hints:
new_hints = set(hints) - self.ctx.hints[self.client.team, self.client.slot]
old_hints = set(hints) - new_hints
if old_hints:
self.ctx.notify_hints(self.client.team, list(old_hints))
if not new_hints:
self.output("Hint was previously used, no points deducted.")
old_hints = list(set(hints) - new_hints)
if old_hints and not new_hints:
self.ctx.notify_hints(self.client.team, old_hints)
self.output("Hint was previously used, no points deducted.")
if new_hints:
found_hints = [hint for hint in new_hints if hint.found]
not_found_hints = [hint for hint in new_hints if not hint.found]
if not not_found_hints: # everything's been found, no need to pay
can_pay = 1000
elif cost:
@@ -1527,7 +1547,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
# By popular vote, make hints prefer non-local placements
not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player))
hints = found_hints
hints = found_hints + old_hints
while can_pay > 0:
if not not_found_hints:
break
@@ -1537,6 +1557,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
self.ctx.hints_used[self.client.team, self.client.slot] += 1
points_available = get_client_points(self.ctx, self.client)
self.ctx.notify_hints(self.client.team, hints)
if not_found_hints:
if hints and cost and int((points_available // cost) == 0):
self.output(
@@ -1550,7 +1571,6 @@ class ClientMessageProcessor(CommonCommandProcessor):
self.output(f"You can't afford the hint. "
f"You have {points_available} points and need at least "
f"{self.ctx.get_hint_cost(self.client.slot)}.")
self.ctx.notify_hints(self.client.team, hints)
self.ctx.save()
return True
@@ -1631,7 +1651,9 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
else:
team, slot = ctx.connect_names[args['name']]
game = ctx.games[slot]
ignore_game = ("TextOnly" in args["tags"] or "Tracker" in args["tags"]) and not args.get("game")
ignore_game = not args.get("game") and any(tag in _non_game_messages for tag in args["tags"])
if not ignore_game and args['game'] != game:
errors.add('InvalidGame')
minver = min_client_version if ignore_game else ctx.minimum_client_versions[slot]
@@ -1905,7 +1927,7 @@ class ServerCommandProcessor(CommonCommandProcessor):
@mark_raw
def _cmd_alias(self, player_name_then_alias_name):
"""Set a player's alias, by listing their base name and then their intended alias."""
player_name, alias_name = player_name_then_alias_name.split(" ", 1)
player_name, _, alias_name = player_name_then_alias_name.partition(" ")
player_name, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
if usable:
for (team, slot), name in self.ctx.player_names.items():
@@ -2134,32 +2156,47 @@ class ServerCommandProcessor(CommonCommandProcessor):
self.output(response)
return False
def _cmd_option(self, option_name: str, option: str):
"""Set options for the server."""
attrtype = self.ctx.simple_options.get(option_name, None)
if attrtype:
if attrtype == bool:
def attrtype(input_text: str):
return input_text.lower() not in {"off", "0", "false", "none", "null", "no"}
elif attrtype == str and option_name.endswith("password"):
def attrtype(input_text: str):
if input_text.lower() in {"null", "none", '""', "''"}:
return None
return input_text
setattr(self.ctx, option_name, attrtype(option))
self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}")
if option_name in {"release_mode", "remaining_mode", "collect_mode"}:
self.ctx.broadcast_all([{"cmd": "RoomUpdate", 'permissions': get_permissions(self.ctx)}])
elif option_name in {"hint_cost", "location_check_points"}:
self.ctx.broadcast_all([{"cmd": "RoomUpdate", option_name: getattr(self.ctx, option_name)}])
return True
else:
known = (f"{option}:{otype}" for option, otype in self.ctx.simple_options.items())
self.output(f"Unrecognized Option {option_name}, known: "
f"{', '.join(known)}")
def _cmd_option(self, option_name: str, option_value: str):
"""Set an option for the server."""
value_type = self.ctx.simple_options.get(option_name, None)
if not value_type:
known_options = (f"{option}: {option_type}" for option, option_type in self.ctx.simple_options.items())
self.output(f"Unrecognized option '{option_name}', known: {', '.join(known_options)}")
return False
if value_type == bool:
def value_type(input_text: str):
return input_text.lower() not in {"off", "0", "false", "none", "null", "no"}
elif value_type == str and option_name.endswith("password"):
def value_type(input_text: str):
return None if input_text.lower() in {"null", "none", '""', "''"} else input_text
elif value_type == str and option_name.endswith("mode"):
valid_values = {"goal", "enabled", "disabled"}
valid_values.update(("auto", "auto_enabled") if option_name != "remaining_mode" else [])
if option_value.lower() not in valid_values:
self.output(f"Unrecognized {option_name} value '{option_value}', known: {', '.join(valid_values)}")
return False
setattr(self.ctx, option_name, value_type(option_value))
self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}")
if option_name in {"release_mode", "remaining_mode", "collect_mode"}:
self.ctx.broadcast_all([{"cmd": "RoomUpdate", 'permissions': get_permissions(self.ctx)}])
elif option_name in {"hint_cost", "location_check_points"}:
self.ctx.broadcast_all([{"cmd": "RoomUpdate", option_name: getattr(self.ctx, option_name)}])
return True
def _cmd_datastore(self):
"""Debug Tool: list writable datastorage keys and approximate the size of their values with pickle."""
total: int = 0
texts = []
for key, value in self.ctx.stored_data.items():
size = len(pickle.dumps(value))
total += size
texts.append(f"Key: {key} | Size: {size}B")
texts.insert(0, f"Found {len(self.ctx.stored_data)} keys, "
f"approximately totaling {Utils.format_SI_prefix(total, power=1024)}B")
self.output("\n".join(texts))
async def console(ctx: Context):
import sys
@@ -2183,7 +2220,7 @@ async def console(ctx: Context):
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
defaults = Utils.get_options()["server_options"].as_dict()
defaults = Utils.get_settings()["server_options"].as_dict()
parser.add_argument('multidata', nargs="?", default=defaults["multidata"])
parser.add_argument('--host', default=defaults["host"])
parser.add_argument('--port', default=defaults["port"], type=int)

View File

@@ -21,6 +21,10 @@ if typing.TYPE_CHECKING:
import pathlib
class OptionError(ValueError):
pass
class Visibility(enum.IntFlag):
none = 0b0000
template = 0b0001
@@ -384,7 +388,8 @@ class Toggle(NumericOption):
default = 0
def __init__(self, value: int):
assert value == 0 or value == 1, "value of Toggle can only be 0 or 1"
# if user puts in an invalid value, make it valid
value = int(bool(value))
self.value = value
@classmethod
@@ -1124,6 +1129,7 @@ class ItemLinks(OptionList):
raise Exception(f"item_link {link['name']} has {intersection} "
f"items in both its local_items and non_local_items pool.")
link.setdefault("link_replacement", None)
link["item_pool"] = list(pool)
class Removed(FreeText):

View File

@@ -65,6 +65,7 @@ Currently, the following games are supported:
* Castlevania 64
* A Short Hike
* Yoshi's Island
* Mario & Luigi: Superstar Saga
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@@ -85,6 +85,7 @@ class SNIClientCommandProcessor(ClientCommandProcessor):
"""Close connection to a currently connected snes"""
self.ctx.snes_reconnect_address = None
self.ctx.cancel_snes_autoreconnect()
self.ctx.snes_state = SNESState.SNES_DISCONNECTED
if self.ctx.snes_socket and not self.ctx.snes_socket.closed:
async_start(self.ctx.snes_socket.close())
return True
@@ -281,7 +282,7 @@ class SNESState(enum.IntEnum):
def launch_sni() -> None:
sni_path = Utils.get_options()["sni_options"]["sni_path"]
sni_path = Utils.get_settings()["sni_options"]["sni_path"]
if not os.path.isdir(sni_path):
sni_path = Utils.local_path(sni_path)
@@ -564,7 +565,7 @@ async def snes_write(ctx: SNIContext, write_list: typing.List[typing.Tuple[int,
PutAddress_Request: SNESRequest = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'}
try:
for address, data in write_list:
PutAddress_Request['Operands'] = [hex(address)[2:], hex(min(len(data), 256))[2:]]
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
if ctx.snes_socket is not None:
await ctx.snes_socket.send(dumps(PutAddress_Request))
await ctx.snes_socket.send(data)
@@ -653,7 +654,7 @@ async def game_watcher(ctx: SNIContext) -> None:
async def run_game(romfile: str) -> None:
auto_start = typing.cast(typing.Union[bool, str],
Utils.get_options()["sni_options"].get("snes_rom_start", True))
Utils.get_settings()["sni_options"].get("snes_rom_start", True))
if auto_start is True:
import webbrowser
webbrowser.open(romfile)

View File

@@ -201,7 +201,7 @@ def cache_path(*path: str) -> str:
def output_path(*path: str) -> str:
if hasattr(output_path, 'cached_path'):
return os.path.join(output_path.cached_path, *path)
output_path.cached_path = user_path(get_options()["general_options"]["output_path"])
output_path.cached_path = user_path(get_settings()["general_options"]["output_path"])
path = os.path.join(output_path.cached_path, *path)
os.makedirs(os.path.dirname(path), exist_ok=True)
return path
@@ -619,6 +619,8 @@ def get_fuzzy_results(input_word: str, wordlist: typing.Sequence[str], limit: ty
def open_filename(title: str, filetypes: typing.Sequence[typing.Tuple[str, typing.Sequence[str]]], suggest: str = "") \
-> typing.Optional[str]:
logging.info(f"Opening file input dialog for {title}.")
def run(*args: str):
return subprocess.run(args, capture_output=True, text=True).stdout.split("\n", 1)[0] or None

View File

@@ -23,7 +23,6 @@ def get_app():
from WebHostLib import register, cache, app as raw_app
from WebHostLib.models import db
register()
app = raw_app
if os.path.exists(configpath) and not app.config["TESTING"]:
import yaml
@@ -34,6 +33,7 @@ def get_app():
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()
logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}")
register()
cache.init_app(app)
db.bind(**app.config["PONY"])
db.generate_mapping(create_tables=True)

View File

@@ -51,6 +51,7 @@ app.config["PONY"] = {
app.config["MAX_ROLL"] = 20
app.config["CACHE_TYPE"] = "SimpleCache"
app.config["HOST_ADDRESS"] = ""
app.config["ASSET_RIGHTS"] = False
cache = Cache()
Compress(app)
@@ -82,6 +83,6 @@ def register():
from WebHostLib.customserver import run_server_process
# to trigger app routing picking up on it
from . import tracker, upload, landing, check, generate, downloads, api, stats, misc
from . import tracker, upload, landing, check, generate, downloads, api, stats, misc, robots
app.register_blueprint(api.api_endpoints)

View File

@@ -150,6 +150,7 @@ def host_room(room: UUID):
if cmd:
Command(room=room, commandtext=cmd)
commit()
return redirect(url_for("host_room", room=room.id))
now = datetime.datetime.utcnow()
# indicate that the page should reload to get the assigned port

14
WebHostLib/robots.py Normal file
View File

@@ -0,0 +1,14 @@
from WebHostLib import app
from flask import abort
from . import cache
@cache.cached()
@app.route('/robots.txt')
def robots():
# If this host is not official, do not allow search engine crawling
if not app.config["ASSET_RIGHTS"]:
return app.send_static_file('robots.txt')
# Send 404 if the host has affirmed this to be the official WebHost
abort(404)

View File

@@ -1,20 +0,0 @@
window.addEventListener('load', () => {
const url = window.location;
setInterval(() => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
// Create a fake DOM using the returned HTML
const domParser = new DOMParser();
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
// Update item and location trackers
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
document.getElementById('location-table').innerHTML = fakeDOM.getElementById('location-table').innerHTML;
};
ajax.open('GET', url);
ajax.send();
}, 15000)
});

View File

@@ -0,0 +1,20 @@
User-agent: Googlebot
Disallow: /
User-agent: APIs-Google
Disallow: /
User-agent: AdsBot-Google-Mobile
Disallow: /
User-agent: AdsBot-Google-Mobile
Disallow: /
User-agent: Mediapartners-Google
Disallow: /
User-agent: Google-Safety
Disallow: /
User-agent: *
Disallow: /

View File

@@ -1,75 +0,0 @@
#player-tracker-wrapper{
margin: 0;
font-family: LexendDeca-Light, sans-serif;
color: white;
font-size: 14px;
}
#inventory-table{
border-top: 2px solid #000000;
border-left: 2px solid #000000;
border-right: 2px solid #000000;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
padding: 3px 3px 10px;
width: 284px;
background-color: #42b149;
}
#inventory-table td{
width: 40px;
height: 40px;
text-align: center;
vertical-align: middle;
}
#inventory-table img{
height: 100%;
max-width: 40px;
max-height: 40px;
filter: grayscale(100%) contrast(75%) brightness(75%);
}
#inventory-table img.acquired{
filter: none;
}
#inventory-table img.powder-fix{
width: 35px;
height: 35px;
}
#location-table{
width: 284px;
border-left: 2px solid #000000;
border-right: 2px solid #000000;
border-bottom: 2px solid #000000;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background-color: #42b149;
padding: 0 3px 3px;
}
#location-table th{
vertical-align: middle;
text-align: center;
padding-right: 10px;
}
#location-table td{
padding-top: 2px;
padding-bottom: 2px;
padding-right: 5px;
line-height: 20px;
}
#location-table td.counter{
padding-right: 8px;
text-align: right;
}
#location-table img{
height: 100%;
max-width: 30px;
max-height: 30px;
}

View File

@@ -0,0 +1,142 @@
@import url('https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@100..900&display=swap');
.tracker-container {
width: 440px;
box-sizing: border-box;
font-family: "Lexend Deca", Arial, Helvetica, sans-serif;
border: 2px solid black;
border-radius: 4px;
resize: both;
background-color: #42b149;
color: white;
}
.hidden {
visibility: hidden;
}
/** Inventory Grid ****************************************************************************************************/
.inventory-grid {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
padding: 1rem;
gap: 1rem;
}
.inventory-grid .item {
position: relative;
display: flex;
justify-content: center;
height: 48px;
}
.inventory-grid .dual-item {
display: flex;
justify-content: center;
}
.inventory-grid .missing {
/* Missing items will be in full grayscale to signify "uncollected". */
filter: grayscale(100%) contrast(75%) brightness(75%);
}
.inventory-grid .item img,
.inventory-grid .dual-item img {
display: flex;
align-items: center;
text-align: center;
font-size: 0.8rem;
text-shadow: 0 1px 2px black;
font-weight: bold;
image-rendering: crisp-edges;
background-size: contain;
background-repeat: no-repeat;
}
.inventory-grid .dual-item img {
height: 48px;
margin: 0 -4px;
}
.inventory-grid .dual-item img:first-child {
align-self: flex-end;
}
.inventory-grid .item .quantity {
position: absolute;
bottom: 0;
right: 0;
text-align: right;
font-weight: 600;
font-size: 1.75rem;
line-height: 1.75rem;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
user-select: none;
}
/** Regions List ******************************************************************************************************/
.regions-list {
padding: 1rem;
}
.regions-list summary {
list-style: none;
display: flex;
gap: 0.5rem;
cursor: pointer;
}
.regions-list summary::before {
content: "⯈";
width: 1em;
flex-shrink: 0;
}
.regions-list details {
font-weight: 300;
}
.regions-list details[open] > summary::before {
content: "⯆";
}
.regions-list .region {
width: 100%;
display: grid;
grid-template-columns: 20fr 8fr 2fr 2fr;
align-items: center;
gap: 4px;
text-align: center;
font-weight: 300;
box-sizing: border-box;
}
.regions-list .region :first-child {
text-align: left;
font-weight: 500;
}
.regions-list .region.region-header {
margin-left: 24px;
width: calc(100% - 24px);
padding: 2px;
}
.regions-list .location-rows {
border-top: 1px solid white;
display: grid;
grid-template-columns: auto 32px;
font-weight: 300;
padding: 2px 8px;
margin-top: 4px;
font-size: 0.8rem;
}
.regions-list .location-rows :nth-child(even) {
text-align: right;
}

View File

@@ -1,86 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ player_name }}&apos;s Tracker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/globalStyles.css") }}"/>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/lttp-tracker.css") }}"/>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/lttp-tracker.js") }}"></script>
</head>
<body>
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
<table id="inventory-table">
<tr>
<td><img src="{{ bow_url }}" class="{{ 'acquired' if bow_acquired }}" /></td>
<td><img src="{{ icons["Blue Boomerang"] }}" class="{{ 'acquired' if 'Blue Boomerang' in acquired_items }}" /></td>
<td><img src="{{ icons["Red Boomerang"] }}" class="{{ 'acquired' if 'Red Boomerang' in acquired_items }}" /></td>
<td><img src="{{ icons["Hookshot"] }}" class="{{ 'acquired' if 'Hookshot' in acquired_items }}" /></td>
<td><img src="{{ icons["Magic Powder"] }}" class="powder-fix {{ 'acquired' if 'Magic Powder' in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Fire Rod"] }}" class="{{ 'acquired' if "Fire Rod" in acquired_items }}" /></td>
<td><img src="{{ icons["Ice Rod"] }}" class="{{ 'acquired' if "Ice Rod" in acquired_items }}" /></td>
<td><img src="{{ icons["Bombos"] }}" class="{{ 'acquired' if "Bombos" in acquired_items }}" /></td>
<td><img src="{{ icons["Ether"] }}" class="{{ 'acquired' if "Ether" in acquired_items }}" /></td>
<td><img src="{{ icons["Quake"] }}" class="{{ 'acquired' if "Quake" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Lamp"] }}" class="{{ 'acquired' if "Lamp" in acquired_items }}" /></td>
<td><img src="{{ icons["Hammer"] }}" class="{{ 'acquired' if "Hammer" in acquired_items }}" /></td>
<td><img src="{{ icons["Flute"] }}" class="{{ 'acquired' if "Flute" in acquired_items }}" /></td>
<td><img src="{{ icons["Bug Catching Net"] }}" class="{{ 'acquired' if "Bug Catching Net" in acquired_items }}" /></td>
<td><img src="{{ icons["Book of Mudora"] }}" class="{{ 'acquired' if "Book of Mudora" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Bottle"] }}" class="{{ 'acquired' if "Bottle" in acquired_items }}" /></td>
<td><img src="{{ icons["Cane of Somaria"] }}" class="{{ 'acquired' if "Cane of Somaria" in acquired_items }}" /></td>
<td><img src="{{ icons["Cane of Byrna"] }}" class="{{ 'acquired' if "Cane of Byrna" in acquired_items }}" /></td>
<td><img src="{{ icons["Cape"] }}" class="{{ 'acquired' if "Cape" in acquired_items }}" /></td>
<td><img src="{{ icons["Magic Mirror"] }}" class="{{ 'acquired' if "Magic Mirror" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Pegasus Boots"] }}" class="{{ 'acquired' if "Pegasus Boots" in acquired_items }}" /></td>
<td><img src="{{ glove_url }}" class="{{ 'acquired' if glove_acquired }}" /></td>
<td><img src="{{ icons["Flippers"] }}" class="{{ 'acquired' if "Flippers" in acquired_items }}" /></td>
<td><img src="{{ icons["Moon Pearl"] }}" class="{{ 'acquired' if "Moon Pearl" in acquired_items }}" /></td>
<td><img src="{{ icons["Mushroom"] }}" class="{{ 'acquired' if "Mushroom" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ sword_url }}" class="{{ 'acquired' if sword_acquired }}" /></td>
<td><img src="{{ shield_url }}" class="{{ 'acquired' if shield_acquired }}" /></td>
<td><img src="{{ mail_url }}" class="acquired" /></td>
<td><img src="{{ icons["Shovel"] }}" class="{{ 'acquired' if "Shovel" in acquired_items }}" /></td>
<td><img src="{{ icons["Triforce"] }}" class="{{ 'acquired' if "Triforce" in acquired_items }}" /></td>
</tr>
</table>
<table id="location-table">
<tr>
<th></th>
<th class="counter"><img src="{{ icons["Chest"] }}" /></th>
{% if key_locations and "Universal" not in key_locations %}
<th class="counter"><img src="{{ icons["Small Key"] }}" /></th>
{% endif %}
{% if big_key_locations %}
<th><img src="{{ icons["Big Key"] }}" /></th>
{% endif %}
</tr>
{% for area in sp_areas %}
<tr>
<td>{{ area }}</td>
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
{% if key_locations and "Universal" not in key_locations %}
<td class="counter">
{{ inventory[small_key_ids[area]] if area in key_locations else '—' }}
</td>
{% endif %}
{% if big_key_locations %}
<td>
{{ '✔' if area in big_key_locations and inventory[big_key_ids[area]] else ('—' if area not in big_key_locations else '') }}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
</body>
</html>

View File

@@ -6,52 +6,42 @@
{% endblock %}
{# List all tracker-relevant icons. Format: (Name, Image URL) #}
{%- set icons = {
"Blue Shield": "https://www.zeldadungeon.net/wiki/images/8/85/Fighters-Shield.png",
"Red Shield": "https://www.zeldadungeon.net/wiki/images/5/55/Fire-Shield.png",
"Mirror Shield": "https://www.zeldadungeon.net/wiki/images/8/84/Mirror-Shield.png",
"Fighter Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/40/SFighterSword.png?width=1920",
"Master Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/SMasterSword.png?width=1920",
"Tempered Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/92/STemperedSword.png?width=1920",
"Golden Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/2/28/SGoldenSword.png?width=1920",
"Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=5f85a70e6366bf473544ef93b274f74c",
"Silver Bow": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/Bow.png?width=1920",
"Green Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c9/SGreenTunic.png?width=1920",
"Blue Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/98/SBlueTunic.png?width=1920",
"Red Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/7/74/SRedTunic.png?width=1920",
"Power Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/f/f5/SPowerGlove.png?width=1920",
"Titan Mitts": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920",
"Progressive Sword": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/cc/ALttP_Master_Sword_Sprite.png?version=55869db2a20e157cd3b5c8f556097725",
"Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png?version=405f42f97240c9dcd2b71ffc4bebc7f9",
"Progressive Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920",
"Flippers": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/4c/ZoraFlippers.png?width=1920",
"Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png?version=d601542d5abcc3e006ee163254bea77e",
"Progressive Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=cfb7648b3714cccc80e2b17b2adf00ed",
"Blue Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c3/ALttP_Boomerang_Sprite.png?version=96127d163759395eb510b81a556d500e",
"Red Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Magical_Boomerang_Sprite.png?version=47cddce7a07bc3e4c2c10727b491f400",
"Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png?version=c90bc8e07a52e8090377bd6ef854c18b",
"Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png?version=1f1acb30d71bd96b60a3491e54bbfe59",
"Magic Powder": "https://www.zeldadungeon.net/wiki/images/thumb/6/62/MagicPowder-ALttP-Sprite.png/86px-MagicPowder-ALttP-Sprite.png",
"Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png?version=6eabc9f24d25697e2c4cd43ddc8207c0",
"Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png?version=1f944148223d91cfc6a615c92286c3bc",
"Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png?version=f4d6aba47fb69375e090178f0fc33b26",
"Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png?version=34027651a5565fcc5a83189178ab17b5",
"Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png?version=efd64d451b1831bd59f7b7d6b61b5879",
"Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png?version=e76eaa1ec509c9a5efb2916698d5a4ce",
"Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png?version=e0adec227193818dcaedf587eba34500",
"Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png?version=e73d1ce0115c2c70eaca15b014bd6f05",
"Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png?version=ec4982b31c56da2c0c010905c5c60390",
"Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png?version=4d40e0ee015b687ff75b333b968d8be6",
"Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png?version=11e4632bba54f6b9bf921df06ac93744",
"Bottle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png?version=fd98ab04db775270cbe79fce0235777b",
"Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png?version=8cc1900dfd887890badffc903bb87943",
"Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png?version=758b607c8cbe2cf1900d42a0b3d0fb54",
"Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png?version=6b77f0d609aab0c751307fc124736832",
"Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png?version=e035dbc9cbe2a3bd44aa6d047762b0cc",
"Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png?version=dc398e1293177581c16303e4f9d12a48",
{% set icons = {
"Blue Shield": "https://www.zeldadungeon.net/wiki/images/thumb/c/c3/FightersShield-ALttP-Sprite.png/100px-FightersShield-ALttP-Sprite.png",
"Red Shield": "https://www.zeldadungeon.net/wiki/images/thumb/9/9e/FireShield-ALttP-Sprite.png/111px-FireShield-ALttP-Sprite.png",
"Mirror Shield": "https://www.zeldadungeon.net/wiki/images/thumb/e/e3/MirrorShield-ALttP-Sprite.png/105px-MirrorShield-ALttP-Sprite.png",
"Progressive Sword": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/c/cc/ALttP_Master_Sword_Sprite.png",
"Progressive Bow": "https://www.zeldadungeon.net/wiki/images/thumb/8/8c/BowArrows-ALttP-Sprite.png/120px-BowArrows-ALttP-Sprite.png",
"Progressive Glove": "https://www.zeldadungeon.net/wiki/images/thumb/4/41/PowerGlove-ALttP-Sprite.png/105px-PowerGlove-ALttP-Sprite.png",
"Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png",
"Flippers": "https://www.zeldadungeon.net/wiki/images/thumb/b/bc/ZoraFlippers-ALttP-Sprite.png/112px-ZoraFlippers-ALttP-Sprite.png",
"Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png",
"Blue Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/f/f0/Boomerang-ALttP-Sprite.png/86px-Boomerang-ALttP-Sprite.png",
"Red Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/3/3c/MagicalBoomerang-ALttP-Sprite.png/86px-MagicalBoomerang-ALttP-Sprite.png",
"Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png",
"Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png",
"Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png",
"Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png",
"Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png",
"Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png",
"Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png",
"Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png",
"Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png",
"Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png",
"Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png",
"Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png",
"Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png",
"Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png",
"Bottles": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png",
"Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png",
"Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png",
"Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png",
"Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png",
"Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png",
"Triforce Piece": "https://www.zeldadungeon.net/wiki/images/thumb/5/54/Triforce_Fragment_-_BS_Zelda.png/62px-Triforce_Fragment_-_BS_Zelda.png",
"Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png?version=4f35d92842f0de39d969181eea03774e",
"Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png?version=136dfa418ba76c8b4e270f466fc12f4d",
"Bombs": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/38/ALttP_Bomb_Sprite.png",
"Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png",
"Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png",
"Chest": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Treasure_Chest_Sprite.png?version=5f530ecd98dcb22251e146e8049c0dda",
"Light World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e7/ALttP_Soldier_Green_Sprite.png?version=d650d417934cd707a47e496489c268a6",
"Dark World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/94/ALttP_Moblin_Sprite.png?version=ebf50e33f4657c377d1606bcc0886ddc",
@@ -68,33 +58,93 @@
"Misery Mire": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/85/ALttP_Vitreous_Sprite.png?version=92b2e9cb0aa63f831760f08041d8d8d8",
"Turtle Rock": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/91/ALttP_Trinexx_Sprite.png?version=0cc867d513952aa03edd155597a0c0be",
"Ganons Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Ganon_Sprite.png?version=956f51f054954dfff53c1a9d4f929c74",
} -%}
} %}
{% set inventory_order = [
"Progressive Sword",
"Progressive Bow",
"Blue Boomerang",
"Red Boomerang",
"Hookshot",
"Bombs",
"Mushroom",
"Magic Powder",
"Fire Rod",
"Ice Rod",
"Bombos",
"Ether",
"Quake",
"Lamp",
"Hammer",
"Flute",
"Bug Catching Net",
"Book of Mudora",
"Cane of Somaria",
"Cane of Byrna",
"Cape",
"Magic Mirror",
"Shovel",
"Pegasus Boots",
"Flippers",
"Progressive Glove",
"Moon Pearl",
"Bottles",
"Triforce Piece",
"Triforce",
] %}
{% set dungeon_keys = {
"Hyrule Castle": ("Small Key (Hyrule Castle)", "Big Key (Hyrule Castle)"),
"Agahnims Tower": ("Small Key (Agahnims Tower)", "Big Key (Agahnims Tower)"),
"Eastern Palace": ("Small Key (Eastern Palace)", "Big Key (Eastern Palace)"),
"Desert Palace": ("Small Key (Desert Palace)", "Big Key (Desert Palace)"),
"Tower of Hera": ("Small Key (Tower of Hera)", "Big Key (Tower of Hera)"),
"Palace of Darkness": ("Small Key (Palace of Darkness)", "Big Key (Palace of Darkness)"),
"Thieves Town": ("Small Key (Thieves Town)", "Big Key (Thieves Town)"),
"Skull Woods": ("Small Key (Skull Woods)", "Big Key (Skull Woods)"),
"Swamp Palace": ("Small Key (Swamp Palace)", "Big Key (Swamp Palace)"),
"Ice Palace": ("Small Key (Ice Palace)", "Big Key (Ice Palace)"),
"Misery Mire": ("Small Key (Misery Mire)", "Big Key (Misery Mire)"),
"Turtle Rock": ("Small Key (Turtle Rock)", "Big Key (Turtle Rock)"),
"Ganons Tower": ("Small Key (Ganons Tower)", "Big Key (Ganons Tower)"),
} %}
{% set multi_items = [
"Progressive Sword",
"Progressive Glove",
"Progressive Bow",
"Bottles",
"Triforce Piece",
] %}
{%- block custom_table_headers %}
{#- macro that creates a table header with display name and image -#}
{%- macro make_header(name, img_src) %}
<th class="center-column">
<img height="24" src="{{ img_src }}" title="{{ name }}" alt="{{ name }}" />
</th>
{% endmacro -%}
{#- call the macro to build the table header -#}
{%- for name in tracking_names %}
{%- if name in icons -%}
{#- macro that creates a table header with display name and image -#}
{%- macro make_header(name, img_src) %}
<th class="center-column">
<img class="icon-sprite" src="{{ icons[name] }}" alt="{{ name | e }}" title="{{ name | e }}" />
<img height="24" src="{{ img_src }}" title="{{ name }}" alt="{{ name }}">
</th>
{%- endif %}
{% endfor -%}
{% endmacro -%}
{#- call the macro to build the table header -#}
{%- for item in inventory_order %}
{%- if item in icons -%}
<th class="center-column">
<img class="icon-sprite" src="{{ icons[item] }}" alt="{{ item | e }}" title="{{ item | e }}">
</th>
{%- endif %}
{% endfor -%}
{% endblock %}
{# build each row of custom entries #}
{% block custom_table_row scoped %}
{%- for id in tracking_ids -%}
{# {{ checks }}#}
{%- if inventories[(team, player)][id] -%}
{%- for item in inventory_order -%}
{%- if inventories[(team, player)][item] -%}
<td class="center-column item-acquired">
{% if id in multi_items %}{{ inventories[(team, player)][id] }}{% else %}✔️{% endif %}
{% if item in multi_items %}
{{ inventories[(team, player)][item] }}
{% else %}
✔️
{% endif %}
</td>
{%- else -%}
<td></td>
@@ -104,102 +154,95 @@
{% block custom_tables %}
{% for team, _ in total_team_locations.items() %}
<div class="table-wrapper">
<table id="area-table" class="table non-unique-item-table">
<thead>
<tr>
<th rowspan="2">#</th>
<th rowspan="2">Name</th>
{% for area in ordered_areas %}
{% set colspan = 1 %}
{% if area in key_locations %}
{% set colspan = colspan + 1 %}
{% endif %}
{% if area in big_key_locations %}
{% set colspan = colspan + 1 %}
{% endif %}
{% if area in icons %}
<th colspan="{{ colspan }}" class="center-column upper-row">
<img class="icon-sprite" src="{{ icons[area] }}" alt="{{ area }}" title="{{ area }}"></th>
{%- else -%}
<th colspan="{{ colspan }}" class="center-column">{{ area }}</th>
{%- endif -%}
{%- endfor -%}
<th rowspan="2" class="center-column">&percnt;</th>
<th rowspan="2" class="center-column hours">Last<br>Activity</th>
</tr>
<tr>
{% for area in ordered_areas %}
{% for team in total_team_locations %}
<div class="table-wrapper">
<table class="table non-unique-item-table">
<thead>
<tr>
<th rowspan="2">#</th>
<th rowspan="2">Name</th>
{% for region in known_regions %}
{% set colspan = 1 %}
{% if region == "Agahnims Tower" %}
{% set colspan = 2 %}
{% elif region in dungeon_keys %}
{% set colspan = 3 %}
{% endif %}
{% if region in icons %}
<th colspan="{{ colspan }}" class="center-column upper-row">
<img class="icon-sprite" src="{{ icons[region] }}" alt="{{ region }}" title="{{ region }}">
</th>
{% else %}
<th colspan="{{ colspan }}" class="center-column">{{ region }}</th>
{% endif %}
{% endfor %}
<th class="center-column">Total</th>
</tr>
<tr>
{% for region in known_regions %}
<th class="center-column lower-row fraction">
<img class="icon-sprite" src="{{ icons["Chest"] }}" alt="Checks" title="Checks Complete">
</th>
{% if region in dungeon_keys %}
<th class="center-column lower-row number">
<img class="icon-sprite" src="{{ icons["Small Key"] }}" alt="Small Key" title="Small Keys">
</th>
{# Special check just for Agahnims Tower, which has no big keys. #}
{% if region != "Agahnims Tower" %}
<th class="center-column lower-row number">
<img class="icon-sprite" src="{{ icons["Big Key"] }}" alt="Big Key" title="Big Keys">
</th>
{% endif %}
{% endif %}
{% endfor %}
{# For "total" checks #}
<th class="center-column lower-row fraction">
<img class="icon-sprite" src="{{ icons["Chest"] }}" alt="Checks" title="Checks Complete">
<img class="icon-sprite" src="{{ icons["Chest"] }}" alt="Checks" title="Total Checks Complete">
</th>
{% if area in key_locations %}
<th class="center-column lower-row number">
<img class="icon-sprite" src="{{ icons["Small Key"] }}" alt="Small Key" title="Small Keys">
</th>
{% endif %}
{% if area in big_key_locations %}
<th class="center-column lower-row number">
<img class="icon-sprite" src="{{ icons["Big Key"] }}" alt="Big Key" title="Big Keys">
</th>
{%- endif -%}
{%- endfor -%}
</tr>
</thead>
<tbody>
{%- for (checks_team, player), area_checks in checks_done.items() if games[(team, player)] == current_tracker and team == checks_team -%}
<tr>
<td><a href="{{ url_for("get_player_tracker", tracker=room.tracker,
tracked_team=team, tracked_player=player)}}">{{ player }}</a></td>
<td>{{ player_names_with_alias[(team, player)] | e }}</td>
{%- for area in ordered_areas -%}
{% if (team, player) in checks_in_area and area in checks_in_area[(team, player)] %}
{%- set checks_done = area_checks[area] -%}
{%- set checks_total = checks_in_area[(team, player)][area] -%}
{%- if checks_done == checks_total -%}
</tr>
</thead>
<tbody>
{% for (player_team, player), player_regions in regions.items() if team == player_team %}
<tr>
<td>
<a href="{{ url_for("get_player_tracker", tracker=room.tracker, tracked_team=team, tracked_player=player) }}">
{{ player }}
</a>
</td>
<td>{{ player_names_with_alias[(team, player)] | e }}</td>
{% for region, counts in player_regions.items() %}
<td class="item-acquired center-column">
{{ checks_done }}/{{ checks_total }}</td>
{%- else -%}
<td class="center-column">{{ checks_done }}/{{ checks_total }}</td>
{%- endif -%}
{%- if area in key_locations -%}
<td class="center-column">{{ inventories[(team, player)][small_key_ids[area]] }}</td>
{%- endif -%}
{%- if area in big_key_locations -%}
<td class="center-column">{% if inventories[(team, player)][big_key_ids[area]] %}✔️{% endif %}</td>
{%- endif -%}
{% else %}
<td class="center-column"></td>
{%- if area in key_locations -%}
<td class="center-column"></td>
{%- endif -%}
{%- if area in big_key_locations -%}
<td class="center-column"></td>
{%- endif -%}
{% endif %}
{%- endfor -%}
{{ counts.checked }}/{{ counts.total }}
</td>
<td class="center-column">
{% set location_count = locations[(team, player)] | length %}
{%- if locations[(team, player)] | length > 0 -%}
{% set percentage_of_completion = locations_complete[(team, player)] / location_count * 100 %}
{{ "{0:.2f}".format(percentage_of_completion) }}
{%- else -%}
100.00
{%- endif -%}
</td>
{% if region in dungeon_keys %}
<td class="center-column">
{{ inventories[(team, player)][dungeon_keys[region][0]] }}
</td>
{%- if activity_timers[(team, player)] -%}
<td class="center-column">{{ activity_timers[(team, player)].total_seconds() }}</td>
{%- else -%}
<td class="center-column">None</td>
{%- endif -%}
</tr>
{%- endfor -%}
</tbody>
</table>
</div>
{# Special check just for Agahnims Tower, which has no big keys. #}
{% if region != "Agahnims Tower" %}
<td class="center-column">
{% if inventories[(team, player)][dungeon_keys[region][1]] %}
✔️
{% endif %}
</td>
{% endif %}
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
{% endblock %}

View File

@@ -49,7 +49,7 @@
<a href="{{ url_for("game_info", game=game_name, lang="en") }}">Game Page</a>
{% if world.web.tutorials %}
<span class="link-spacer">|</span>
<a href="{{ url_for("tutorial_landing") }}#{{ game_name }}">Setup Guides</a>
<a href="{{ url_for("tutorial_landing", _anchor = game_name | urlencode) }}">Setup Guides</a>
{% endif %}
{% if world.web.options_page is string %}
<span class="link-spacer">|</span>

View File

@@ -1,73 +1,89 @@
{%- set icons = {
"Blue Shield": "https://www.zeldadungeon.net/wiki/images/8/85/Fighters-Shield.png",
"Red Shield": "https://www.zeldadungeon.net/wiki/images/5/55/Fire-Shield.png",
"Mirror Shield": "https://www.zeldadungeon.net/wiki/images/8/84/Mirror-Shield.png",
"Fighter Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/40/SFighterSword.png?width=1920",
"Master Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/SMasterSword.png?width=1920",
"Tempered Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/92/STemperedSword.png?width=1920",
"Golden Sword": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/2/28/SGoldenSword.png?width=1920",
"Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=5f85a70e6366bf473544ef93b274f74c",
"Silver Bow": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/6/65/Bow.png?width=1920",
"Green Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c9/SGreenTunic.png?width=1920",
"Blue Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/9/98/SBlueTunic.png?width=1920",
"Red Mail": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/7/74/SRedTunic.png?width=1920",
"Power Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/f/f5/SPowerGlove.png?width=1920",
"Titan Mitts": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920",
"Progressive Sword": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/cc/ALttP_Master_Sword_Sprite.png?version=55869db2a20e157cd3b5c8f556097725",
"Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png?version=405f42f97240c9dcd2b71ffc4bebc7f9",
"Progressive Glove": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/c/c1/STitanMitt.png?width=1920",
"Flippers": "https://oyster.ignimgs.com/mediawiki/apis.ign.com/the-legend-of-zelda-a-link-to-the-past/4/4c/ZoraFlippers.png?width=1920",
"Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png?version=d601542d5abcc3e006ee163254bea77e",
"Progressive Bow": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Bow_%26_Arrows_Sprite.png?version=cfb7648b3714cccc80e2b17b2adf00ed",
"Blue Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c3/ALttP_Boomerang_Sprite.png?version=96127d163759395eb510b81a556d500e",
"Red Boomerang": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Magical_Boomerang_Sprite.png?version=47cddce7a07bc3e4c2c10727b491f400",
"Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png?version=c90bc8e07a52e8090377bd6ef854c18b",
"Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png?version=1f1acb30d71bd96b60a3491e54bbfe59",
"Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png?version=c24e38effbd4f80496d35830ce8ff4ec",
"Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png?version=6eabc9f24d25697e2c4cd43ddc8207c0",
"Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png?version=1f944148223d91cfc6a615c92286c3bc",
"Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png?version=f4d6aba47fb69375e090178f0fc33b26",
"Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png?version=34027651a5565fcc5a83189178ab17b5",
"Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png?version=efd64d451b1831bd59f7b7d6b61b5879",
"Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png?version=e76eaa1ec509c9a5efb2916698d5a4ce",
"Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png?version=e0adec227193818dcaedf587eba34500",
"Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png?version=e73d1ce0115c2c70eaca15b014bd6f05",
"Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png?version=ec4982b31c56da2c0c010905c5c60390",
"Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png?version=4d40e0ee015b687ff75b333b968d8be6",
"Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png?version=11e4632bba54f6b9bf921df06ac93744",
"Bottle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png?version=fd98ab04db775270cbe79fce0235777b",
"Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png?version=8cc1900dfd887890badffc903bb87943",
"Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png?version=758b607c8cbe2cf1900d42a0b3d0fb54",
"Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png?version=6b77f0d609aab0c751307fc124736832",
"Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png?version=e035dbc9cbe2a3bd44aa6d047762b0cc",
"Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png?version=dc398e1293177581c16303e4f9d12a48",
{% set icons = {
"Blue Shield": "https://www.zeldadungeon.net/wiki/images/thumb/c/c3/FightersShield-ALttP-Sprite.png/100px-FightersShield-ALttP-Sprite.png",
"Red Shield": "https://www.zeldadungeon.net/wiki/images/thumb/9/9e/FireShield-ALttP-Sprite.png/111px-FireShield-ALttP-Sprite.png",
"Mirror Shield": "https://www.zeldadungeon.net/wiki/images/thumb/e/e3/MirrorShield-ALttP-Sprite.png/105px-MirrorShield-ALttP-Sprite.png",
"Fighter Sword": "https://upload.wikimedia.org/wikibooks/en/8/8e/Zelda_ALttP_item_L-1_Sword.png",
"Master Sword": "https://upload.wikimedia.org/wikibooks/en/8/87/BS_Zelda_AST_item_L-2_Sword.png",
"Tempered Sword": "https://upload.wikimedia.org/wikibooks/en/c/cc/BS_Zelda_AST_item_L-3_Sword.png",
"Golden Sword": "https://upload.wikimedia.org/wikibooks/en/4/40/BS_Zelda_AST_item_L-4_Sword.png",
"Bow": "https://www.zeldadungeon.net/wiki/images/thumb/8/8c/BowArrows-ALttP-Sprite.png/120px-BowArrows-ALttP-Sprite.png",
"Silver Bow": "https://upload.wikimedia.org/wikibooks/en/6/69/Zelda_ALttP_item_Silver_Arrows.png",
"Green Mail": "https://upload.wikimedia.org/wikibooks/en/d/dd/Zelda_ALttP_item_Green_Mail.png",
"Blue Mail": "https://upload.wikimedia.org/wikibooks/en/b/b5/Zelda_ALttP_item_Blue_Mail.png",
"Red Mail": "https://upload.wikimedia.org/wikibooks/en/d/db/Zelda_ALttP_item_Red_Mail.png",
"Power Glove": "https://www.zeldadungeon.net/wiki/images/thumb/4/41/PowerGlove-ALttP-Sprite.png/105px-PowerGlove-ALttP-Sprite.png",
"Titan Mitts": "https://www.zeldadungeon.net/wiki/images/thumb/7/75/TitanMitt-ALttP-Sprite.png/105px-TitanMitt-ALttP-Sprite.png",
"Pegasus Boots": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Pegasus_Shoes_Sprite.png",
"Flippers": "https://www.zeldadungeon.net/wiki/images/thumb/b/bc/ZoraFlippers-ALttP-Sprite.png/112px-ZoraFlippers-ALttP-Sprite.png",
"Moon Pearl": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Moon_Pearl_Sprite.png",
"Blue Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/f/f0/Boomerang-ALttP-Sprite.png/86px-Boomerang-ALttP-Sprite.png",
"Red Boomerang": "https://www.zeldadungeon.net/wiki/images/thumb/3/3c/MagicalBoomerang-ALttP-Sprite.png/86px-MagicalBoomerang-ALttP-Sprite.png",
"Hookshot": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/24/Hookshot.png",
"Mushroom": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/35/ALttP_Mushroom_Sprite.png",
"Magic Powder": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Powder_Sprite.png",
"Fire Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d6/FireRod.png",
"Ice Rod": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d7/ALttP_Ice_Rod_Sprite.png",
"Bombos": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/8c/ALttP_Bombos_Medallion_Sprite.png",
"Ether": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/Ether.png",
"Quake": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/56/ALttP_Quake_Medallion_Sprite.png",
"Lamp": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/6/63/ALttP_Lantern_Sprite.png",
"Hammer": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d1/ALttP_Hammer_Sprite.png",
"Shovel": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/c/c4/ALttP_Shovel_Sprite.png",
"Flute": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/db/Flute.png",
"Bug Catching Net": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/5/54/Bug-CatchingNet.png",
"Book of Mudora": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/2/22/ALttP_Book_of_Mudora_Sprite.png",
"Bottles": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ef/ALttP_Magic_Bottle_Sprite.png",
"Cane of Somaria": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e1/ALttP_Cane_of_Somaria_Sprite.png",
"Cane of Byrna": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/bc/ALttP_Cane_of_Byrna_Sprite.png",
"Cape": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1c/ALttP_Magic_Cape_Sprite.png",
"Magic Mirror": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e5/ALttP_Magic_Mirror_Sprite.png",
"Triforce": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/4/4e/TriforceALttPTitle.png",
"Triforce Piece": "https://www.zeldadungeon.net/wiki/images/thumb/5/54/Triforce_Fragment_-_BS_Zelda.png/62px-Triforce_Fragment_-_BS_Zelda.png",
"Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png?version=4f35d92842f0de39d969181eea03774e",
"Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png?version=136dfa418ba76c8b4e270f466fc12f4d",
"Chest": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Treasure_Chest_Sprite.png?version=5f530ecd98dcb22251e146e8049c0dda",
"Light World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/e7/ALttP_Soldier_Green_Sprite.png?version=d650d417934cd707a47e496489c268a6",
"Dark World": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/94/ALttP_Moblin_Sprite.png?version=ebf50e33f4657c377d1606bcc0886ddc",
"Hyrule Castle": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/d/d3/ALttP_Ball_and_Chain_Trooper_Sprite.png?version=1768a87c06d29cc8e7ddd80b9fa516be",
"Agahnims Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/1/1e/ALttP_Agahnim_Sprite.png?version=365956e61b0c2191eae4eddbe591dab5",
"Desert Palace": "https://www.zeldadungeon.net/wiki/images/2/25/Lanmola-ALTTP-Sprite.png",
"Eastern Palace": "https://www.zeldadungeon.net/wiki/images/d/dc/RedArmosKnight.png",
"Tower of Hera": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/3c/ALttP_Moldorm_Sprite.png?version=c588257bdc2543468e008a6b30f262a7",
"Palace of Darkness": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/e/ed/ALttP_Helmasaur_King_Sprite.png?version=ab8a4a1cfd91d4fc43466c56cba30022",
"Swamp Palace": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/7/73/ALttP_Arrghus_Sprite.png?version=b098be3122e53f751b74f4a5ef9184b5",
"Skull Woods": "https://alttp-wiki.net/images/6/6a/Mothula.png",
"Thieves Town": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/86/ALttP_Blind_the_Thief_Sprite.png?version=3833021bfcd112be54e7390679047222",
"Ice Palace": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Kholdstare_Sprite.png?version=e5a1b0e8b2298e550d85f90bf97045c0",
"Misery Mire": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/8/85/ALttP_Vitreous_Sprite.png?version=92b2e9cb0aa63f831760f08041d8d8d8",
"Turtle Rock": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/9/91/ALttP_Trinexx_Sprite.png?version=0cc867d513952aa03edd155597a0c0be",
"Ganons Tower": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/b/b9/ALttP_Ganon_Sprite.png?version=956f51f054954dfff53c1a9d4f929c74",
} -%}
"Bombs": "https://static.wikia.nocookie.net/zelda_gamepedia_en/images/3/38/ALttP_Bomb_Sprite.png",
"Small Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/f/f1/ALttP_Small_Key_Sprite.png",
"Big Key": "https://gamepedia.cursecdn.com/zelda_gamepedia_en/3/33/ALttP_Big_Key_Sprite.png",
} %}
<!DOCTYPE html>
{% set inventory_order = [
"Progressive Bow", "Boomerangs", "Hookshot", "Bombs", "Mushroom", "Magic Powder",
"Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Progressive Mail",
"Lamp", "Hammer", "Flute", "Bug Catching Net", "Book of Mudora", "Progressive Shield",
"Bottles", "Cane of Somaria", "Cane of Byrna", "Cape", "Magic Mirror", "Progressive Sword",
"Shovel", "Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Triforce Piece",
] %}
{# Most have a duplicated 0th entry for when we have none of that item to still load the correct icon/name. #}
{% set progressive_order = {
"Progressive Bow": ["Bow", "Bow", "Silver Bow"],
"Progressive Mail": ["Green Mail", "Blue Mail", "Red Mail"],
"Progressive Shield": ["Blue Shield", "Blue Shield", "Red Shield", "Mirror Shield"],
"Progressive Sword": ["Fighter Sword", "Fighter Sword", "Master Sword", "Tempered Sword", "Golden Sword"],
"Progressive Glove": ["Power Glove", "Power Glove", "Titan Mitts"],
} %}
{% set dungeon_keys = {
"Hyrule Castle": ("Small Key (Hyrule Castle)", "Big Key (Hyrule Castle)"),
"Agahnims Tower": ("Small Key (Agahnims Tower)", "Big Key (Agahnims Tower)"),
"Eastern Palace": ("Small Key (Eastern Palace)", "Big Key (Eastern Palace)"),
"Desert Palace": ("Small Key (Desert Palace)", "Big Key (Desert Palace)"),
"Tower of Hera": ("Small Key (Tower of Hera)", "Big Key (Tower of Hera)"),
"Palace of Darkness": ("Small Key (Palace of Darkness)", "Big Key (Palace of Darkness)"),
"Swamp Palace": ("Small Key (Swamp Palace)", "Big Key (Swamp Palace)"),
"Thieves Town": ("Small Key (Thieves Town)", "Big Key (Thieves Town)"),
"Skull Woods": ("Small Key (Skull Woods)", "Big Key (Skull Woods)"),
"Ice Palace": ("Small Key (Ice Palace)", "Big Key (Ice Palace)"),
"Misery Mire": ("Small Key (Misery Mire)", "Big Key (Misery Mire)"),
"Turtle Rock": ("Small Key (Turtle Rock)", "Big Key (Turtle Rock)"),
"Ganons Tower": ("Small Key (Ganons Tower)", "Big Key (Ganons Tower)"),
} %}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ player_name }}&apos;s Tracker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/lttp-tracker.css") }}"/>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/lttp-tracker.js") }}"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/tracker__ALinkToThePast.css') }}">
</head>
<body>
@@ -76,79 +92,128 @@
<a href="{{ url_for("get_generic_game_tracker", tracker=room.tracker, tracked_team=team, tracked_player=player) }}">Switch To Generic Tracker</a>
</div>
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
<table id="inventory-table">
<tr>
<td><img src="{{ icons[bow_icon] }}" class="{{ 'acquired' if bow_acquired }}" /></td>
<td><img src="{{ icons["Blue Boomerang"] }}" class="{{ 'acquired' if 'Blue Boomerang' in acquired_items }}" /></td>
<td><img src="{{ icons["Red Boomerang"] }}" class="{{ 'acquired' if 'Red Boomerang' in acquired_items }}" /></td>
<td><img src="{{ icons["Hookshot"] }}" class="{{ 'acquired' if 'Hookshot' in acquired_items }}" /></td>
<td><img src="{{ icons["Magic Powder"] }}" class="powder-fix {{ 'acquired' if 'Magic Powder' in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Fire Rod"] }}" class="{{ 'acquired' if "Fire Rod" in acquired_items }}" /></td>
<td><img src="{{ icons["Ice Rod"] }}" class="{{ 'acquired' if "Ice Rod" in acquired_items }}" /></td>
<td><img src="{{ icons["Bombos"] }}" class="{{ 'acquired' if "Bombos" in acquired_items }}" /></td>
<td><img src="{{ icons["Ether"] }}" class="{{ 'acquired' if "Ether" in acquired_items }}" /></td>
<td><img src="{{ icons["Quake"] }}" class="{{ 'acquired' if "Quake" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Lamp"] }}" class="{{ 'acquired' if "Lamp" in acquired_items }}" /></td>
<td><img src="{{ icons["Hammer"] }}" class="{{ 'acquired' if "Hammer" in acquired_items }}" /></td>
<td><img src="{{ icons["Flute"] }}" class="{{ 'acquired' if "Flute" in acquired_items }}" /></td>
<td><img src="{{ icons["Bug Catching Net"] }}" class="{{ 'acquired' if "Bug Catching Net" in acquired_items }}" /></td>
<td><img src="{{ icons["Book of Mudora"] }}" class="{{ 'acquired' if "Book of Mudora" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Bottle"] }}" class="{{ 'acquired' if "Bottle" in acquired_items }}" /></td>
<td><img src="{{ icons["Cane of Somaria"] }}" class="{{ 'acquired' if "Cane of Somaria" in acquired_items }}" /></td>
<td><img src="{{ icons["Cane of Byrna"] }}" class="{{ 'acquired' if "Cane of Byrna" in acquired_items }}" /></td>
<td><img src="{{ icons["Cape"] }}" class="{{ 'acquired' if "Cape" in acquired_items }}" /></td>
<td><img src="{{ icons["Magic Mirror"] }}" class="{{ 'acquired' if "Magic Mirror" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons["Pegasus Boots"] }}" class="{{ 'acquired' if "Pegasus Boots" in acquired_items }}" /></td>
<td><img src="{{ icons[glove_icon] }}" class="{{ 'acquired' if glove_acquired }}" /></td>
<td><img src="{{ icons["Flippers"] }}" class="{{ 'acquired' if "Flippers" in acquired_items }}" /></td>
<td><img src="{{ icons["Moon Pearl"] }}" class="{{ 'acquired' if "Moon Pearl" in acquired_items }}" /></td>
<td><img src="{{ icons["Mushroom"] }}" class="{{ 'acquired' if "Mushroom" in acquired_items }}" /></td>
</tr>
<tr>
<td><img src="{{ icons[sword_icon] }}" class="{{ 'acquired' if sword_acquired }}" /></td>
<td><img src="{{ icons[shield_icon] }}" class="{{ 'acquired' if shield_acquired }}" /></td>
<td><img src="{{ icons[mail_icon] }}" class="acquired" /></td>
<td><img src="{{ icons["Shovel"] }}" class="{{ 'acquired' if "Shovel" in acquired_items }}" /></td>
<td><img src="{{ icons["Triforce"] }}" class="{{ 'acquired' if "Triforce" in acquired_items }}" /></td>
</tr>
</table>
<table id="location-table">
<tr>
<th></th>
<th class="counter"><img src="{{ icons["Chest"] }}" /></th>
{% if key_locations and "Universal" not in key_locations %}
<th class="counter"><img src="{{ icons["Small Key"] }}" /></th>
<div class="tracker-container">
{# Inventory Grid #}
<div class="inventory-grid">
{% for item in inventory_order %}
{% if item in progressive_order %}
{% set non_prog_item = progressive_order[item][inventory[item]] %}
<div class="item">
<img
src="{{ icons[non_prog_item] }}"
alt="{{ non_prog_item }}"
title="{{ non_prog_item }}"
{# Progressive Mail gets a special exception, since it starts displaying green mail. #}
class="{{ 'missing' if (item not in inventory or inventory[item] == 0) and item != 'Progressive Mail' }}"
>
</div>
{% elif item == "Boomerangs" %}
<div class="dual-item">
<img
src="{{ icons['Blue Boomerang'] }}"
alt="Blue Boomerang"
title="Blue Boomerang"
class="{{ 'missing' if 'Blue Boomerang' not in inventory }}"
>
<img
src="{{ icons['Red Boomerang'] }}"
alt="Red Boomerang"
title="Red Boomerang"
class="{{ 'missing' if 'Red Boomerang' not in inventory }}"
>
</div>
{% else %}
<div class="item {{ 'hidden' if item == 'Triforce Piece' and inventory['Triforce Piece'] == 0 }}">
<img
src="{{ icons[item] }}"
alt="{{ item }}"
title="{{ item }}"
class="{{ 'missing' if item not in inventory or inventory[item] == 0 }}"
>
{% if item == "Bottles" or item == "Triforce Piece" %}
<div class="quantity">{{ inventory[item] }}</div>
{% endif %}
</div>
{% endif %}
{% if big_key_locations %}
<th><img src="{{ icons["Big Key"] }}" /></th>
{% endif %}
</tr>
{% for area in sp_areas %}
<tr>
<td>{{ area }}</td>
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
{% if key_locations and "Universal" not in key_locations %}
<td class="counter">
{{ inventory[small_key_ids[area]] if area in key_locations else '—' }}
</td>
{% endif %}
{% if big_key_locations %}
<td>
{{ '✔' if area in big_key_locations and inventory[big_key_ids[area]] else ('—' if area not in big_key_locations else '') }}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
<div class="regions-list">
<div class="region region-header">
<div></div>
<div></div>
<div><img src="{{ icons['Small Key'] }}" alt="SK" title="Small Keys"></div>
<div><img src="{{ icons['Big Key'] }}" alt="BK" title="Big Keys"></div>
</div>
{% for region_name in known_regions %}
{% set region_data = regions[region_name] %}
{% if region_data["locations"] | length > 0 %}
<details class="region-details">
<summary>
{% if region_name in dungeon_keys %}
<div class="region">
<span>{{ region_name }}</span>
<span>{{ region_data["checked"] }} / {{ region_data["locations"] | length }}</span>
<span>{{ inventory[dungeon_keys[region_name][0]] }}</span>
<span>
{% if region_name == "Agahnims Tower" %}
&mdash;
{% elif inventory[dungeon_keys[region_name][1]] %}
{% endif %}
</span>
</div>
{% else %}
<div class="region">
<span>{{ region_name }}</span>
<span>{{ region_data["checked"] }} / {{ region_data["locations"] | length }}</span>
<span>&mdash;</span>
<span>&mdash;</span>
</div>
{% endif %}
</summary>
<div class="location-rows">
{% for location, checked in region_data["locations"] %}
<div>{{ location }}</div>
<div>{% if checked %}✔{% endif %}</div>
{% endfor %}
</div>
</details>
{% endif %}
{% endfor %}
</div>
</div>
<script>
const parser = new DOMParser();
const interval = 15_000;
window.addEventListener("load", () => {
setInterval(() => updateTracker()
.then(() => console.log("Refreshed tracker."))
.catch(console.error), interval);
});
async function updateTracker() {
const response = await fetch(`${window.location}`);
if (!response.ok) {
throw new Error(`Failed to fetch tracker update from ${window.location}. Received response: ${response.statusText}`);
}
const fakeDOM = parser.parseFromString(await response.text(), "text/html");
document.querySelector(".inventory-grid").innerHTML = fakeDOM.querySelector(".inventory-grid").innerHTML;
const regionDetailElements = document.querySelectorAll(".region-details");
const fakeDetailElements = fakeDOM.querySelectorAll(".region-details");
for (let i = 0; i < regionDetailElements.length; ++i) {
const isOpen = regionDetailElements[i].open;
regionDetailElements[i].innerHTML = fakeDetailElements[i].innerHTML;
regionDetailElements[i].open = isOpen;
}
}
</script>
</body>
</html>

View File

@@ -1,7 +1,7 @@
import datetime
import collections
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple, Counter
from uuid import UUID
from flask import render_template
@@ -422,11 +422,11 @@ from worlds import network_data_package
if "Factorio" in network_data_package["games"]:
def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]):
inventories: Dict[TeamPlayer, Dict[int, int]] = {
(team, player): {
inventories: Dict[TeamPlayer, collections.Counter[str]] = {
(team, player): collections.Counter({
tracker_data.item_id_to_name["Factorio"][item_id]: count
for item_id, count in tracker_data.get_player_inventory_counts(team, player).items()
} for team, players in tracker_data.get_all_slots().items() for player in players
}) for team, players in tracker_data.get_all_slots().items() for player in players
if tracker_data.get_player_game(team, player) == "Factorio"
}
@@ -456,210 +456,111 @@ if "Factorio" in network_data_package["games"]:
_multiworld_trackers["Factorio"] = render_Factorio_multiworld_tracker
if "A Link to the Past" in network_data_package["games"]:
# Mapping from non-progressive item to progressive name and max level.
non_progressive_items = {
"Fighter Sword": ("Progressive Sword", 1),
"Master Sword": ("Progressive Sword", 2),
"Tempered Sword": ("Progressive Sword", 3),
"Golden Sword": ("Progressive Sword", 4),
"Power Glove": ("Progressive Glove", 1),
"Titans Mitts": ("Progressive Glove", 2),
"Bow": ("Progressive Bow", 1),
"Silver Bow": ("Progressive Bow", 2),
"Blue Mail": ("Progressive Mail", 1),
"Red Mail": ("Progressive Mail", 2),
"Blue Shield": ("Progressive Shield", 1),
"Red Shield": ("Progressive Shield", 2),
"Mirror Shield": ("Progressive Shield", 3),
}
progressive_item_max = {
"Progressive Sword": 4,
"Progressive Glove": 2,
"Progressive Bow": 2,
"Progressive Mail": 2,
"Progressive Shield": 3,
}
bottle_items = [
"Bottle",
"Bottle (Bee)",
"Bottle (Blue Potion)",
"Bottle (Fairy)",
"Bottle (Good Bee)",
"Bottle (Green Potion)",
"Bottle (Red Potion)",
]
known_regions = [
"Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace",
"Tower of Hera", "Palace of Darkness", "Swamp Palace", "Thieves Town", "Skull Woods", "Ice Palace",
"Misery Mire", "Turtle Rock", "Ganons Tower"
]
class RegionCounts(NamedTuple):
total: int
checked: int
def prepare_inventories(team: int, player: int, inventory: Counter[str], tracker_data: TrackerData):
for item, (prog_item, level) in non_progressive_items.items():
if item in inventory:
inventory[prog_item] = min(max(inventory[prog_item], level), progressive_item_max[prog_item])
for bottle in bottle_items:
inventory["Bottles"] = min(inventory["Bottles"] + inventory[bottle], 4)
if "Progressive Bow (Alt)" in inventory:
inventory["Progressive Bow"] += inventory["Progressive Bow (Alt)"]
inventory["Progressive Bow"] = min(inventory["Progressive Bow"], progressive_item_max["Progressive Bow"])
# Highlight 'bombs' if we received any bomb upgrades in bombless start.
# In race mode, we'll just assume bombless start for simplicity.
if tracker_data.get_slot_data(team, player).get("bombless_start", True):
inventory["Bombs"] = sum(count for item, count in inventory.items() if item.startswith("Bomb Upgrade"))
else:
inventory["Bombs"] = 1
# Triforce item if we meet goal.
if tracker_data.get_room_client_statuses()[team, player] == ClientStatus.CLIENT_GOAL:
inventory["Triforce"] = 1
def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_trackers: List[str]):
# Helper objects.
alttp_id_lookup = tracker_data.item_name_to_id["A Link to the Past"]
inventories: Dict[Tuple[int, int], Counter[str]] = {
(team, player): collections.Counter({
tracker_data.item_id_to_name["A Link to the Past"][code]: count
for code, count in tracker_data.get_player_inventory_counts(team, player).items()
})
for team, players in tracker_data.get_all_players().items()
for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past"
}
multi_items = {
alttp_id_lookup[name]
for name in ("Progressive Sword", "Progressive Bow", "Bottle", "Progressive Glove", "Triforce Piece")
}
links = {
"Bow": "Progressive Bow",
"Silver Arrows": "Progressive Bow",
"Silver Bow": "Progressive Bow",
"Progressive Bow (Alt)": "Progressive Bow",
"Bottle (Red Potion)": "Bottle",
"Bottle (Green Potion)": "Bottle",
"Bottle (Blue Potion)": "Bottle",
"Bottle (Fairy)": "Bottle",
"Bottle (Bee)": "Bottle",
"Bottle (Good Bee)": "Bottle",
"Fighter Sword": "Progressive Sword",
"Master Sword": "Progressive Sword",
"Tempered Sword": "Progressive Sword",
"Golden Sword": "Progressive Sword",
"Power Glove": "Progressive Glove",
"Titans Mitts": "Progressive Glove",
}
links = {alttp_id_lookup[key]: alttp_id_lookup[value] for key, value in links.items()}
levels = {
"Fighter Sword": 1,
"Master Sword": 2,
"Tempered Sword": 3,
"Golden Sword": 4,
"Power Glove": 1,
"Titans Mitts": 2,
"Bow": 1,
"Silver Bow": 2,
"Triforce Piece": 90,
}
tracking_names = [
"Progressive Sword", "Progressive Bow", "Book of Mudora", "Hammer", "Hookshot", "Magic Mirror", "Flute",
"Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Blue Boomerang", "Red Boomerang",
"Bug Catching Net", "Cape", "Shovel", "Lamp", "Mushroom", "Magic Powder", "Cane of Somaria",
"Cane of Byrna", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Bottle", "Triforce Piece", "Triforce",
]
default_locations = {
"Light World": {
1572864, 1572865, 60034, 1572867, 1572868, 60037, 1572869, 1572866, 60040, 59788, 60046, 60175,
1572880, 60049, 60178, 1572883, 60052, 60181, 1572885, 60055, 60184, 191256, 60058, 60187, 1572884,
1572886, 1572887, 1572906, 60202, 60205, 59824, 166320, 1010170, 60208, 60211, 60214, 60217, 59836,
60220, 60223, 59839, 1573184, 60226, 975299, 1573188, 1573189, 188229, 60229, 60232, 1573193,
1573194, 60235, 1573187, 59845, 59854, 211407, 60238, 59857, 1573185, 1573186, 1572882, 212328,
59881, 59761, 59890, 59770, 193020, 212605
},
"Dark World": {
59776, 59779, 975237, 1572870, 60043, 1572881, 60190, 60193, 60196, 60199, 60840, 1573190, 209095,
1573192, 1573191, 60241, 60244, 60247, 60250, 59884, 59887, 60019, 60022, 60028, 60031
},
"Desert Palace": {1573216, 59842, 59851, 59791, 1573201, 59830},
"Eastern Palace": {1573200, 59827, 59893, 59767, 59833, 59773},
"Hyrule Castle": {60256, 60259, 60169, 60172, 59758, 59764, 60025, 60253},
"Agahnims Tower": {60082, 60085},
"Tower of Hera": {1573218, 59878, 59821, 1573202, 59896, 59899},
"Swamp Palace": {60064, 60067, 60070, 59782, 59785, 60073, 60076, 60079, 1573204, 60061},
"Thieves Town": {59905, 59908, 59911, 59914, 59917, 59920, 59923, 1573206},
"Skull Woods": {59809, 59902, 59848, 59794, 1573205, 59800, 59803, 59806},
"Ice Palace": {59872, 59875, 59812, 59818, 59860, 59797, 1573207, 59869},
"Misery Mire": {60001, 60004, 60007, 60010, 60013, 1573208, 59866, 59998},
"Turtle Rock": {59938, 59941, 59944, 1573209, 59947, 59950, 59953, 59956, 59926, 59929, 59932, 59935},
"Palace of Darkness": {
59968, 59971, 59974, 59977, 59980, 59983, 59986, 1573203, 59989, 59959, 59992, 59962, 59995,
59965
},
"Ganons Tower": {
60160, 60163, 60166, 60088, 60091, 60094, 60097, 60100, 60103, 60106, 60109, 60112, 60115, 60118,
60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157
},
"Total": set()
}
key_only_locations = {
"Light World": set(),
"Dark World": set(),
"Desert Palace": {0x140031, 0x14002b, 0x140061, 0x140028},
"Eastern Palace": {0x14005b, 0x140049},
"Hyrule Castle": {0x140037, 0x140034, 0x14000d, 0x14003d},
"Agahnims Tower": {0x140061, 0x140052},
"Tower of Hera": set(),
"Swamp Palace": {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a},
"Thieves Town": {0x14005e, 0x14004f},
"Skull Woods": {0x14002e, 0x14001c},
"Ice Palace": {0x140004, 0x140022, 0x140025, 0x140046},
"Misery Mire": {0x140055, 0x14004c, 0x140064},
"Turtle Rock": {0x140058, 0x140007},
"Palace of Darkness": set(),
"Ganons Tower": {0x140040, 0x140043, 0x14003a, 0x14001f},
"Total": set()
}
location_to_area = {}
for area, locations in default_locations.items():
for location in locations:
location_to_area[location] = area
for area, locations in key_only_locations.items():
for location in locations:
location_to_area[location] = area
# Translate non-progression items to progression items for tracker simplicity.
for (team, player), inventory in inventories.items():
prepare_inventories(team, player, inventory, tracker_data)
checks_in_area = {area: len(checks) for area, checks in default_locations.items()}
checks_in_area["Total"] = 216
ordered_areas = (
"Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace",
"Tower of Hera", "Palace of Darkness", "Swamp Palace", "Skull Woods", "Thieves Town", "Ice Palace",
"Misery Mire", "Turtle Rock", "Ganons Tower", "Total"
)
player_checks_in_area = {
regions: Dict[Tuple[int, int], Dict[str, RegionCounts]] = {
(team, player): {
area_name: len(tracker_data._multidata["checks_in_area"][player][area_name])
if area_name != "Total" else tracker_data._multidata["checks_in_area"][player]["Total"]
for area_name in ordered_areas
region_name: RegionCounts(
total=len(tracker_data._multidata["checks_in_area"][player][region_name]),
checked=sum(
1 for location in tracker_data._multidata["checks_in_area"][player][region_name]
if location in tracker_data.get_player_checked_locations(team, player)
),
)
for region_name in known_regions
}
for team, players in tracker_data.get_all_slots().items()
for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
for team, players in tracker_data.get_all_players().items()
for player in players if tracker_data.get_slot_info(team, player).game == "A Link to the Past"
}
tracking_ids = []
for item in tracking_names:
tracking_ids.append(alttp_id_lookup[item])
# Can't wait to get this into the apworld. Oof.
from worlds.alttp import Items
small_key_ids = {}
big_key_ids = {}
ids_small_key = {}
ids_big_key = {}
for item_name, data in Items.item_table.items():
if "Key" in item_name:
area = item_name.split("(")[1][:-1]
if "Small" in item_name:
small_key_ids[area] = data[2]
ids_small_key[data[2]] = area
else:
big_key_ids[area] = data[2]
ids_big_key[data[2]] = area
def _get_location_table(checks_table: dict) -> dict:
loc_to_area = {}
for area, locations in checks_table.items():
if area == "Total":
continue
for location in locations:
loc_to_area[location] = area
return loc_to_area
player_location_to_area = {
(team, player): _get_location_table(tracker_data._multidata["checks_in_area"][player])
for team, players in tracker_data.get_all_slots().items()
for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
}
checks_done: Dict[TeamPlayer, Dict[str: int]] = {
(team, player): {location_name: 0 for location_name in default_locations}
for team, players in tracker_data.get_all_slots().items()
for player in players
if tracker_data.get_slot_info(team, player).type != SlotType.group and
tracker_data.get_slot_info(team, player).game == "A Link to the Past"
}
inventories: Dict[TeamPlayer, Dict[int, int]] = {}
player_big_key_locations = {(player): set() for player in tracker_data.get_all_slots()[0]}
player_small_key_locations = {player: set() for player in tracker_data.get_all_slots()[0]}
group_big_key_locations = set()
group_key_locations = set()
for (team, player), locations in checks_done.items():
# Check if game complete.
if tracker_data.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL:
inventories[team, player][106] = 1 # Triforce
# Count number of locations checked.
for location in tracker_data.get_player_checked_locations(team, player):
checks_done[team, player][player_location_to_area[team, player][location]] += 1
checks_done[team, player]["Total"] += 1
# Count keys.
for location, (item, receiving, _) in tracker_data.get_player_locations(team, player).items():
if item in ids_big_key:
player_big_key_locations[receiving].add(ids_big_key[item])
elif item in ids_small_key:
player_small_key_locations[receiving].add(ids_small_key[item])
# Iterate over received items and build inventory/key counts.
inventories[team, player] = collections.Counter()
for network_item in tracker_data.get_player_received_items(team, player):
target_item = links.get(network_item.item, network_item.item)
if network_item.item in levels: # non-progressive
inventories[team, player][target_item] = (max(inventories[team, player][target_item], levels[network_item.item]))
else:
inventories[team, player][target_item] += 1
group_key_locations |= player_small_key_locations[player]
group_big_key_locations |= player_big_key_locations[player]
# Get a totals count.
for player, player_regions in regions.items():
total = 0
checked = 0
for region, region_counts in player_regions.items():
total += region_counts.total
checked += region_counts.checked
regions[player]["Total"] = RegionCounts(total, checked)
return render_template(
"multitracker__ALinkToThePast.html",
@@ -682,209 +583,39 @@ if "A Link to the Past" in network_data_package["games"]:
item_id_to_name=tracker_data.item_id_to_name,
location_id_to_name=tracker_data.location_id_to_name,
inventories=inventories,
tracking_names=tracking_names,
tracking_ids=tracking_ids,
multi_items=multi_items,
checks_done=checks_done,
ordered_areas=ordered_areas,
checks_in_area=player_checks_in_area,
key_locations=group_key_locations,
big_key_locations=group_big_key_locations,
small_key_ids=small_key_ids,
big_key_ids=big_key_ids,
regions=regions,
known_regions=known_regions,
)
def render_ALinkToThePast_tracker(tracker_data: TrackerData, team: int, player: int) -> str:
# Helper objects.
alttp_id_lookup = tracker_data.item_name_to_id["A Link to the Past"]
inventory = collections.Counter({
tracker_data.item_id_to_name["A Link to the Past"][code]: count
for code, count in tracker_data.get_player_inventory_counts(team, player).items()
})
links = {
"Bow": "Progressive Bow",
"Silver Arrows": "Progressive Bow",
"Silver Bow": "Progressive Bow",
"Progressive Bow (Alt)": "Progressive Bow",
"Bottle (Red Potion)": "Bottle",
"Bottle (Green Potion)": "Bottle",
"Bottle (Blue Potion)": "Bottle",
"Bottle (Fairy)": "Bottle",
"Bottle (Bee)": "Bottle",
"Bottle (Good Bee)": "Bottle",
"Fighter Sword": "Progressive Sword",
"Master Sword": "Progressive Sword",
"Tempered Sword": "Progressive Sword",
"Golden Sword": "Progressive Sword",
"Power Glove": "Progressive Glove",
"Titans Mitts": "Progressive Glove",
}
links = {alttp_id_lookup[key]: alttp_id_lookup[value] for key, value in links.items()}
levels = {
"Fighter Sword": 1,
"Master Sword": 2,
"Tempered Sword": 3,
"Golden Sword": 4,
"Power Glove": 1,
"Titans Mitts": 2,
"Bow": 1,
"Silver Bow": 2,
"Triforce Piece": 90,
}
tracking_names = [
"Progressive Sword", "Progressive Bow", "Book of Mudora", "Hammer", "Hookshot", "Magic Mirror", "Flute",
"Pegasus Boots", "Progressive Glove", "Flippers", "Moon Pearl", "Blue Boomerang", "Red Boomerang",
"Bug Catching Net", "Cape", "Shovel", "Lamp", "Mushroom", "Magic Powder", "Cane of Somaria",
"Cane of Byrna", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Bottle", "Triforce Piece", "Triforce",
]
default_locations = {
"Light World": {
1572864, 1572865, 60034, 1572867, 1572868, 60037, 1572869, 1572866, 60040, 59788, 60046, 60175,
1572880, 60049, 60178, 1572883, 60052, 60181, 1572885, 60055, 60184, 191256, 60058, 60187, 1572884,
1572886, 1572887, 1572906, 60202, 60205, 59824, 166320, 1010170, 60208, 60211, 60214, 60217, 59836,
60220, 60223, 59839, 1573184, 60226, 975299, 1573188, 1573189, 188229, 60229, 60232, 1573193,
1573194, 60235, 1573187, 59845, 59854, 211407, 60238, 59857, 1573185, 1573186, 1572882, 212328,
59881, 59761, 59890, 59770, 193020, 212605
},
"Dark World": {
59776, 59779, 975237, 1572870, 60043, 1572881, 60190, 60193, 60196, 60199, 60840, 1573190, 209095,
1573192, 1573191, 60241, 60244, 60247, 60250, 59884, 59887, 60019, 60022, 60028, 60031
},
"Desert Palace": {1573216, 59842, 59851, 59791, 1573201, 59830},
"Eastern Palace": {1573200, 59827, 59893, 59767, 59833, 59773},
"Hyrule Castle": {60256, 60259, 60169, 60172, 59758, 59764, 60025, 60253},
"Agahnims Tower": {60082, 60085},
"Tower of Hera": {1573218, 59878, 59821, 1573202, 59896, 59899},
"Swamp Palace": {60064, 60067, 60070, 59782, 59785, 60073, 60076, 60079, 1573204, 60061},
"Thieves Town": {59905, 59908, 59911, 59914, 59917, 59920, 59923, 1573206},
"Skull Woods": {59809, 59902, 59848, 59794, 1573205, 59800, 59803, 59806},
"Ice Palace": {59872, 59875, 59812, 59818, 59860, 59797, 1573207, 59869},
"Misery Mire": {60001, 60004, 60007, 60010, 60013, 1573208, 59866, 59998},
"Turtle Rock": {59938, 59941, 59944, 1573209, 59947, 59950, 59953, 59956, 59926, 59929, 59932, 59935},
"Palace of Darkness": {
59968, 59971, 59974, 59977, 59980, 59983, 59986, 1573203, 59989, 59959, 59992, 59962, 59995,
59965
},
"Ganons Tower": {
60160, 60163, 60166, 60088, 60091, 60094, 60097, 60100, 60103, 60106, 60109, 60112, 60115, 60118,
60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157
},
"Total": set()
}
key_only_locations = {
"Light World": set(),
"Dark World": set(),
"Desert Palace": {0x140031, 0x14002b, 0x140061, 0x140028},
"Eastern Palace": {0x14005b, 0x140049},
"Hyrule Castle": {0x140037, 0x140034, 0x14000d, 0x14003d},
"Agahnims Tower": {0x140061, 0x140052},
"Tower of Hera": set(),
"Swamp Palace": {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a},
"Thieves Town": {0x14005e, 0x14004f},
"Skull Woods": {0x14002e, 0x14001c},
"Ice Palace": {0x140004, 0x140022, 0x140025, 0x140046},
"Misery Mire": {0x140055, 0x14004c, 0x140064},
"Turtle Rock": {0x140058, 0x140007},
"Palace of Darkness": set(),
"Ganons Tower": {0x140040, 0x140043, 0x14003a, 0x14001f},
"Total": set()
}
location_to_area = {}
for area, locations in default_locations.items():
for checked_location in locations:
location_to_area[checked_location] = area
for area, locations in key_only_locations.items():
for checked_location in locations:
location_to_area[checked_location] = area
# Translate non-progression items to progression items for tracker simplicity.
prepare_inventories(team, player, inventory, tracker_data)
checks_in_area = {area: len(checks) for area, checks in default_locations.items()}
checks_in_area["Total"] = 216
ordered_areas = (
"Light World", "Dark World", "Hyrule Castle", "Agahnims Tower", "Eastern Palace", "Desert Palace",
"Tower of Hera", "Palace of Darkness", "Swamp Palace", "Skull Woods", "Thieves Town", "Ice Palace",
"Misery Mire", "Turtle Rock", "Ganons Tower", "Total"
)
tracking_ids = []
for item in tracking_names:
tracking_ids.append(alttp_id_lookup[item])
# Can't wait to get this into the apworld. Oof.
from worlds.alttp import Items
small_key_ids = {}
big_key_ids = {}
ids_small_key = {}
ids_big_key = {}
for item_name, data in Items.item_table.items():
if "Key" in item_name:
area = item_name.split("(")[1][:-1]
if "Small" in item_name:
small_key_ids[area] = data[2]
ids_small_key[data[2]] = area
else:
big_key_ids[area] = data[2]
ids_big_key[data[2]] = area
inventory = collections.Counter()
checks_done = {loc_name: 0 for loc_name in default_locations}
player_big_key_locations = set()
player_small_key_locations = set()
player_locations = tracker_data.get_player_locations(team, player)
for checked_location in tracker_data.get_player_checked_locations(team, player):
if checked_location in player_locations:
area_name = location_to_area.get(checked_location, None)
if area_name:
checks_done[area_name] += 1
checks_done["Total"] += 1
for received_item in tracker_data.get_player_received_items(team, player):
target_item = links.get(received_item.item, received_item.item)
if received_item.item in levels: # non-progressive
inventory[target_item] = max(inventory[target_item], levels[received_item.item])
else:
inventory[target_item] += 1
for location, (item_id, _, _) in player_locations.items():
if item_id in ids_big_key:
player_big_key_locations.add(ids_big_key[item_id])
elif item_id in ids_small_key:
player_small_key_locations.add(ids_small_key[item_id])
# Note the presence of the triforce item
if tracker_data.get_player_client_status(team, player) == ClientStatus.CLIENT_GOAL:
inventory[106] = 1 # Triforce
# Progressive items need special handling for icons and class
progressive_items = {
"Progressive Sword": 94,
"Progressive Glove": 97,
"Progressive Bow": 100,
"Progressive Mail": 96,
"Progressive Shield": 95,
}
progressive_names = {
"Progressive Sword": [None, "Fighter Sword", "Master Sword", "Tempered Sword", "Golden Sword"],
"Progressive Glove": [None, "Power Glove", "Titan Mitts"],
"Progressive Bow": [None, "Bow", "Silver Bow"],
"Progressive Mail": ["Green Mail", "Blue Mail", "Red Mail"],
"Progressive Shield": [None, "Blue Shield", "Red Shield", "Mirror Shield"]
regions = {
region_name: {
"checked": sum(
1 for location in tracker_data._multidata["checks_in_area"][player][region_name]
if location in tracker_data.get_player_checked_locations(team, player)
),
"locations": [
(
tracker_data.location_id_to_name["A Link to the Past"][location],
location in tracker_data.get_player_checked_locations(team, player)
)
for location in tracker_data._multidata["checks_in_area"][player][region_name]
],
}
for region_name in known_regions
}
# Determine which icon to use
display_data = {}
for item_name, item_id in progressive_items.items():
level = min(inventory[item_id], len(progressive_names[item_name]) - 1)
display_name = progressive_names[item_name][level]
acquired = True
if not display_name:
acquired = False
display_name = progressive_names[item_name][level + 1]
base_name = item_name.split(maxsplit=1)[1].lower()
display_data[base_name + "_acquired"] = acquired
display_data[base_name + "_icon"] = display_name
# The single player tracker doesn't care about overworld, underworld, and total checks. Maybe it should?
sp_areas = ordered_areas[2:15]
# Sort locations in regions by name
for region in regions:
regions[region]["locations"].sort()
return render_template(
template_name_or_list="tracker__ALinkToThePast.html",
@@ -893,15 +624,8 @@ if "A Link to the Past" in network_data_package["games"]:
player=player,
inventory=inventory,
player_name=tracker_data.get_player_name(team, player),
checks_done=checks_done,
checks_in_area=checks_in_area,
acquired_items={tracker_data.item_id_to_name["A Link to the Past"][id] for id in inventory},
sp_areas=sp_areas,
small_key_ids=small_key_ids,
key_locations=player_small_key_locations,
big_key_ids=big_key_ids,
big_key_locations=player_big_key_locations,
**display_data,
regions=regions,
known_regions=known_regions,
)
_multiworld_trackers["A Link to the Past"] = render_ALinkToThePast_multiworld_tracker

View File

@@ -63,12 +63,13 @@ def process_multidata(compressed_multidata, files={}):
game_data = games_package_schema.validate(game_data)
game_data = {key: value for key, value in sorted(game_data.items())}
game_data["checksum"] = data_package_checksum(game_data)
game_data_package = GameDataPackage(checksum=game_data["checksum"],
data=pickle.dumps(game_data))
if original_checksum != game_data["checksum"]:
raise Exception(f"Original checksum {original_checksum} != "
f"calculated checksum {game_data['checksum']} "
f"for game {game}.")
game_data_package = GameDataPackage(checksum=game_data["checksum"],
data=pickle.dumps(game_data))
decompressed_multidata["datapackage"][game] = {
"version": game_data.get("version", 0),
"checksum": game_data["checksum"],
@@ -192,6 +193,8 @@ def uploads():
res = upload_zip_to_db(zfile)
except VersionException:
flash(f"Could not load multidata. Wrong Version detected.")
except Exception as e:
flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})")
else:
if res is str:
return res

View File

@@ -27,14 +27,9 @@ local mmbn3Socket = nil
local frame = 0
-- States
local ITEMSTATE_NONINITIALIZED = "Game Not Yet Started" -- Game has not yet started
local ITEMSTATE_NONITEM = "Non-Itemable State" -- Do not send item now. RAM is not capable of holding
local ITEMSTATE_IDLE = "Item State Ready" -- Ready for the next item if there are any
local ITEMSTATE_SENT = "Item Sent Not Claimed" -- The ItemBit is set, but the dialog has not been closed yet
local itemState = ITEMSTATE_NONINITIALIZED
local itemQueued = nil
local itemQueueCounter = 120
local itemState = ITEMSTATE_NONITEM
local debugEnabled = false
local game_complete = false
@@ -104,21 +99,24 @@ end
local IsInBattle = function()
return memory.read_u8(0x020097F8) == 0x08
end
local IsItemQueued = function()
return memory.read_u8(0x2000224) == 0x01
end
-- This function actually determines when you're on ANY full-screen menu (navi cust, link battle, etc.) but we
-- don't want to check any locations there either so it's fine.
local IsOnTitle = function()
return bit.band(memory.read_u8(0x020097F8),0x04) == 0
end
local IsItemable = function()
return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle() and not IsItemQueued()
return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle()
end
local is_game_complete = function()
if IsOnTitle() or itemState == ITEMSTATE_NONINITIALIZED then return game_complete end
-- If the Cannary Byte is 0xFF, then the save RAM is untrustworthy
if memory.read_u8(canary_byte) == 0xFF then
return game_complete
end
-- If on the title screen don't read RAM, RAM can't be trusted yet
if IsOnTitle() then return game_complete end
-- If the game is already marked complete, do not read memory
if game_complete then return true end
@@ -177,14 +175,6 @@ local Check_Progressive_Undernet_ID = function()
end
return 9
end
local GenerateTextBytes = function(message)
bytes = {}
for i = 1, #message do
local c = message:sub(i,i)
table.insert(bytes, charDict[c])
end
return bytes
end
-- Item Message Generation functions
local Next_Progressive_Undernet_ID = function(index)
@@ -196,150 +186,6 @@ local Next_Progressive_Undernet_ID = function(index)
item_index=ordered_IDs[index]
return item_index
end
local Extra_Progressive_Undernet = function()
fragBytes = int32ToByteList_le(20)
bytes = {
0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF
}
bytes = TableConcat(bytes, GenerateTextBytes("The extra data\ndecompiles into:\n\"20 BugFrags\"!!"))
return bytes
end
local GenerateChipGet = function(chip, code, amt)
chipBytes = int16ToByteList_le(chip)
bytes = {
0xF6, 0x10, chipBytes[1], chipBytes[2], code, amt,
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['c'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'],
}
if chip < 256 then
bytes = TableConcat(bytes, {
charDict['\"'], 0xF9,0x00,chipBytes[1],0x01,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!']
})
else
bytes = TableConcat(bytes, {
charDict['\"'], 0xF9,0x00,chipBytes[1],0x02,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!']
})
end
return bytes
end
local GenerateKeyItemGet = function(item, amt)
bytes = {
0xF6, 0x00, item, amt,
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'],
charDict['\"'], 0xF9, 0x00, item, 0x00, charDict['\"'],charDict['!'],charDict['!']
}
return bytes
end
local GenerateSubChipGet = function(subchip, amt)
-- SubChips have an extra bit of trouble. If you have too many, they're supposed to skip to another text bank that doesn't give you the item
-- Instead, I'm going to just let it get eaten
bytes = {
0xF6, 0x20, subchip, amt, 0xFF, 0xFF, 0xFF,
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'],
charDict['S'], charDict['u'], charDict['b'], charDict['C'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'],
charDict['\"'], 0xF9, 0x00, subchip, 0x00, charDict['\"'],charDict['!'],charDict['!']
}
return bytes
end
local GenerateZennyGet = function(amt)
zennyBytes = int32ToByteList_le(amt)
bytes = {
0xF6, 0x30, zennyBytes[1], zennyBytes[2], zennyBytes[3], zennyBytes[4], 0xFF, 0xFF, 0xFF,
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], charDict['\"']
}
-- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it
zennyStr = tostring(amt)
for i = 1, #zennyStr do
local c = zennyStr:sub(i,i)
table.insert(bytes, charDict[c])
end
bytes = TableConcat(bytes, {
charDict[' '], charDict['Z'], charDict['e'], charDict['n'], charDict['n'], charDict['y'], charDict['s'], charDict['\"'],charDict['!'],charDict['!']
})
return bytes
end
local GenerateProgramGet = function(program, color, amt)
bytes = {
0xF6, 0x40, (program * 4), amt, color,
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['N'], charDict['a'], charDict['v'], charDict['i'], charDict['\n'],
charDict['C'], charDict['u'], charDict['s'], charDict['t'], charDict['o'], charDict['m'], charDict['i'], charDict['z'], charDict['e'], charDict['r'], charDict[' '], charDict['P'], charDict['r'], charDict['o'], charDict['g'], charDict['r'], charDict['a'], charDict['m'], charDict[':'], charDict['\n'],
charDict['\"'], 0xF9, 0x00, program, 0x05, charDict['\"'],charDict['!'],charDict['!']
}
return bytes
end
local GenerateBugfragGet = function(amt)
fragBytes = int32ToByteList_le(amt)
bytes = {
0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF,
charDict['G'], charDict['o'], charDict['t'], charDict[':'], charDict['\n'], charDict['\"']
}
-- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it
bugFragStr = tostring(amt)
for i = 1, #bugFragStr do
local c = bugFragStr:sub(i,i)
table.insert(bytes, charDict[c])
end
bytes = TableConcat(bytes, {
charDict[' '], charDict['B'], charDict['u'], charDict['g'], charDict['F'], charDict['r'], charDict['a'], charDict['g'], charDict['s'], charDict['\"'],charDict['!'],charDict['!']
})
return bytes
end
local GenerateGetMessageFromItem = function(item)
--Special case for progressive undernet
if item["type"] == "undernet" then
undernet_id = Check_Progressive_Undernet_ID()
if undernet_id > 8 then
return Extra_Progressive_Undernet()
end
return GenerateKeyItemGet(Next_Progressive_Undernet_ID(undernet_id),1)
elseif item["type"] == "chip" then
return GenerateChipGet(item["itemID"], item["subItemID"], item["count"])
elseif item["type"] == "key" then
return GenerateKeyItemGet(item["itemID"], item["count"])
elseif item["type"] == "subchip" then
return GenerateSubChipGet(item["itemID"], item["count"])
elseif item["type"] == "zenny" then
return GenerateZennyGet(item["count"])
elseif item["type"] == "program" then
return GenerateProgramGet(item["itemID"], item["subItemID"], item["count"])
elseif item["type"] == "bugfrag" then
return GenerateBugfragGet(item["count"])
end
return GenerateTextBytes("Empty Message")
end
local GetMessage = function(item)
startBytes = {0x02, 0x00}
playerLockBytes = {0xF8,0x00, 0xF8, 0x10}
msgOpenBytes = {0xF1, 0x02}
textBytes = GenerateTextBytes("Receiving\ndata from\n"..item["sender"]..".")
dotdotWaitBytes = {0xEA,0x00,0x0A,0x00,0x4D,0xEA,0x00,0x0A,0x00,0x4D}
continueBytes = {0xEB, 0xE9}
-- continueBytes = {0xE9}
playReceiveAnimationBytes = {0xF8,0x04,0x18}
chipGiveBytes = GenerateGetMessageFromItem(item)
playerFinishBytes = {0xF8, 0x0C}
playerUnlockBytes={0xEB, 0xF8, 0x08}
-- playerUnlockBytes={0xF8, 0x08}
endMessageBytes = {0xF8, 0x10, 0xE7}
bytes = {}
bytes = TableConcat(bytes,startBytes)
bytes = TableConcat(bytes,playerLockBytes)
bytes = TableConcat(bytes,msgOpenBytes)
bytes = TableConcat(bytes,textBytes)
bytes = TableConcat(bytes,dotdotWaitBytes)
bytes = TableConcat(bytes,continueBytes)
bytes = TableConcat(bytes,playReceiveAnimationBytes)
bytes = TableConcat(bytes,chipGiveBytes)
bytes = TableConcat(bytes,playerFinishBytes)
bytes = TableConcat(bytes,playerUnlockBytes)
bytes = TableConcat(bytes,endMessageBytes)
return bytes
end
local getChipCodeIndex = function(chip_id, chip_code)
chipCodeArrayStartAddress = 0x8011510 + (0x20 * chip_id)
@@ -353,6 +199,10 @@ local getChipCodeIndex = function(chip_id, chip_code)
end
local getProgramColorIndex = function(program_id, program_color)
-- For whatever reason, OilBody (ID 24) does not follow the rules and should be color index 3
if program_id == 24 then
return 3
end
-- The general case, most programs use white pink or yellow. This is the values the enums already have
if program_id >= 20 and program_id <= 47 then
return program_color-1
@@ -401,11 +251,11 @@ local changeZenny = function(val)
return 0
end
if memory.read_u32_le(0x20018F4) <= math.abs(tonumber(val)) and tonumber(val) < 0 then
memory.write_u32_le(0x20018f4, 0)
memory.write_u32_le(0x20018F4, 0)
val = 0
return "empty"
end
memory.write_u32_le(0x20018f4, memory.read_u32_le(0x20018F4) + tonumber(val))
memory.write_u32_le(0x20018F4, memory.read_u32_le(0x20018F4) + tonumber(val))
if memory.read_u32_le(0x20018F4) > 999999 then
memory.write_u32_le(0x20018F4, 999999)
end
@@ -417,30 +267,17 @@ local changeFrags = function(val)
return 0
end
if memory.read_u16_le(0x20018F8) <= math.abs(tonumber(val)) and tonumber(val) < 0 then
memory.write_u16_le(0x20018f8, 0)
memory.write_u16_le(0x20018F8, 0)
val = 0
return "empty"
end
memory.write_u16_le(0x20018f8, memory.read_u16_le(0x20018F8) + tonumber(val))
memory.write_u16_le(0x20018F8, memory.read_u16_le(0x20018F8) + tonumber(val))
if memory.read_u16_le(0x20018F8) > 9999 then
memory.write_u16_le(0x20018F8, 9999)
end
return val
end
-- Fix Health Pools
local fix_hp = function()
-- Current Health fix
if IsInBattle() and not (memory.read_u16_le(0x20018A0) == memory.read_u16_le(0x2037294)) then
memory.write_u16_le(0x20018A0, memory.read_u16_le(0x2037294))
end
-- Max Health Fix
if IsInBattle() and not (memory.read_u16_le(0x20018A2) == memory.read_u16_le(0x2037296)) then
memory.write_u16_le(0x20018A2, memory.read_u16_le(0x2037296))
end
end
local changeRegMemory = function(amt)
regMemoryAddress = 0x02001897
currentRegMem = memory.read_u8(regMemoryAddress)
@@ -448,34 +285,18 @@ local changeRegMemory = function(amt)
end
local changeMaxHealth = function(val)
fix_hp()
if val == nil then
fix_hp()
if val == nil then
return 0
end
if math.abs(tonumber(val)) >= memory.read_u16_le(0x20018A2) and tonumber(val) < 0 then
memory.write_u16_le(0x20018A2, 0)
if IsInBattle() then
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
if memory.read_u16_le(0x2037296) >= memory.read_u16_le(0x20018A2) then
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
end
end
fix_hp()
return "lethal"
end
memory.write_u16_le(0x20018A2, memory.read_u16_le(0x20018A2) + tonumber(val))
if memory.read_u16_le(0x20018A2) > 9999 then
memory.write_u16_le(0x20018A2, 9999)
end
if IsInBattle() then
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
end
fix_hp()
return val
end
local SendItem = function(item)
local SendItemToGame = function(item)
if item["type"] == "undernet" then
undernet_id = Check_Progressive_Undernet_ID()
if undernet_id > 8 then
@@ -553,13 +374,6 @@ local OpenShortcuts = function()
end
end
local RestoreItemRam = function()
if backup_bytes ~= nil then
memory.write_bytes_as_array(0x203fe10, backup_bytes)
end
backup_bytes = nil
end
local process_block = function(block)
-- Sometimes the block is nothing, if this is the case then quietly stop processing
if block == nil then
@@ -574,14 +388,7 @@ local process_block = function(block)
end
local itemStateMachineProcess = function()
if itemState == ITEMSTATE_NONINITIALIZED then
itemQueueCounter = 120
-- Only exit this state the first time a dialog window pops up. This way we know for sure that we're ready to receive
if not IsInMenu() and (IsInDialog() or IsInTransition()) then
itemState = ITEMSTATE_NONITEM
end
elseif itemState == ITEMSTATE_NONITEM then
itemQueueCounter = 120
if itemState == ITEMSTATE_NONITEM then
-- Always attempt to restore the previously stored memory in this state
-- Exit this state whenever the game is in an itemable status
if IsItemable() then
@@ -592,26 +399,11 @@ local itemStateMachineProcess = function()
if not IsItemable() then
itemState = ITEMSTATE_NONITEM
end
if itemQueueCounter == 0 then
if #itemsReceived > loadItemIndexFromRAM() and not IsItemQueued() then
itemQueued = itemsReceived[loadItemIndexFromRAM()+1]
SendItem(itemQueued)
itemState = ITEMSTATE_SENT
end
else
itemQueueCounter = itemQueueCounter - 1
end
elseif itemState == ITEMSTATE_SENT then
-- Once the item is sent, wait for the dialog to close. Then clear the item bit and be ready for the next item.
if IsInTransition() or IsInMenu() or IsOnTitle() then
itemState = ITEMSTATE_NONITEM
itemQueued = nil
RestoreItemRam()
elseif not IsInDialog() then
itemState = ITEMSTATE_IDLE
if #itemsReceived > loadItemIndexFromRAM() then
itemQueued = itemsReceived[loadItemIndexFromRAM()+1]
SendItemToGame(itemQueued)
saveItemIndexToRAM(itemQueued["itemIndex"])
itemQueued = nil
RestoreItemRam()
itemState = ITEMSTATE_NONITEM
end
end
end
@@ -702,18 +494,8 @@ function main()
-- Handle the debug data display
gui.cleartext()
if debugEnabled then
-- gui.text(0,0,"Item Queued: "..tostring(IsItemQueued()))
-- gui.text(0,16,"In Battle: "..tostring(IsInBattle()))
-- gui.text(0,32,"In Dialog: "..tostring(IsInDialog()))
-- gui.text(0,48,"In Menu: "..tostring(IsInMenu()))
gui.text(0,48,"Item Wait Time: "..tostring(itemQueueCounter))
gui.text(0,64,itemState)
if itemQueued == nil then
gui.text(0,80,"No item queued")
else
gui.text(0,80,itemQueued["type"].." "..itemQueued["itemID"])
end
gui.text(0,96,"Item Index: "..loadItemIndexFromRAM())
gui.text(0,0,itemState)
gui.text(0,16,"Item Index: "..loadItemIndexFromRAM())
end
emu.frameadvance()

View File

@@ -35,7 +35,7 @@
/worlds/celeste64/ @PoryGone
# ChecksFinder
/worlds/checksfinder/ @jonloveslegos
/worlds/checksfinder/ @SunCatMC
# Clique
/worlds/clique/ @ThePhar
@@ -92,6 +92,9 @@
/worlds/lufia2ac/ @el-u
/worlds/lufia2ac/docs/ @wordfcuk @el-u
# Mario & Luigi: Superstar Saga
/worlds/mlss/ @jamesbrq
# Meritous
/worlds/meritous/ @FelicitusNeko

View File

@@ -17,6 +17,15 @@
* Use type annotations where possible for function signatures and class members.
* Use type annotations where appropriate for local variables (e.g. `var: List[int] = []`, or when the
type is hard or impossible to deduce.) Clear annotations help developers look up and validate API calls.
* If a line ends with an open bracket/brace/parentheses, the matching closing bracket should be at the
beginning of a line at the same indentation as the beginning of the line with the open bracket.
```python
stuff = {
x: y
for x, y in thing
if y > 2
}
```
* New classes, attributes, and methods in core code should have docstrings that follow
[reST style](https://peps.python.org/pep-0287/).
* Worlds that do not follow PEP8 should still have a consistent style across its files to make reading easier.

View File

@@ -1,4 +1,4 @@
# This is a sample configuration for the Web host.
# This is a sample configuration for the Web host.
# If you wish to change any of these, rename this file to config.yaml
# Default values are shown here. Uncomment and change the values as desired.
@@ -25,7 +25,7 @@
# Secret key used to determine important things like cookie authentication of room/seed page ownership.
# If you wish to deploy, uncomment the following line and set it to something not easily guessable.
# SECRET_KEY: "Your secret key here"
# SECRET_KEY: "Your secret key here"
# TODO
#JOB_THRESHOLD: 2
@@ -38,15 +38,16 @@
# provider: "sqlite"
# filename: "ap.db3" # This MUST be the ABSOLUTE PATH to the file.
# create_db: true
# Maximum number of players that are allowed to be rolled on the server. After this limit, one should roll locally and upload the results.
#MAX_ROLL: 20
# TODO
#CACHE_TYPE: "simple"
# TODO
#JSON_AS_ASCII: false
# Host Address. This is the address encoded into the patch that will be used for client auto-connect.
#HOST_ADDRESS: archipelago.gg
# Asset redistribution rights. If true, the host affirms they have been given explicit permission to redistribute
# the proprietary assets in WebHostLib
#ASSET_RIGHTS: false

View File

@@ -200,7 +200,7 @@ class Group:
def _dump_value(cls, value: Any, f: TextIO, indent: str) -> None:
"""Write a single yaml line to f"""
from Utils import dump, Dumper as BaseDumper
yaml_line: str = dump(value, Dumper=cast(BaseDumper, cls._dumper))
yaml_line: str = dump(value, Dumper=cast(BaseDumper, cls._dumper), width=2**31-1)
assert yaml_line.count("\n") == 1, f"Unexpected input for yaml dumper: {value}"
f.write(f"{indent}{yaml_line}")
@@ -671,7 +671,6 @@ class GeneratorOptions(Group):
weights_file_path: WeightsFilePath = WeightsFilePath("weights.yaml")
meta_file_path: MetaFilePath = MetaFilePath("meta.yaml")
spoiler: Spoiler = Spoiler(3)
glitch_triforce_room: GlitchTriforceRoom = GlitchTriforceRoom(1) # why is this here?
race: Race = Race(0)
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")

View File

@@ -21,7 +21,7 @@ from pathlib import Path
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
try:
requirement = 'cx-Freeze>=6.15.10'
requirement = 'cx-Freeze>=7.0.0'
import pkg_resources
try:
pkg_resources.require(requirement)
@@ -228,8 +228,8 @@ class BuildCommand(setuptools.command.build.build):
# Override cx_Freeze's build_exe command for pre and post build steps
class BuildExeCommand(cx_Freeze.command.build_exe.BuildEXE):
user_options = cx_Freeze.command.build_exe.BuildEXE.user_options + [
class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
user_options = cx_Freeze.command.build_exe.build_exe.user_options + [
('yes', 'y', 'Answer "yes" to all questions.'),
('extra-data=', None, 'Additional files to add.'),
]

View File

@@ -1,7 +1,7 @@
from argparse import Namespace
from typing import List, Optional, Tuple, Type, Union
from BaseClasses import CollectionState, MultiWorld
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
from worlds.AutoWorld import World, call_all
gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
@@ -17,19 +17,21 @@ def setup_solo_multiworld(
:param steps: The gen steps that should be called on the generated multiworld before returning. Default calls
steps through pre_fill
:param seed: The seed to be used when creating this multiworld
:return: The generated multiworld
"""
return setup_multiworld(world_type, steps, seed)
def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps,
seed: Optional[int] = None) -> MultiWorld:
seed: Optional[int] = None) -> MultiWorld:
"""
Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and
calling the provided gen steps.
:param worlds: type/s of worlds to generate a multiworld for
:param steps: gen steps that should be called before returning. Default calls through pre_fill
:param worlds: Type/s of worlds to generate a multiworld for
:param steps: Gen steps that should be called before returning. Default calls through pre_fill
:param seed: The seed to be used when creating this multiworld
:return: The generated multiworld
"""
if not isinstance(worlds, list):
worlds = [worlds]
@@ -49,3 +51,59 @@ def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple
for step in steps:
call_all(multiworld, step)
return multiworld
class TestWorld(World):
game = f"Test Game"
item_name_to_id = {}
location_name_to_id = {}
hidden = True
def generate_test_multiworld(players: int = 1) -> MultiWorld:
"""
Generates a multiworld using a special Test Case World class, and seed of 0.
:param players: Number of players to generate the multiworld for
:return: The generated test multiworld
"""
multiworld = setup_multiworld([TestWorld] * players, seed=0)
multiworld.regions += [Region("Menu", player_id + 1, multiworld) for player_id in range(players)]
return multiworld
def generate_locations(count: int, player_id: int, region: Region, address: Optional[int] = None,
tag: str = "") -> List[Location]:
"""
Generates the specified amount of locations for the player and adds them to the specified region.
:param count: Number of locations to create
:param player_id: ID of the player to create the locations for
:param address: Address for the specified locations. They will all share the same address if multiple are created
:param region: Parent region to add these locations to
:param tag: Tag to add to the name of the generated locations
:return: List containing the created locations
"""
prefix = f"player{player_id}{tag}_location"
locations = [Location(player_id, f"{prefix}{i}", address, region) for i in range(count)]
region.locations += locations
return locations
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
"""
Generates the specified amount of items for the target player.
:param count: The amount of items to create
:param player_id: ID of the player to create the items for
:param advancement: Whether the created items should be advancement
:param code: The code the items should be created with
:return: List containing the created items
"""
item_type = "prog" if advancement else ""
classification = ItemClassification.progression if advancement else ItemClassification.filler
items = [Item(f"player{player_id}_{item_type}item{i}", classification, code, player_id) for i in range(count)]
return items

View File

@@ -1,41 +1,15 @@
from typing import List, Iterable
import unittest
import Options
from Options import Accessibility
from worlds.AutoWorld import World
from test.general import generate_items, generate_locations, generate_test_multiworld
from Fill import FillError, balance_multiworld_progression, fill_restrictive, \
distribute_early_items, distribute_items_restrictive
from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item, Location, \
ItemClassification, CollectionState
ItemClassification
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
def generate_multiworld(players: int = 1) -> MultiWorld:
multiworld = MultiWorld(players)
multiworld.set_seed(0)
multiworld.player_name = {}
multiworld.state = CollectionState(multiworld)
for i in range(players):
player_id = i+1
world = World(multiworld, player_id)
multiworld.game[player_id] = f"Game {player_id}"
multiworld.worlds[player_id] = world
multiworld.player_name[player_id] = "Test Player " + str(player_id)
region = Region("Menu", player_id, multiworld, "Menu Region Hint")
multiworld.regions.append(region)
for option_key, option in Options.PerGameCommonOptions.type_hints.items():
if hasattr(multiworld, option_key):
getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
else:
setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))})
# TODO - remove this loop once all worlds use options dataclasses
world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
for option_key in world.options_dataclass.type_hints})
return multiworld
class PlayerDefinition(object):
multiworld: MultiWorld
id: int
@@ -55,12 +29,12 @@ class PlayerDefinition(object):
self.regions = [menu]
def generate_region(self, parent: Region, size: int, access_rule: CollectionRule = lambda state: True) -> Region:
region_tag = "_region" + str(len(self.regions))
region_name = "player" + str(self.id) + region_tag
region = Region("player" + str(self.id) + region_tag, self.id, self.multiworld)
self.locations += generate_locations(size, self.id, None, region, region_tag)
region_tag = f"_region{len(self.regions)}"
region_name = f"player{self.id}{region_tag}"
region = Region(f"player{self.id}{region_tag}", self.id, self.multiworld)
self.locations += generate_locations(size, self.id, region, None, region_tag)
entrance = Entrance(self.id, region_name + "_entrance", parent)
entrance = Entrance(self.id, f"{region_name}_entrance", parent)
parent.exits.append(entrance)
entrance.connect(region)
entrance.access_rule = access_rule
@@ -94,7 +68,7 @@ def region_contains(region: Region, item: Item) -> bool:
def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
menu = multiworld.get_region("Menu", player_id)
locations = generate_locations(location_count, player_id, None, menu)
locations = generate_locations(location_count, player_id, menu, None)
prog_items = generate_items(prog_item_count, player_id, True)
multiworld.itempool += prog_items
basic_items = generate_items(basic_item_count, player_id, False)
@@ -103,28 +77,6 @@ def generate_player_data(multiworld: MultiWorld, player_id: int, location_count:
return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items)
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]:
locations = []
prefix = "player" + str(player_id) + tag + "_location"
for i in range(count):
name = prefix + str(i)
location = Location(player_id, name, address, region)
locations.append(location)
region.locations.append(location)
return locations
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
items = []
item_type = "prog" if advancement else ""
for i in range(count):
name = "player" + str(player_id) + "_" + item_type + "item" + str(i)
items.append(Item(name,
ItemClassification.progression if advancement else ItemClassification.filler,
code, player_id))
return items
def names(objs: list) -> Iterable[str]:
return map(lambda o: o.name, objs)
@@ -132,7 +84,7 @@ def names(objs: list) -> Iterable[str]:
class TestFillRestrictive(unittest.TestCase):
def test_basic_fill(self):
"""Tests `fill_restrictive` fills and removes the locations and items from their respective lists"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
@@ -150,7 +102,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_ordered_fill(self):
"""Tests `fill_restrictive` fulfills set rules"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
locations = player1.locations
@@ -167,7 +119,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_partial_fill(self):
"""Tests that `fill_restrictive` returns unfilled locations"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 3, 2)
item0 = player1.prog_items[0]
@@ -193,7 +145,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_minimal_fill(self):
"""Test that fill for minimal player can have unreachable items"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
@@ -218,7 +170,7 @@ class TestFillRestrictive(unittest.TestCase):
the non-minimal player get all items.
"""
multiworld = generate_multiworld(2)
multiworld = generate_test_multiworld(2)
player1 = generate_player_data(multiworld, 1, 3, 3)
player2 = generate_player_data(multiworld, 2, 3, 3)
@@ -245,11 +197,11 @@ class TestFillRestrictive(unittest.TestCase):
# all of player2's locations and items should be accessible (not all of player1's)
for item in player2.prog_items:
self.assertTrue(multiworld.state.has(item.name, player2.id),
f'{item} is unreachable in {item.location}')
f"{item} is unreachable in {item.location}")
def test_reversed_fill(self):
"""Test a different set of rules can be satisfied"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
@@ -268,7 +220,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_multi_step_fill(self):
"""Test that fill is able to satisfy multiple spheres"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 4, 4)
items = player1.prog_items
@@ -293,7 +245,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_impossible_fill(self):
"""Test that fill raises an error when it can't place any items"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
locations = player1.locations
@@ -310,7 +262,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_circular_fill(self):
"""Test that fill raises an error when it can't place all items"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 3, 3)
item0 = player1.prog_items[0]
@@ -331,7 +283,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_competing_fill(self):
"""Test that fill raises an error when it can't place items in a way to satisfy the conditions"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
@@ -348,7 +300,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_multiplayer_fill(self):
"""Test that items can be placed across worlds"""
multiworld = generate_multiworld(2)
multiworld = generate_test_multiworld(2)
player1 = generate_player_data(multiworld, 1, 2, 2)
player2 = generate_player_data(multiworld, 2, 2, 2)
@@ -369,7 +321,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_multiplayer_rules_fill(self):
"""Test that fill across worlds satisfies the rules"""
multiworld = generate_multiworld(2)
multiworld = generate_test_multiworld(2)
player1 = generate_player_data(multiworld, 1, 2, 2)
player2 = generate_player_data(multiworld, 2, 2, 2)
@@ -393,7 +345,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_restrictive_progress(self):
"""Test that various spheres with different requirements can be filled"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, prog_item_count=25)
items = player1.prog_items.copy()
multiworld.completion_condition[player1.id] = lambda state: state.has_all(
@@ -417,7 +369,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_swap_to_earlier_location_with_item_rule(self):
"""Test that item swap happens and works as intended"""
# test for PR#1109
multiworld = generate_multiworld(1)
multiworld = generate_test_multiworld(1)
player1 = generate_player_data(multiworld, 1, 4, 4)
locations = player1.locations[:] # copy required
items = player1.prog_items[:] # copy required
@@ -442,7 +394,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_swap_to_earlier_location_with_item_rule2(self):
"""Test that swap works before all items are placed"""
multiworld = generate_multiworld(1)
multiworld = generate_test_multiworld(1)
player1 = generate_player_data(multiworld, 1, 5, 5)
locations = player1.locations[:] # copy required
items = player1.prog_items[:] # copy required
@@ -484,7 +436,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_double_sweep(self):
"""Test that sweep doesn't duplicate Event items when sweeping"""
# test for PR1114
multiworld = generate_multiworld(1)
multiworld = generate_test_multiworld(1)
player1 = generate_player_data(multiworld, 1, 1, 1)
location = player1.locations[0]
location.address = None
@@ -498,7 +450,7 @@ class TestFillRestrictive(unittest.TestCase):
def test_correct_item_instance_removed_from_pool(self):
"""Test that a placed item gets removed from the submitted pool"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(multiworld, 1, 2, 2)
player1.prog_items[0].name = "Different_item_instance_but_same_item_name"
@@ -515,7 +467,7 @@ class TestFillRestrictive(unittest.TestCase):
class TestDistributeItemsRestrictive(unittest.TestCase):
def test_basic_distribute(self):
"""Test that distribute_items_restrictive is deterministic"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -535,7 +487,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_excluded_distribute(self):
"""Test that distribute_items_restrictive doesn't put advancement items on excluded locations"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -550,7 +502,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_non_excluded_item_distribute(self):
"""Test that useful items aren't placed on excluded locations"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -565,7 +517,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_too_many_excluded_distribute(self):
"""Test that fill fails if it can't place all progression items due to too many excluded locations"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -578,7 +530,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_non_excluded_item_must_distribute(self):
"""Test that fill fails if it can't place useful items due to too many excluded locations"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -593,7 +545,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_priority_distribute(self):
"""Test that priority locations receive advancement items"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -608,7 +560,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_excess_priority_distribute(self):
"""Test that if there's more priority locations than advancement items, they can still fill"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
@@ -623,7 +575,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_multiple_world_priority_distribute(self):
"""Test that priority fill can be satisfied for multiple worlds"""
multiworld = generate_multiworld(3)
multiworld = generate_test_multiworld(3)
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
player2 = generate_player_data(
@@ -653,7 +605,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_can_remove_locations_in_fill_hook(self):
"""Test that distribute_items_restrictive calls the fill hook and allows for item and location removal"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
@@ -673,12 +625,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_seed_robust_to_item_order(self):
"""Test deterministic fill"""
mw1 = generate_multiworld()
mw1 = generate_test_multiworld()
gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1)
mw2 = generate_multiworld()
mw2 = generate_test_multiworld()
gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
mw2.itempool.append(mw2.itempool.pop(0))
@@ -691,12 +643,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_seed_robust_to_location_order(self):
"""Test deterministic fill even if locations in a region are reordered"""
mw1 = generate_multiworld()
mw1 = generate_test_multiworld()
gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1)
mw2 = generate_multiworld()
mw2 = generate_test_multiworld()
gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
reg = mw2.get_region("Menu", gen2.id)
@@ -710,7 +662,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_can_reserve_advancement_items_for_general_fill(self):
"""Test that priority locations fill still satisfies item rules"""
multiworld = generate_multiworld()
multiworld = generate_test_multiworld()
player1 = generate_player_data(
multiworld, 1, location_count=5, prog_item_count=5)
items = player1.prog_items
@@ -727,7 +679,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_non_excluded_local_items(self):
"""Test that local items get placed locally in a multiworld"""
multiworld = generate_multiworld(2)
multiworld = generate_test_multiworld(2)
player1 = generate_player_data(
multiworld, 1, location_count=5, basic_item_count=5)
player2 = generate_player_data(
@@ -748,7 +700,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_early_items(self) -> None:
"""Test that the early items API successfully places items early"""
mw = generate_multiworld(2)
mw = generate_test_multiworld(2)
player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5)
player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5)
mw.early_items[1][player1.basic_items[0].name] = 1
@@ -803,11 +755,11 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
if location.item and location.item == item:
return True
self.fail("Expected " + region.name + " to contain " + item.name +
"\n Contains" + str(list(map(lambda location: location.item, region.locations))))
self.fail(f"Expected {region.name} to contain {item.name}.\n"
f"Contains{list(map(lambda location: location.item, region.locations))}")
def setUp(self) -> None:
multiworld = generate_multiworld(2)
multiworld = generate_test_multiworld(2)
self.multiworld = multiworld
player1 = generate_player_data(
multiworld, 1, prog_item_count=2, basic_item_count=40)

View File

@@ -1,18 +1,24 @@
import os
import unittest
from io import StringIO
from tempfile import TemporaryFile
from typing import Any, Dict, List, cast
from settings import Settings
import Utils
from settings import Settings, Group
class TestIDs(unittest.TestCase):
yaml_options: Dict[Any, Any]
@classmethod
def setUpClass(cls) -> None:
with TemporaryFile("w+", encoding="utf-8") as f:
Settings(None).dump(f)
f.seek(0, os.SEEK_SET)
cls.yaml_options = Utils.parse_yaml(f.read())
yaml_options = Utils.parse_yaml(f.read())
assert isinstance(yaml_options, dict)
cls.yaml_options = yaml_options
def test_utils_in_yaml(self) -> None:
"""Tests that the auto generated host.yaml has default settings in it"""
@@ -30,3 +36,47 @@ class TestIDs(unittest.TestCase):
self.assertIn(option_key, utils_options)
for sub_option_key in option_set:
self.assertIn(sub_option_key, utils_options[option_key])
class TestSettingsDumper(unittest.TestCase):
def test_string_format(self) -> None:
"""Test that dumping a string will yield the expected output"""
# By default, pyyaml has automatic line breaks in strings and quoting is optional.
# What we want for consistency instead is single-line strings and always quote them.
# Line breaks have to become \n in that quoting style.
class AGroup(Group):
key: str = " ".join(["x"] * 60) + "\n" # more than 120 chars, contains spaces and a line break
with StringIO() as writer:
AGroup().dump(writer, 0)
expected_value = AGroup.key.replace("\n", "\\n")
self.assertEqual(writer.getvalue(), f"key: \"{expected_value}\"\n",
"dumped string has unexpected formatting")
def test_indentation(self) -> None:
"""Test that dumping items will add indentation"""
# NOTE: we don't care how many spaces there are, but it has to be a multiple of level
class AList(List[Any]):
__doc__ = None # make sure we get no doc string
class AGroup(Group):
key: AList = cast(AList, ["a", "b", [1]])
for level in range(3):
with StringIO() as writer:
AGroup().dump(writer, level)
lines = writer.getvalue().split("\n", 5)
key_line = lines[0]
key_spaces = len(key_line) - len(key_line.lstrip(" "))
value_lines = lines[1:-1]
value_spaces = [len(value_line) - len(value_line.lstrip(" ")) for value_line in value_lines]
if level == 0:
self.assertEqual(key_spaces, 0)
else:
self.assertGreaterEqual(key_spaces, level)
self.assertEqual(key_spaces % level, 0)
self.assertGreaterEqual(value_spaces[0], key_spaces) # a
self.assertEqual(value_spaces[1], value_spaces[0]) # b
self.assertEqual(value_spaces[2], value_spaces[0]) # start of sub-list
self.assertGreater(value_spaces[3], value_spaces[0],
f"{value_lines[3]} should have more indentation than {value_lines[0]} in {lines}")

View File

@@ -1,4 +1,7 @@
import unittest
from BaseClasses import PlandoOptions
from Options import ItemLinks
from worlds.AutoWorld import AutoWorldRegister
@@ -17,3 +20,30 @@ class TestOptions(unittest.TestCase):
with self.subTest(game=gamename):
self.assertFalse(hasattr(world_type, "options"),
f"Unexpected assignment to {world_type.__name__}.options!")
def test_item_links_name_groups(self):
"""Tests that item links successfully unfold item_name_groups"""
item_link_groups = [
[{
"name": "ItemLinkGroup",
"item_pool": ["Everything"],
"link_replacement": False,
"replacement_item": None,
}],
[{
"name": "ItemLinkGroup",
"item_pool": ["Hammer", "Bow"],
"link_replacement": False,
"replacement_item": None,
}]
]
# we really need some sort of test world but generic doesn't have enough items for this
world = AutoWorldRegister.world_types["A Link to the Past"]
plando_options = PlandoOptions.from_option_string("bosses")
item_links = [ItemLinks.from_any(item_link_groups[0]), ItemLinks.from_any(item_link_groups[1])]
for link in item_links:
link.verify(world, "tester", plando_options)
self.assertIn("Hammer", link.value[0]["item_pool"])
self.assertIn("Bow", link.value[0]["item_pool"])
# TODO test that the group created using these options has the items

View File

@@ -121,11 +121,7 @@ class AutoLogicRegister(type):
def _timed_call(method: Callable[..., Any], *args: Any,
multiworld: Optional["MultiWorld"] = None, player: Optional[int] = None) -> Any:
start = time.perf_counter()
if multiworld:
with multiworld.observer(method, start):
ret = method(*args)
else:
ret = method(*args)
ret = method(*args)
taken = time.perf_counter() - start
if taken > 1.0:
if player and multiworld:
@@ -173,7 +169,7 @@ def call_stage(multiworld: "MultiWorld", method_name: str, *args: Any) -> None:
for world_type in sorted(world_types, key=lambda world: world.__name__):
stage_callable = getattr(world_type, f"stage_{method_name}", None)
if stage_callable:
_timed_call(stage_callable, multiworld, *args, multiworld=multiworld)
_timed_call(stage_callable, multiworld, *args)
class WebWorld:

View File

@@ -19,9 +19,9 @@ class WorldPosition:
def get_position(self, random):
if self.room_x is None or self.room_y is None:
return random.choice(standard_positions)
return self.room_id, random.choice(standard_positions)
else:
return self.room_x, self.room_y
return self.room_id, (self.room_x, self.room_y)
class LocationData:
@@ -46,24 +46,26 @@ class LocationData:
self.needs_bat_logic: int = needs_bat_logic
self.local_item: int = None
def get_position(self, random):
def get_random_position(self, random):
x: int = None
y: int = None
if self.world_positions is None or len(self.world_positions) == 0:
if self.room_id is None:
return None
self.room_x, self.room_y = random.choice(standard_positions)
if self.room_id is None:
x, y = random.choice(standard_positions)
return self.room_id, x, y
else:
selected_pos = random.choice(self.world_positions)
self.room_id = selected_pos.room_id
self.room_x, self.room_y = selected_pos.get_position(random)
return self.room_x, self.room_y
room_id, (x, y) = selected_pos.get_position(random)
return self.get_random_room_id(random), x, y
def get_room_id(self, random):
def get_random_room_id(self, random):
if self.world_positions is None or len(self.world_positions) == 0:
return None
if self.room_id is None:
return None
if self.room_id is None:
selected_pos = random.choice(self.world_positions)
self.room_id = selected_pos.room_id
self.room_x, self.room_y = selected_pos.get_position(random)
return selected_pos.room_id
return self.room_id
@@ -97,7 +99,7 @@ def get_random_room_in_regions(regions: [str], random) -> int:
possible_rooms = {}
for locname in location_table:
if location_table[locname].region in regions:
room = location_table[locname].get_room_id(random)
room = location_table[locname].get_random_room_id(random)
if room is not None:
possible_rooms[room] = location_table[locname].room_id
return random.choice(list(possible_rooms.keys()))

View File

@@ -25,8 +25,6 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
def create_regions(multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
for name, locdata in location_table.items():
locdata.get_position(multiworld.random)
menu = Region("Menu", player, multiworld)

View File

@@ -371,8 +371,9 @@ class AdventureWorld(World):
if location.item.player == self.player and \
location.item.name == "nothing":
location_data = location_table[location.name]
room_id = location_data.get_random_room_id(self.random)
auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id,
location_data.room_id))
room_id))
# standard Adventure items, which are placed in the rom
elif location.item.player == self.player and \
location.item.name != "nothing" and \
@@ -383,14 +384,18 @@ class AdventureWorld(World):
item_ram_address = item_ram_addresses[item_table[location.item.name].table_index]
item_position_data_start = item_position_table + item_ram_address - items_ram_start
location_data = location_table[location.name]
room_x, room_y = location_data.get_position(self.multiworld.per_slot_randoms[self.player])
(room_id, room_x, room_y) = \
location_data.get_random_position(self.random)
if location_data.needs_bat_logic and bat_logic == 0x0:
copied_location = copy.copy(location_data)
copied_location.local_item = item_ram_address
copied_location.room_id = room_id
copied_location.room_x = room_x
copied_location.room_y = room_y
bat_no_touch_locs.append(copied_location)
del unplaced_local_items[location.item.name]
rom_deltas[item_position_data_start] = location_data.room_id
rom_deltas[item_position_data_start] = room_id
rom_deltas[item_position_data_start + 1] = room_x
rom_deltas[item_position_data_start + 2] = room_y
local_item_to_location[item_table_offset] = self.location_name_to_id[location.name] \
@@ -398,14 +403,20 @@ class AdventureWorld(World):
# items from other worlds, and non-standard Adventure items handled by script, like difficulty switches
elif location.item.code is not None:
if location.item.code != nothing_item_id:
location_data = location_table[location.name]
location_data = copy.copy(location_table[location.name])
(room_id, room_x, room_y) = \
location_data.get_random_position(self.random)
location_data.room_id = room_id
location_data.room_x = room_x
location_data.room_y = room_y
foreign_item_locations.append(location_data)
if location_data.needs_bat_logic and bat_logic == 0x0:
bat_no_touch_locs.append(location_data)
else:
location_data = location_table[location.name]
room_id = location_data.get_random_room_id(self.random)
auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id,
location_data.room_id))
room_id))
# Adventure items that are in another world get put in an invalid room until needed
for unplaced_item_name, unplaced_item in unplaced_local_items.items():
item_position_data_start = get_item_position_data_start(unplaced_item.table_index)

View File

@@ -264,7 +264,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
if loc in all_state_base.events:
all_state_base.events.remove(loc)
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True,
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, lock=True, allow_excluded=True,
name="LttP Dungeon Items")

View File

@@ -23,170 +23,7 @@ def parse_arguments(argv, no_defaults=False):
multiargs, _ = parser.parse_known_args(argv)
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--logic', default=defval('no_glitches'), const='no_glitches', nargs='?', choices=['no_glitches', 'minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'],
help='''\
Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches:
Minor Glitches: May require Fake Flippers, Bunny Revival
and Dark Room Navigation.
Overworld Glitches: May require overworld glitches.
Hybrid Major Glitches: May require both overworld and underworld clipping.
No Logic: Distribute items without regard for
item requirements.
''')
parser.add_argument('--glitch_triforce', help='Allow glitching to Triforce from Ganon\'s room', action='store_true')
parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'],
help='''\
Select game mode. (default: %(default)s)
Open: World starts with Zelda rescued.
Standard: Fixes Hyrule Castle Secret Entrance and Front Door
but may lead to weird rain state issues if you exit
through the Hyrule Castle side exits before rescuing
Zelda in a full shuffle.
Inverted: Starting locations are Dark Sanctuary in West Dark
World or at Link's House, which is shuffled freely.
Requires the moon pearl to be Link in the Light World
instead of a bunny.
''')
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?',
choices=['ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', 'ganon_pedestal'],
help='''\
Select completion goal. (default: %(default)s)
Ganon: Collect all crystals, beat Agahnim 2 then
defeat Ganon.
Crystals: Collect all crystals then defeat Ganon.
Pedestal: Places the Triforce at the Master Sword Pedestal.
Ganon Pedestal: Pull the Master Sword Pedestal, then defeat Ganon.
All Dungeons: Collect all crystals, pendants, beat both
Agahnim fights and then defeat Ganon.
Triforce Hunt: Places 30 Triforce Pieces in the world, collect
20 of them to beat the game.
Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect
20 of them to beat the game.
Ganon Triforce Hunt: Places 30 Triforce Pieces in the world, collect
20 of them, then defeat Ganon.
Local Ganon Triforce Hunt: Places 30 Triforce Pieces in your world,
collect 20 of them, then defeat Ganon.
''')
parser.add_argument('--triforce_pieces_available', default=defval(30),
type=lambda value: min(max(int(value), 1), 90),
help='''Set Triforce Pieces available in item pool.''')
parser.add_argument('--triforce_pieces_required', default=defval(20),
type=lambda value: min(max(int(value), 1), 90),
help='''Set Triforce Pieces required to win a Triforce Hunt''')
parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?',
choices=['easy', 'normal', 'hard', 'expert'],
help='''\
Select game difficulty. Affects available itempool. (default: %(default)s)
Easy: An easier setting with some equipment duplicated and increased health.
Normal: Normal difficulty.
Hard: A harder setting with less equipment and reduced health.
Expert: A harder yet setting with minimum equipment and health.
''')
parser.add_argument('--item_functionality', default=defval('normal'), const='normal', nargs='?',
choices=['easy', 'normal', 'hard', 'expert'],
help='''\
Select limits on item functionality to increase difficulty. (default: %(default)s)
Easy: Easy functionality. (Medallions usable without sword)
Normal: Normal functionality.
Hard: Reduced functionality.
Expert: Greatly reduced functionality.
''')
parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'],
help='''\
Select game timer setting. Affects available itempool. (default: %(default)s)
None: No timer.
Display: Displays a timer but does not affect
the itempool.
Timed: Starts with clock at zero. Green Clocks
subtract 4 minutes (Total: 20), Blue Clocks
subtract 2 minutes (Total: 10), Red Clocks add
2 minutes (Total: 10). Winner is player with
lowest time at the end.
Timed OHKO: Starts clock at 10 minutes. Green Clocks add
5 minutes (Total: 25). As long as clock is at 0,
Link will die in one hit.
OHKO: Like Timed OHKO, but no clock items are present
and the clock is permenantly at zero.
Timed Countdown: Starts with clock at 40 minutes. Same clocks as
Timed mode. If time runs out, you lose (but can
still keep playing).
''')
parser.add_argument('--countdown_start_time', default=defval(10), type=int,
help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''')
parser.add_argument('--red_clock_time', default=defval(-2), type=int,
help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''')
parser.add_argument('--blue_clock_time', default=defval(2), type=int,
help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''')
parser.add_argument('--green_clock_time', default=defval(4), type=int,
help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''')
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
help='''\
Select dungeon counter display settings. (default: %(default)s)
(Note, since timer takes up the same space on the hud as dungeon
counters, timer settings override dungeon counter settings.)
Default: Dungeon counters only show when the compass is
picked up, or otherwise sent, only when compass
shuffle is turned on.
On: Dungeon counters are always displayed.
Pickup: Dungeon counters are shown when the compass is
picked up, even when compass shuffle is turned
off.
Off: Dungeon counters are never shown.
''')
parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?',
choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'],
help='''\
Select item filling algorithm. (default: %(default)s
balanced: vt26 derivitive that aims to strike a balance between
the overworld heavy vt25 and the dungeon heavy vt26
algorithm.
vt26: Shuffle items and place them in a random location
that it is not impossible to be in. This includes
dungeon keys and items.
vt25: Shuffle items and place them in a random location
that it is not impossible to be in.
Flood: Push out items starting from Link\'s House and
slightly biased to placing progression items with
less restrictions.
''')
parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed'],
help='''\
Select Entrance Shuffling Algorithm. (default: %(default)s)
Full: Mix cave and dungeon entrances freely while limiting
multi-entrance caves to one world.
Simple: Shuffle Dungeon Entrances/Exits between each other
and keep all 4-entrance dungeons confined to one
location. All caves outside of death mountain are
shuffled in pairs and matched by original type.
Restricted: Use Dungeons shuffling from Simple but freely
connect remaining entrances.
Crossed: Mix cave and dungeon entrances freely while allowing
caves to cross between worlds.
Insanity: Decouple entrances and exits from each other and
shuffle them freely. Caves that used to be single
entrance will still exit to the same location from
which they are entered.
Vanilla: All entrances are in the same locations they were
in the base game.
Legacy shuffles preserve behavior from older versions of the
entrance randomizer including significant technical limitations.
The dungeon variants only mix up dungeons and keep the rest of
the overworld vanilla.
''')
parser.add_argument('--open_pyramid', default=defval('auto'), help='''\
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
fast ganon goals are crystals, ganon_triforce_hunt, local_ganon_triforce_hunt, pedestalganon
auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle
is vanilla, dungeons_simple or dungeons_full.
goal - Opens pyramid hole if the goal specifies a fast ganon.
yes - Always opens the pyramid hole.
no - Never opens the pyramid hole.
''', choices=['auto', 'goal', 'yes', 'no'])
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
parser.add_argument('--count', help='''\
Use to batch generate multiple seeds with same settings.
@@ -195,16 +32,6 @@ def parse_arguments(argv, no_defaults=False):
--seed given will produce the same 10 (different) roms each
time).
''', type=int)
parser.add_argument('--custom', default=defval(False), help='Not supported.')
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
# included for backwards compatibility
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True))
parser.add_argument('--no-shuffleganon', help='''\
If set, the Pyramid Hole and Ganon's Tower are not
included entrance shuffle pool.
''', action='store_false', dest='shuffleganon')
parser.add_argument('--sprite', help='''\
Path to a sprite sheet to use for Link. Needs to be in
binary format and have a length of 0x7000 (28672) bytes,
@@ -212,35 +39,12 @@ def parse_arguments(argv, no_defaults=False):
Alternatively, can be a ALttP Rom patched with a Link
sprite that will be extracted.
''')
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
"singularity"])
parser.add_argument('--enemy_health', default=defval('default'),
choices=['default', 'easy', 'normal', 'hard', 'expert'])
parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos'])
parser.add_argument('--beemizer_total_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100))
parser.add_argument('--beemizer_trap_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100))
parser.add_argument('--shop_shuffle', default='', help='''\
combine letters for options:
g: generate default inventories for light and dark world shops, and unique shops
f: generate default inventories for each shop individually
i: shuffle the default inventories of the shops around
p: randomize the prices of the items in shop inventories
u: shuffle capacity upgrades into the item pool
w: consider witch's hut like any other shop and shuffle/randomize it too
''')
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
parser.add_argument('--sprite_pool', help='''\
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
parser.add_argument('--dark_room_logic', default=('Lamp'), choices=["lamp", "torches", "none"], help='''\
For unlit dark rooms, require the Lamp to be considered in logic by default.
Torches means additionally easily accessible Torches that can be lit with Fire Rod are considered doable.
None means full traversal through dark rooms without tools is considered doable.''')
parser.add_argument('--multi', default=defval(1), type=lambda value: max(int(value), 1))
parser.add_argument('--names', default=defval(''))
parser.add_argument('--outputpath')
parser.add_argument('--game', default="A Link to the Past")
parser.add_argument('--game', default="Archipelago")
parser.add_argument('--race', default=defval(False), action='store_true')
parser.add_argument('--outputname')
if multiargs.multi:
@@ -249,43 +53,21 @@ def parse_arguments(argv, no_defaults=False):
ret = parser.parse_args(argv)
# shuffle medallions
ret.required_medallions = ("random", "random")
# cannot be set through CLI currently
ret.plando_items = []
ret.plando_texts = {}
ret.plando_connections = []
if ret.timer == "none":
ret.timer = False
if ret.dungeon_counters == 'on':
ret.dungeon_counters = True
elif ret.dungeon_counters == 'off':
ret.dungeon_counters = False
if multiargs.multi:
defaults = copy.deepcopy(ret)
for player in range(1, multiargs.multi + 1):
playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True)
for name in ['logic', 'mode', 'goal', 'difficulty', 'item_functionality',
'shuffle', 'open_pyramid', 'timer',
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
'beemizer_total_chance', 'beemizer_trap_chance',
'shufflebosses', 'enemy_health', 'enemy_damage',
'sprite',
"triforce_pieces_available",
"triforce_pieces_required", "shop_shuffle",
"required_medallions",
"plando_items", "plando_texts", "plando_connections",
'dungeon_counters',
'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
'game']:
for name in ["plando_items", "plando_texts", "plando_connections", "game", "sprite", "sprite_pool"]:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
else:
getattr(ret, name)[player] = value
return ret
return ret

View File

@@ -554,19 +554,20 @@ def link_entrances(world, player):
# check for swamp palace fix
if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)':
world.swamp_patch_required[player] = True
world.worlds[player].swamp_patch_required = True
# check for potion shop location
if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop':
world.powder_patch_required[player] = True
world.worlds[player].powder_patch_required = True
# check for ganon location
if world.get_entrance('Pyramid Hole', player).connected_region.name != 'Pyramid':
world.ganon_at_pyramid[player] = False
world.worlds[player].ganon_at_pyramid = False
# check for Ganon's Tower location
if world.get_entrance('Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)':
world.ganonstower_vanilla[player] = False
world.worlds[player].ganonstower_vanilla = False
def link_inverted_entrances(world, player):
# Link's house shuffled freely, Houlihan set in mandatory_connections
@@ -1261,19 +1262,19 @@ def link_inverted_entrances(world, player):
# patch swamp drain
if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)':
world.swamp_patch_required[player] = True
world.worlds[player].swamp_patch_required = True
# check for potion shop location
if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop':
world.powder_patch_required[player] = True
world.worlds[player].powder_patch_required = True
# check for ganon location
if world.get_entrance('Inverted Pyramid Hole', player).connected_region.name != 'Pyramid':
world.ganon_at_pyramid[player] = False
world.worlds[player].ganon_at_pyramid = False
# check for Ganon's Tower location
if world.get_entrance('Inverted Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)':
world.ganonstower_vanilla[player] = False
world.worlds[player].ganonstower_vanilla = False
def connect_simple(world, exitname, regionname, player):

View File

@@ -238,7 +238,7 @@ def generate_itempool(world):
raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}")
if multiworld.timer[player] in ['ohko', 'timed_ohko']:
multiworld.can_take_damage[player] = False
world.can_take_damage = False
if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False)
else:
@@ -276,13 +276,14 @@ def generate_itempool(world):
# set up item pool
additional_triforce_pieces = 0
treasure_hunt_total = 0
if multiworld.custom:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count,
treasure_hunt_icon) = make_custom_item_pool(multiworld, player)
pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = (
make_custom_item_pool(multiworld, player))
multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999)
else:
pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, \
treasure_hunt_icon, additional_triforce_pieces = get_pool_core(multiworld, player)
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
additional_triforce_pieces) = get_pool_core(multiworld, player)
for item in precollected_items:
multiworld.push_precollected(item_factory(item, world))
@@ -317,11 +318,11 @@ def generate_itempool(world):
'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']):
if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]:
if 'Bow' in placed_items["Link's Uncle"]:
multiworld.escape_assist[player].append('arrows')
multiworld.worlds[player].escape_assist.append('arrows')
elif 'Cane' in placed_items["Link's Uncle"]:
multiworld.escape_assist[player].append('magic')
multiworld.worlds[player].escape_assist.append('magic')
else:
multiworld.escape_assist[player].append('bombs')
multiworld.worlds[player].escape_assist.append('bombs')
for (location, item) in placed_items.items():
multiworld.get_location(location, player).place_locked_item(item_factory(item, world))
@@ -334,13 +335,11 @@ def generate_itempool(world):
item.code = 0x65 # Progressive Bow (Alt)
break
if clock_mode is not None:
multiworld.clock_mode[player] = clock_mode
if clock_mode:
world.clock_mode = clock_mode
if treasure_hunt_count is not None:
multiworld.treasure_hunt_count[player] = treasure_hunt_count % 999
if treasure_hunt_icon is not None:
multiworld.treasure_hunt_icon[player] = treasure_hunt_icon
multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999
multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total
dungeon_items = [item for item in get_dungeon_item_pool_player(world)
if item.name not in multiworld.worlds[player].dungeon_local_item_names]
@@ -369,7 +368,7 @@ def generate_itempool(world):
elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
# key drop shuffle and universal keys are on. Add universal keys in place of key drop keys.
multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world))
dungeon_item_replacements = sum(difficulties[multiworld.difficulty[player]].extras, []) * 2
dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2
multiworld.random.shuffle(dungeon_item_replacements)
for x in range(len(dungeon_items)-1, -1, -1):
@@ -464,8 +463,6 @@ def generate_itempool(world):
while len(items) > pool_count:
items_were_cut = False
for reduce_item in items_reduction_table:
if len(items) <= pool_count:
break
if len(reduce_item) == 2:
items_were_cut = items_were_cut or cut_item(items, *reduce_item)
elif len(reduce_item) == 4:
@@ -477,7 +474,10 @@ def generate_itempool(world):
items.remove(bottle)
removed_filler.append(bottle)
items_were_cut = True
assert items_were_cut, f"Failed to limit item pool size for player {player}"
if items_were_cut:
break
else:
raise Exception(f"Failed to limit item pool size for player {player}")
if len(items) < pool_count:
items += removed_filler[len(items) - pool_count:]
@@ -498,8 +498,8 @@ def generate_itempool(world):
for i in range(4):
next(adv_heart_pieces).classification = ItemClassification.progression
multiworld.required_medallions[player] = (multiworld.misery_mire_medallion[player].current_key.title(),
multiworld.turtle_rock_medallion[player].current_key.title())
world.required_medallions = (multiworld.misery_mire_medallion[player].current_key.title(),
multiworld.turtle_rock_medallion[player].current_key.title())
place_bosses(world)
@@ -591,9 +591,9 @@ def get_pool_core(world, player: int):
pool = []
placed_items = {}
precollected_items = []
clock_mode = None
treasure_hunt_count = None
treasure_hunt_icon = None
clock_mode: str = ""
treasure_hunt_required: int = 0
treasure_hunt_total: int = 0
diff = difficulties[difficulty]
pool.extend(diff.alwaysitems)
@@ -682,21 +682,21 @@ def get_pool_core(world, player: int):
if 'triforce_hunt' in goal:
if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra:
triforce_pieces = world.triforce_pieces_available[player].value + world.triforce_pieces_extra[player].value
treasure_hunt_total = (world.triforce_pieces_available[player].value
+ world.triforce_pieces_extra[player].value)
elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage:
percentage = float(max(100, world.triforce_pieces_percentage[player].value)) / 100
triforce_pieces = int(round(world.triforce_pieces_required[player].value * percentage, 0))
percentage = float(world.triforce_pieces_percentage[player].value) / 100
treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0))
else: # available
triforce_pieces = world.triforce_pieces_available[player].value
treasure_hunt_total = world.triforce_pieces_available[player].value
triforce_pieces = max(triforce_pieces, world.triforce_pieces_required[player].value)
triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value))
pieces_in_core = min(extraitems, triforce_pieces)
additional_pieces_to_place = triforce_pieces - pieces_in_core
pool.extend(["Triforce Piece"] * pieces_in_core)
extraitems -= pieces_in_core
treasure_hunt_count = world.triforce_pieces_required[player].value
treasure_hunt_icon = 'Triforce Piece'
treasure_hunt_required = world.triforce_pieces_required[player].value
for extra in diff.extras:
if extraitems >= len(extra):
@@ -737,7 +737,7 @@ def get_pool_core(world, player: int):
place_item(key_location, "Small Key (Universal)")
pool = pool[:-3]
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
additional_pieces_to_place)
@@ -752,9 +752,9 @@ def make_custom_item_pool(world, player):
pool = []
placed_items = {}
precollected_items = []
clock_mode = None
treasure_hunt_count = None
treasure_hunt_icon = None
clock_mode: str = ""
treasure_hunt_required: int = 0
treasure_hunt_total: int = 0
def place_item(loc, item):
assert loc not in placed_items, "cannot place item twice"
@@ -849,8 +849,7 @@ def make_custom_item_pool(world, player):
if "triforce" in world.goal[player]:
pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player])
itemtotal += world.triforce_pieces_available[player]
treasure_hunt_count = world.triforce_pieces_required[player]
treasure_hunt_icon = 'Triforce Piece'
treasure_hunt_required = world.triforce_pieces_required[player]
if timer in ['display', 'timed', 'timed_countdown']:
clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch'
@@ -895,4 +894,4 @@ def make_custom_item_pool(world, player):
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
logging.warning(f"Pool was filled up with {total_items_to_place - itemtotal} Nothing's for player {player}")
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon)
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_required)

View File

@@ -433,7 +433,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
if multiworld.key_drop_shuffle[player]:
key_drop_enemies = {
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
0x4E20A, 0x4E326, 0x4E4F7, 0x4E686, 0x4E70C, 0x4E7C8, 0x4E7FA
0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA
}
for enemy in key_drop_enemies:
if rom.read_byte(enemy) == 0x12:
@@ -945,22 +945,22 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x118C64, [first_bot, mid_bot, last_bot])
# patch medallion requirements
if world.required_medallions[player][0] == 'Bombos':
if local_world.required_medallions[0] == 'Bombos':
rom.write_byte(0x180022, 0x00) # requirement
rom.write_byte(0x4FF2, 0x31) # sprite
rom.write_byte(0x50D1, 0x80)
rom.write_byte(0x51B0, 0x00)
elif world.required_medallions[player][0] == 'Quake':
elif local_world.required_medallions[0] == 'Quake':
rom.write_byte(0x180022, 0x02) # requirement
rom.write_byte(0x4FF2, 0x31) # sprite
rom.write_byte(0x50D1, 0x88)
rom.write_byte(0x51B0, 0x00)
if world.required_medallions[player][1] == 'Bombos':
if local_world.required_medallions[1] == 'Bombos':
rom.write_byte(0x180023, 0x00) # requirement
rom.write_byte(0x5020, 0x31) # sprite
rom.write_byte(0x50FF, 0x90)
rom.write_byte(0x51DE, 0x00)
elif world.required_medallions[player][1] == 'Ether':
elif local_world.required_medallions[1] == 'Ether':
rom.write_byte(0x180023, 0x01) # requirement
rom.write_byte(0x5020, 0x31) # sprite
rom.write_byte(0x50FF, 0x98)
@@ -1069,7 +1069,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Byrna residual magic cost
rom.write_bytes(0x45C42, [0x04, 0x02, 0x01])
difficulty = world.difficulty_requirements[player]
difficulty = local_world.difficulty_requirements
# Set overflow items for progressive equipment
rom.write_bytes(0x180090,
@@ -1240,17 +1240,17 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x180044, 0x01) # hammer activates tablets
# set up clocks for timed modes
if world.clock_mode[player] in ['ohko', 'countdown-ohko']:
if local_world.clock_mode in ['ohko', 'countdown-ohko']:
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
elif world.clock_mode[player] == 'stopwatch':
elif local_world.clock_mode == 'stopwatch':
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
elif world.clock_mode[player] == 'countdown':
elif local_world.clock_mode == 'countdown':
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
else:
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
# Set up requested clock settings
if world.clock_mode[player] in ['countdown-ohko', 'stopwatch', 'countdown']:
if local_world.clock_mode in ['countdown-ohko', 'stopwatch', 'countdown']:
rom.write_int32(0x180200,
world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204,
@@ -1263,14 +1263,14 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32)
# Set up requested start time for countdown modes
if world.clock_mode[player] in ['countdown-ohko', 'countdown']:
if local_world.clock_mode in ['countdown-ohko', 'countdown']:
rom.write_int32(0x18020C, world.countdown_start_time[player] * 60 * 60) # starting time (in frames, sint32)
else:
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
# set up goals for treasure hunt
rom.write_int16(0x180163, world.treasure_hunt_count[player])
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
rom.write_int16(0x180163, local_world.treasure_hunt_required)
rom.write_bytes(0x180165, [0x0E, 0x28]) # Triforce Piece Sprite
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
@@ -1283,14 +1283,14 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x180211, gametype) # Game type
# assorted fixes
rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[
player] else 0x00) # Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1
# Toggle whether to be in real/fake dark world when dying in a DW dungeon before killing aga1
rom.write_byte(0x1800A2, 0x01 if local_world.fix_fake_world else 0x00)
# Lock or unlock aga tower door during escape sequence.
rom.write_byte(0x180169, 0x00)
if world.mode[player] == 'inverted':
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
rom.write_byte(0x180171,
0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death
0x01 if local_world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death
rom.write_byte(0x180173, 0x01) # Bob is enabled
rom.write_byte(0x180168, 0x08) # Spike Cave Damage
rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Cape usage
@@ -1306,7 +1306,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness
rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00)
rom.write_byte(0x180174, 0x01 if local_world.fix_fake_world else 0x00)
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
# Starting equipment
@@ -1448,7 +1448,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
for address in keys[item.name]:
equip[address] = min(equip[address] + 1, 99)
elif item.name in bottles:
if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
if equip[0x34F] < local_world.difficulty_requirements.progressive_bottle_limit:
equip[0x35C + equip[0x34F]] = bottles[item.name]
equip[0x34F] += 1
elif item.name in rupees:
@@ -1507,9 +1507,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x180080,
[50, 50, 70, 70]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10)
rom.write_byte(0x18004D, ((0x01 if 'arrows' in world.escape_assist[player] else 0x00) |
(0x02 if 'bombs' in world.escape_assist[player] else 0x00) |
(0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist
rom.write_byte(0x18004D, ((0x01 if 'arrows' in local_world.escape_assist else 0x00) |
(0x02 if 'bombs' in local_world.escape_assist else 0x00) |
(0x04 if 'magic' in local_world.escape_assist else 0x00))) # Escape assist
if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
rom.write_byte(0x18003E, 0x01) # make ganon invincible
@@ -1546,7 +1546,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count
if world.clock_mode[player] or not world.dungeon_counters[player]:
if local_world.clock_mode or not world.dungeon_counters[player]:
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
elif world.dungeon_counters[player] is True:
rom.write_byte(0x18003C, 0x02) # always on
@@ -1616,7 +1616,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix
rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.glitches_required[
rom.write_byte(0x186383, 0x01 if world.glitches_required[
player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
@@ -1653,13 +1653,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
rom.write_byte(0x18003D, 0x01 if local_world.swamp_patch_required else 0x00)
# powder patch: remove the need to leave the screen after powder, since it causes problems for potion shop at race game
# temporarally we are just nopping out this check we will conver this to a rom fix soon.
rom.write_bytes(0x02F539,
[0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0,
0x4F])
[0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if local_world.powder_patch_required else [
0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
# allow smith into multi-entrance caves in appropriate shuffles
if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or (
@@ -1859,7 +1859,7 @@ def apply_oof_sfx(rom, oof: str):
rom.write_bytes(0x12803A, oof_bytes)
rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB])
#Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
# Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08])
@@ -2421,7 +2421,7 @@ def write_strings(rom, world, player):
' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!'
tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or (
if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or (
world.swordless[player] or world.glitches_required[player] == 'no_glitches')):
prog_bow_locs = world.find_item_locations('Progressive Bow', player, True)
world.per_slot_randoms[player].shuffle(prog_bow_locs)
@@ -2482,16 +2482,16 @@ def write_strings(rom, world, player):
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
else:
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
if world.treasure_hunt_count[player] > 1:
if w.treasure_hunt_required > 1:
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
"hidden in a hollow tree. If you bring\n%d Triforce pieces out of %d, I can reassemble it." % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
else:
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
"hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
elif world.goal[player] in ['pedestal']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
@@ -2500,20 +2500,20 @@ def write_strings(rom, world, player):
tt['ganon_fall_in'] = Ganon1_texts[local_random.randint(0, len(Ganon1_texts) - 1)]
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if world.treasure_hunt_count[player] > 1:
if w.treasure_hunt_required > 1:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
else:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
(w.treasure_hunt_required, w.treasure_hunt_total)
tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)]
@@ -3021,7 +3021,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
def get_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options()
options = Utils.get_settings()
if not file_name:
file_name = options["lttp_options"]["rom_file"]
if not os.path.exists(file_name):

View File

@@ -18,7 +18,8 @@ from .StateHelpers import (can_extend_magic, can_kill_most_things,
can_shoot_arrows, has_beam_sword, has_crystals,
has_fire_source, has_hearts, has_melee_weapon,
has_misery_mire_medallion, has_sword, has_turtle_rock_medallion,
has_triforce_pieces, can_use_bombs, can_bomb_or_bonk)
has_triforce_pieces, can_use_bombs, can_bomb_or_bonk,
can_activate_crystal_switch)
from .UnderworldGlitchRules import underworld_glitches_rules
@@ -63,20 +64,24 @@ def set_rules(world):
if world.glitches_required[player] == 'no_glitches':
no_glitches_rules(world, player)
forbid_bomb_jump_requirements(world, player)
elif world.glitches_required[player] == 'overworld_glitches':
# Initially setting no_glitches_rules to set the baseline rules for some
# entrances. The overworld_glitches_rules set is primarily additive.
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
overworld_glitches_rules(world, player)
forbid_bomb_jump_requirements(world, player)
elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']:
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
overworld_glitches_rules(world, player)
underworld_glitches_rules(world, player)
bomb_jump_requirements(world, player)
elif world.glitches_required[player] == 'minor_glitches':
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
forbid_bomb_jump_requirements(world, player)
else:
raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}')
@@ -97,7 +102,7 @@ def set_rules(world):
# if swamp and dam have not been moved we require mirror for swamp palace
# however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself.
if not world.swamp_patch_required[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
if not world.worlds[player].swamp_patch_required and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player))
# GT Entrance may be required for Turtle Rock for OWG and < 7 required
@@ -186,247 +191,254 @@ def dungeon_boss_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location(location, player))
def global_rules(world, player):
def global_rules(multiworld: MultiWorld, player: int):
world = multiworld.worlds[player]
# ganon can only carry triforce
add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
add_item_rule(multiworld.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
# dungeon prizes can only be crystals/pendants
crystals_and_pendants: Set[str] = \
{item for item, item_data in item_table.items() if item_data.type == "Crystal"}
prize_locations: Iterator[str] = \
(locations for locations, location_data in location_table.items() if location_data[2] == True)
for prize_location in prize_locations:
add_item_rule(world.get_location(prize_location, player),
add_item_rule(multiworld.get_location(prize_location, player),
lambda item: item.name in crystals_and_pendants and item.player == player)
# determines which S&Q locations are available - hide from paths since it isn't an in-game location
for exit in world.get_region('Menu', player).exits:
for exit in multiworld.get_region('Menu', player).exits:
exit.hide_path = True
try:
old_man_sq = world.get_entrance('Old Man S&Q', player)
old_man_sq = multiworld.get_entrance('Old Man S&Q', player)
except KeyError:
pass # it doesn't exist, should be dungeon-only unittests
else:
old_man = world.get_location("Old Man", player)
old_man = multiworld.get_location("Old Man", player)
set_rule(old_man_sq, lambda state: old_man.can_reach(state))
set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player))
set_rule(world.get_location('Purple Chest', player),
set_rule(multiworld.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
set_rule(multiworld.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player))
set_rule(multiworld.get_location('Purple Chest', player),
lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest
set_rule(world.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player))
set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player))
set_rule(multiworld.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player))
set_rule(multiworld.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player))
set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith
set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player))
set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player))
set_rule(world.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player))
set_rule(world.get_location('Library', player), lambda state: state.has('Pegasus Boots', player))
set_rule(multiworld.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith
set_rule(multiworld.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player))
set_rule(multiworld.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player))
set_rule(multiworld.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player))
set_rule(multiworld.get_location('Library', player), lambda state: state.has('Pegasus Boots', player))
if world.enemy_shuffle[player]:
set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and
can_kill_most_things(state, player, 4))
if multiworld.enemy_shuffle[player]:
set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and
can_kill_most_things(state, player, 4))
else:
set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)
and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4))
set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)
and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4))
or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player)
or has_beam_sword(state, player)))
set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
set_rule(multiworld.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
set_rule(world.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Chicken House', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(world.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(world.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(world.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(world.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(world.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Chicken House', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(multiworld.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(multiworld.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(multiworld.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(multiworld.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player)
or has_beam_sword(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Fire Rod", "Cane of Somaria"], player))
set_rule(multiworld.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_location('Spike Cave', player), lambda state:
set_rule(multiworld.get_location('Spike Cave', player), lambda state:
state.has('Hammer', player) and can_lift_rocks(state, player) and
((state.has('Cape', player) and can_extend_magic(state, player, 16, True)) or
(state.has('Cane of Byrna', player) and
(can_extend_magic(state, player, 12, True) or
(state.multiworld.can_take_damage[player] and (state.has('Pegasus Boots', player) or has_hearts(state, player, 4))))))
(world.can_take_damage and (state.has('Pegasus Boots', player) or has_hearts(state, player, 4))))))
)
set_rule(world.get_entrance('Hookshot Cave Bomb Wall (North)', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Hookshot Cave Bomb Wall (South)', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Hookshot Cave Bomb Wall (North)', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Hookshot Cave Bomb Wall (South)', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_location('Hookshot Cave - Bottom Right', player),
set_rule(multiworld.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
set_rule(multiworld.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player))
set_rule(multiworld.get_location('Hookshot Cave - Bottom Right', player),
lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
set_rule(multiworld.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Sewers Door', player),
set_rule(multiworld.get_location('Hyrule Castle - Map Guard Key Drop', player),
lambda state: can_kill_most_things(state, player, 1))
set_rule(multiworld.get_entrance('Sewers Door', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or (
world.small_key_shuffle[player] == small_key_shuffle.option_universal and world.mode[
multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal and multiworld.mode[
player] == 'standard')) # standard universal small keys cannot access the shop
set_rule(world.get_entrance('Sewers Back Door', player),
set_rule(multiworld.get_entrance('Sewers Back Door', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4))
set_rule(world.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(multiworld.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_entrance('Agahnim 1', player),
set_rule(multiworld.get_entrance('Agahnim 1', player),
lambda state: has_sword(state, player) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 4))
set_rule(world.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(world.get_location('Castle Tower - Dark Maze', player),
set_rule(multiworld.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Castle Tower - Dark Maze', player),
lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)',
player))
set_rule(world.get_location('Castle Tower - Dark Archer Key Drop', player),
set_rule(multiworld.get_location('Castle Tower - Dark Archer Key Drop', player),
lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)',
player, 2))
set_rule(world.get_location('Castle Tower - Circle of Pots Key Drop', player),
set_rule(multiworld.get_location('Castle Tower - Circle of Pots Key Drop', player),
lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)',
player, 3))
set_always_allow(world.get_location('Eastern Palace - Big Key Chest', player),
set_always_allow(multiworld.get_location('Eastern Palace - Big Key Chest', player),
lambda state, item: item.name == 'Big Key (Eastern Palace)' and item.player == player)
set_rule(world.get_location('Eastern Palace - Big Key Chest', player),
set_rule(multiworld.get_location('Eastern Palace - Big Key Chest', player),
lambda state: can_kill_most_things(state, player, 5) and (state._lttp_has_key('Small Key (Eastern Palace)',
player, 2) or ((location_item_name(state, 'Eastern Palace - Big Key Chest', player)
== ('Big Key (Eastern Palace)', player) and state.has('Small Key (Eastern Palace)',
player)))))
set_rule(world.get_location('Eastern Palace - Dark Eyegore Key Drop', player),
set_rule(multiworld.get_location('Eastern Palace - Dark Eyegore Key Drop', player),
lambda state: state.has('Big Key (Eastern Palace)', player) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Eastern Palace - Big Chest', player),
set_rule(multiworld.get_location('Eastern Palace - Big Chest', player),
lambda state: state.has('Big Key (Eastern Palace)', player))
# not bothering to check for can_kill_most_things in the rooms leading to boss, as if you can kill a boss you should
# be able to get through these rooms
ep_boss = world.get_location('Eastern Palace - Boss', player)
ep_boss = multiworld.get_location('Eastern Palace - Boss', player)
add_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and
state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and
ep_boss.parent_region.dungeon.boss.can_defeat(state))
ep_prize = world.get_location('Eastern Palace - Prize', player)
ep_prize = multiworld.get_location('Eastern Palace - Prize', player)
add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and
state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and
ep_prize.parent_region.dungeon.boss.can_defeat(state))
if not world.enemy_shuffle[player]:
if not multiworld.enemy_shuffle[player]:
add_rule(ep_boss, lambda state: can_shoot_arrows(state, player))
add_rule(ep_prize, lambda state: can_shoot_arrows(state, player))
# You can always kill the Stalfos' with the pots on easy/normal
if world.enemy_health[player] in ("hard", "expert") or world.enemy_shuffle[player]:
if multiworld.enemy_health[player] in ("hard", "expert") or multiworld.enemy_shuffle[player]:
stalfos_rule = lambda state: can_kill_most_things(state, player, 4)
for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
'Eastern Palace - Big Key Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize']:
add_rule(world.get_location(location, player), stalfos_rule)
add_rule(multiworld.get_location(location, player), stalfos_rule)
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player))
set_rule(multiworld.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
set_rule(multiworld.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player))
set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4))
set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3))
set_rule(world.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player, 4))
set_rule(world.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player, 4))
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
add_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
set_rule(multiworld.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4))
set_rule(multiworld.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3))
set_rule(multiworld.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player, 4))
set_rule(multiworld.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player, 4))
add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
add_rule(multiworld.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
# logic patch to prevent placing a crystal in Desert that's required to reach the required keys
if not (world.small_key_shuffle[player] and world.big_key_shuffle[player]):
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
if not (multiworld.small_key_shuffle[player] and multiworld.big_key_shuffle[player]):
add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
if world.enemy_shuffle[player]:
add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3))
set_rule(multiworld.get_location('Tower of Hera - Basement Cage', player), lambda state: can_activate_crystal_switch(state, player))
set_rule(multiworld.get_location('Tower of Hera - Map Chest', player), lambda state: can_activate_crystal_switch(state, player))
set_rule(multiworld.get_entrance('Tower of Hera Small Key Door', player), lambda state: can_activate_crystal_switch(state, player) and (state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)))
set_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_activate_crystal_switch(state, player) and state.has('Big Key (Tower of Hera)', player))
if multiworld.enemy_shuffle[player]:
add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3))
else:
add_rule(world.get_entrance('Tower of Hera Big Key Door', player),
add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player),
lambda state: (has_melee_weapon(state, player) or (state.has('Silver Bow', player)
and can_shoot_arrows(state, player)) or state.has("Cane of Byrna", player)
or state.has("Cane of Somaria", player)))
set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player))
if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player)
set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player))
if multiworld.accessibility[player] != 'locations':
set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player)
set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player))
set_rule(world.get_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2))
set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3))
set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
if world.pot_shuffle[player]:
set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
set_rule(multiworld.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player))
set_rule(multiworld.get_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2))
set_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3))
set_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
if multiworld.pot_shuffle[player]:
# it could move the key to the top right platform which can only be reached with bombs
add_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)
add_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)
if state.has('Hookshot', player)
else state._lttp_has_key('Small Key (Swamp Palace)', player, 4))
set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
if world.accessibility[player] != 'locations':
allow_self_locking_items(world.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)')
set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5))
if not world.small_key_shuffle[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
forbid_item(world.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player)
set_rule(world.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
set_rule(world.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
if world.pot_shuffle[player]:
set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
if multiworld.accessibility[player] != 'locations':
allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)')
set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5))
if not multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
forbid_item(multiworld.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player)
set_rule(multiworld.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
set_rule(multiworld.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
if multiworld.pot_shuffle[player]:
# key can (and probably will) be moved behind bombable wall
set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player))
set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player))
if world.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind":
set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player))
if multiworld.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind":
set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player))
set_rule(world.get_location('Thieves\' Town - Big Chest', player),
set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player),
lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player))
if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
set_rule(world.get_location('Thieves\' Town - Spike Switch Pot Key', player),
if multiworld.accessibility[player] != 'locations' and not multiworld.key_drop_shuffle[player]:
set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player),
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
# We need so many keys in the SW doors because they are all reachable as the last door (except for the door to mothula)
set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player))
if world.accessibility[player] != 'locations':
allow_self_locking_items(world.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)')
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
add_rule(world.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
add_rule(world.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player))
if multiworld.accessibility[player] != 'locations':
allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)')
set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
add_rule(multiworld.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player))
set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player))
set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player))
set_rule(multiworld.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player))
set_rule(multiworld.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player))
set_rule(multiworld.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2))
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5))))
set_rule(multiworld.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2))
set_rule(multiworld.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
set_rule(multiworld.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5))))
# This is a complicated rule, so let's break it down.
# Hookshot always suffices to get to the right side.
# Also, once you get over there, you have to cross the spikes, so that's the last line.
@@ -436,102 +448,104 @@ def global_rules(world, player):
# Hence if big key is available then it's 6 keys, otherwise 4 keys.
# If key_drop is off, then we have 3 drop keys available, and can never satisfy the 6 key requirement because one key is on right side,
# so this reduces perfectly to original logic.
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or
(state._lttp_has_key('Small Key (Ice Palace)', player, 4)
set_rule(multiworld.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or
(state._lttp_has_key('Small Key (Ice Palace)', player, 4)
if item_name_in_location_names(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player),
('Ice Palace - Hammer Block Key Drop', player),
('Ice Palace - Big Key Chest', player),
('Ice Palace - Map Chest', player)])
else state._lttp_has_key('Small Key (Ice Palace)', player, 6))) and
(state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player))
else state._lttp_has_key('Small Key (Ice Palace)', player, 6))) and (
world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
set_rule(multiworld.get_entrance('Ice Palace (East Top)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player))
set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (has_sword(state, player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or can_shoot_arrows(state, player))) # need to defeat wizzrobes, bombs don't work ...
set_rule(world.get_location('Misery Mire - Fishbone Pot Key', player), lambda state: state.has('Big Key (Misery Mire)', player) or state._lttp_has_key('Small Key (Misery Mire)', player, 4))
set_rule(multiworld.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (has_sword(state, player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or can_shoot_arrows(state, player))) # need to defeat wizzrobes, bombs don't work ...
set_rule(multiworld.get_location('Misery Mire - Fishbone Pot Key', player), lambda state: state.has('Big Key (Misery Mire)', player) or state._lttp_has_key('Small Key (Misery Mire)', player, 4))
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player))
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.multiworld.can_take_damage[player] and has_hearts(state, player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player))
set_rule(multiworld.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player))
set_rule(multiworld.get_location('Misery Mire - Spike Chest', player), lambda state: (world.can_take_damage and has_hearts(state, player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
set_rule(multiworld.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player))
# How to access crystal switch:
# If have big key: then you will need 2 small keys to be able to hit switch and return to main area, as you can burn key in dark room
# If not big key: cannot burn key in dark room, hence need only 1 key. all doors immediately available lead to a crystal switch.
# The listed chests are those which can be reached if you can reach a crystal switch.
set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
set_rule(multiworld.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
set_rule(multiworld.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
set_rule(world.get_location('Misery Mire - Conveyor Crystal Key Drop', player),
set_rule(multiworld.get_location('Misery Mire - Conveyor Crystal Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 4)
if location_item_name(state, 'Misery Mire - Compass Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Big Key Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Conveyor Crystal Key Drop', player) == ('Big Key (Misery Mire)', player)
else state._lttp_has_key('Small Key (Misery Mire)', player, 5))
set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 5)
set_rule(multiworld.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 5)
if ((location_item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or (location_item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)]))
else state._lttp_has_key('Small Key (Misery Mire)', player, 6))
set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player))
set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player))
set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, player))
set_rule(multiworld.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player))
set_rule(multiworld.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player))
set_rule(multiworld.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_location('Turtle Rock - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
set_rule(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player)))
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10))
set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player)
or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player))
set_rule(world.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10))
set_rule(multiworld.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_location('Turtle Rock - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
set_rule(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
set_rule(multiworld.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(multiworld.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(multiworld.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player)))
set_rule(multiworld.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(multiworld.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10))
set_rule(multiworld.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player)
or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player))
set_rule(multiworld.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(multiworld.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
set_rule(multiworld.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10))
if not world.worlds[player].fix_trock_doors:
add_rule(world.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player))
if not multiworld.worlds[player].fix_trock_doors:
add_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player))
if world.enemy_shuffle[player]:
set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3))
if multiworld.enemy_shuffle[player]:
set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3))
else:
set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player))
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area
set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player))
set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))
set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player))
set_rule(world.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player))
if world.pot_shuffle[player]:
set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player))
set_rule(multiworld.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player))
set_rule(multiworld.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area
set_rule(multiworld.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player))
set_rule(multiworld.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))
set_rule(multiworld.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player))
set_rule(multiworld.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player))
if multiworld.pot_shuffle[player]:
# chest switch may be up on ledge where bombs are required
set_rule(world.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3))))
if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
if multiworld.accessibility[player] != 'locations':
set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)))
if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
if multiworld.accessibility[player] != 'locations':
set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
set_rule(world.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6))
set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6))
# these key rules are conservative, you might be able to get away with more lenient rules
randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right']
compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - Conveyor Star Pits Pot Key']
back_chests = ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']
set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player))
set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player))
set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
if multiworld.pot_shuffle[player]:
set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
# this seemed to be causing generation failure, disable for now
@@ -540,63 +554,63 @@ def global_rules(world, player):
# It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements.
# However we need to leave these at the lower values to derive that with 7 keys it is always possible to reach Bob and Ice Armos.
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 6))
set_rule(multiworld.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 6))
# It is possible to need more than 7 keys ....
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
set_rule(multiworld.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests + back_chests, [player] * len(randomizer_room_chests + back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
# The actual requirements for these rooms to avoid key-lock
set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or
((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
set_rule(multiworld.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or
((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
for location in randomizer_room_chests:
set_rule(world.get_location(location, player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
set_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))))
# Once again it is possible to need more than 7 keys...
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
set_rule(multiworld.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))))
set_rule(world.get_entrance('Ganons Tower (Bottom) (East)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
set_rule(multiworld.get_entrance('Ganons Tower (Bottom) (East)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(back_chests, [player] * len(back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
# Actual requirements
for location in compass_room_chests:
set_rule(world.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
set_rule(multiworld.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))))
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
set_rule(multiworld.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player),
set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Left', player),
lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
set_rule(world.get_location('Ganons Tower - Big Key Chest', player),
set_rule(multiworld.get_location('Ganons Tower - Big Key Chest', player),
lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player),
set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Right', player),
lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
if world.enemy_shuffle[player]:
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
if multiworld.enemy_shuffle[player]:
set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player),
lambda state: state.has('Big Key (Ganons Tower)', player))
else:
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player),
lambda state: state.has('Big Key (Ganons Tower)', player) and can_shoot_arrows(state, player))
set_rule(world.get_entrance('Ganons Tower Torch Rooms', player),
set_rule(multiworld.get_entrance('Ganons Tower Torch Rooms', player),
lambda state: can_kill_most_things(state, player, 8) and has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
set_rule(world.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1))
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player),
set_rule(multiworld.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1))
set_rule(multiworld.get_location('Ganons Tower - Pre-Moldorm Chest', player),
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7))
set_rule(world.get_entrance('Ganons Tower Moldorm Door', player),
set_rule(multiworld.get_entrance('Ganons Tower Moldorm Door', player),
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8))
set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player),
set_rule(multiworld.get_entrance('Ganons Tower Moldorm Gap', player),
lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
ganon = world.get_location('Ganon', player)
set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player))
ganon = multiworld.get_location('Ganon', player)
set_rule(ganon, lambda state: GanonDefeatRule(state, player))
if world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
if multiworld.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
add_rule(ganon, lambda state: has_triforce_pieces(state, player))
elif world.goal[player] == 'ganon_pedestal':
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
elif multiworld.goal[player] == 'ganon_pedestal':
add_rule(multiworld.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
else:
add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player))
set_rule(world.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop
set_rule(multiworld.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop
set_rule(world.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player))
set_rule(multiworld.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player))
def default_rules(world, player):
@@ -895,7 +909,6 @@ def no_glitches_rules(world, player):
add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
forbid_bomb_jump_requirements(world, player)
add_conditional_lamps(world, player)
def fake_flipper_rules(world, player):
@@ -923,12 +936,20 @@ def fake_flipper_rules(world, player):
set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Moon Pearl', player))
def forbid_bomb_jump_requirements(world, player):
def bomb_jump_requirements(multiworld, player):
DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
for location in DMs_room_chests:
add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False)
add_rule(multiworld.get_location(location, player), lambda state: can_use_bombs(state, player), combine="or")
set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: can_use_bombs(state, player))
def forbid_bomb_jump_requirements(multiworld, player):
DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
for location in DMs_room_chests:
add_rule(multiworld.get_location(location, player), lambda state: state.has('Hookshot', player))
set_rule(multiworld.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
set_rule(multiworld.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False)
DW_Entrances = ['Bumper Cave (Bottom)',
@@ -1007,9 +1028,6 @@ def add_conditional_lamps(world, player):
def open_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player),
lambda state: can_kill_most_things(state, player, 1))
def basement_key_rule(state):
if location_item_name(state, 'Sewers - Key Rat Key Drop', player) == ("Small Key (Hyrule Castle)", player):
return state._lttp_has_key("Small Key (Hyrule Castle)", player, 2)
@@ -1018,7 +1036,7 @@ def open_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 2))
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), basement_key_rule)
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) and can_kill_most_things(state, player, 1))
@@ -1026,8 +1044,10 @@ def open_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and
state.has('Big Key (Hyrule Castle)', player))
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)
and state.has('Big Key (Hyrule Castle)', player)
and (world.enemy_health[player] in ("easy", "default")
or can_kill_most_things(state, player, 1)))
def swordless_rules(world, player):
@@ -1057,6 +1077,7 @@ def add_connection(parent_name, target_name, entrance_name, world, player):
parent.exits.append(connection)
connection.connect(target)
def standard_rules(world, player):
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
world.get_entrance('Uncle S&Q', player).hide_path = True
@@ -1068,18 +1089,23 @@ def standard_rules(world, player):
if world.small_key_shuffle[player] != small_key_shuffle.option_universal:
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
and can_kill_most_things(state, player, 2))
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)
and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and
state.has('Big Key (Hyrule Castle)', player))
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)
and state.has('Big Key (Hyrule Castle)', player)
and (world.enemy_health[player] in ("easy", "default")
or can_kill_most_things(state, player, 1)))
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3)
and can_kill_most_things(state, player, 1))
else:
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state.has('Big Key (Hyrule Castle)', player))
@@ -1106,14 +1132,10 @@ def set_trock_key_rules(world, player):
all_state.stale[player] = True
# Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon.
can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge[player] is None else world.can_access_trock_eyebridge[player]
world.can_access_trock_eyebridge[player] = can_reach_back
can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) if world.can_access_trock_front[player] is None else world.can_access_trock_front[player]
world.can_access_trock_front[player] = can_reach_front
can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) if world.can_access_trock_big_chest[player] is None else world.can_access_trock_big_chest[player]
world.can_access_trock_big_chest[player] = can_reach_big_chest
can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) if world.can_access_trock_middle[player] is None else world.can_access_trock_middle[player]
world.can_access_trock_middle[player] = can_reach_middle
can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player))
can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player))
can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player))
can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player))
# If you can't enter from the back, the door to the front of TR requires only 2 small keys if the big key is in one of these chests since 2 key doors are locked behind the big key door.
# If you can only enter from the middle, this includes all locations that can only be reached by exiting the front. This can include Laser Bridge and Crystaroller if the front and back connect via Dark DM Ledge!

View File

@@ -30,7 +30,7 @@ def can_shoot_arrows(state: CollectionState, player: int) -> bool:
def has_triforce_pieces(state: CollectionState, player: int) -> bool:
count = state.multiworld.treasure_hunt_count[player]
count = state.multiworld.worlds[player].treasure_hunt_required
return state.count('Triforce Piece', player) + state.count('Power Star', player) >= count
@@ -48,8 +48,8 @@ def can_lift_heavy_rocks(state: CollectionState, player: int) -> bool:
def bottle_count(state: CollectionState, player: int) -> int:
return min(state.multiworld.difficulty_requirements[player].progressive_bottle_limit,
state.count_group("Bottles", player))
return min(state.multiworld.worlds[player].difficulty_requirements.progressive_bottle_limit,
state.count_group("Bottles", player))
def has_hearts(state: CollectionState, player: int, count: int) -> int:
@@ -59,7 +59,7 @@ def has_hearts(state: CollectionState, player: int, count: int) -> int:
def heart_count(state: CollectionState, player: int) -> int:
# Warning: This only considers items that are marked as advancement items
diff = state.multiworld.difficulty_requirements[player]
diff = state.multiworld.worlds[player].difficulty_requirements
return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \
+ state.count('Sanctuary Heart Container', player) \
+ min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
@@ -106,6 +106,12 @@ def can_bomb_or_bonk(state: CollectionState, player: int) -> bool:
return state.has("Pegasus Boots", player) or can_use_bombs(state, player)
def can_activate_crystal_switch(state: CollectionState, player: int) -> bool:
return (has_melee_weapon(state, player) or can_use_bombs(state, player) or can_shoot_arrows(state, player)
or state.has_any(["Hookshot", "Cane of Somaria", "Cane of Byrna", "Fire Rod", "Ice Rod", "Blue Boomerang",
"Red Boomerang"], player))
def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool:
if state.multiworld.enemy_shuffle[player]:
# I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any.
@@ -171,10 +177,11 @@ def can_melt_things(state: CollectionState, player: int) -> bool:
def has_misery_mire_medallion(state: CollectionState, player: int) -> bool:
return state.has(state.multiworld.required_medallions[player][0], player)
return state.has(state.multiworld.worlds[player].required_medallions[0], player)
def has_turtle_rock_medallion(state: CollectionState, player: int) -> bool:
return state.has(state.multiworld.required_medallions[player][1], player)
return state.has(state.multiworld.worlds[player].required_medallions[1], player)
def can_boots_clip_lw(state: CollectionState, player: int) -> bool:

View File

@@ -15,7 +15,7 @@ def underworld_glitch_connections(world, player):
specrock.exits.append(kikiskip)
mire.exits.extend([mire_to_hera, mire_to_swamp])
if world.fix_fake_world[player]:
if world.worlds[player].fix_fake_world:
kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region)
mire_to_hera.connect(world.get_entrance('Tower of Hera Exit', player).connected_region)
mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit', player).connected_region)
@@ -38,8 +38,8 @@ def fake_pearl_state(state, player):
# Sets the rules on where we can actually go using this clip.
# Behavior differs based on what type of ER shuffle we're playing.
def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str):
fix_dungeon_exits = world.fix_palaceofdarkness_exit[player]
fix_fake_worlds = world.fix_fake_world[player]
fix_dungeon_exits = world.worlds[player].fix_palaceofdarkness_exit
fix_fake_worlds = world.worlds[player].fix_fake_world
dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0]
if not fix_dungeon_exits: # vanilla, simple, restricted, dungeons_simple; should never have fake worlds fix
@@ -52,7 +52,7 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du
add_rule(clip, lambda state: state.has('Cape', player) or has_beam_sword(state, player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier
# Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally.
add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix
elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix
# Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region.
add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player)))
# exiting restriction
@@ -62,9 +62,6 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du
def underworld_glitches_rules(world, player):
fix_dungeon_exits = world.fix_palaceofdarkness_exit[player]
fix_fake_worlds = world.fix_fake_world[player]
# Ice Palace Entrance Clip
# This is the easiest one since it's a simple internal clip.
# Need to also add melting to freezor chest since it's otherwise assumed.
@@ -92,7 +89,7 @@ def underworld_glitches_rules(world, player):
# Build the rule for SP moat.
# We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT.
# First we require a certain type of entrance shuffle, then build the rule from its pieces.
if not world.swamp_patch_required[player]:
if not world.worlds[player].swamp_patch_required:
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rule_map = {
'Misery Mire (Entrance)': (lambda state: True),

View File

@@ -251,6 +251,18 @@ class ALTTPWorld(World):
dungeons: typing.Dict[str, Dungeon]
waterfall_fairy_bottle_fill: str
pyramid_fairy_bottle_fill: str
escape_assist: list
can_take_damage: bool = True
swamp_patch_required: bool = False
powder_patch_required: bool = False
ganon_at_pyramid: bool = True
ganonstower_vanilla: bool = True
fix_fake_world: bool = True
clock_mode: str = ""
treasure_hunt_required: int = 0
treasure_hunt_total: int = 0
def __init__(self, *args, **kwargs):
self.dungeon_local_item_names = set()
@@ -265,6 +277,8 @@ class ALTTPWorld(World):
self.fix_skullwoods_exit = None
self.fix_palaceofdarkness_exit = None
self.fix_trock_exit = None
self.required_medallions = ["Ether", "Quake"]
self.escape_assist = []
super(ALTTPWorld, self).__init__(*args, **kwargs)
@classmethod
@@ -298,7 +312,7 @@ class ALTTPWorld(World):
"Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)",
"Bottle (Bee)", "Bottle (Good Bee)"
]
if multiworld.difficulty[player] not in ["hard", "expert"]:
if multiworld.item_pool[player] not in ["hard", "expert"]:
bottle_options.append("Bottle (Fairy)")
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
@@ -344,7 +358,7 @@ class ALTTPWorld(World):
if option == "original_dungeon":
self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group]
multiworld.difficulty_requirements[player] = difficulties[multiworld.item_pool[player].current_key]
self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key]
# enforce pre-defined local items.
if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
@@ -370,7 +384,7 @@ class ALTTPWorld(World):
if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and
multiworld.entrance_shuffle[player] in [
"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]):
multiworld.fix_fake_world[player] = False
self.fix_fake_world = False
# seeded entrance shuffle
old_random = multiworld.random
@@ -438,15 +452,16 @@ class ALTTPWorld(World):
if 'Sword' in item_name:
if state.has('Golden Sword', item.player):
pass
elif state.has('Tempered Sword', item.player) and self.multiworld.difficulty_requirements[
item.player].progressive_sword_limit >= 4:
elif (state.has('Tempered Sword', item.player) and
self.difficulty_requirements.progressive_sword_limit >= 4):
return 'Golden Sword'
elif state.has('Master Sword', item.player) and self.multiworld.difficulty_requirements[
item.player].progressive_sword_limit >= 3:
elif (state.has('Master Sword', item.player) and
self.difficulty_requirements.progressive_sword_limit >= 3):
return 'Tempered Sword'
elif state.has('Fighter Sword', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 2:
elif (state.has('Fighter Sword', item.player) and
self.difficulty_requirements.progressive_sword_limit >= 2):
return 'Master Sword'
elif self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 1:
elif self.difficulty_requirements.progressive_sword_limit >= 1:
return 'Fighter Sword'
elif 'Glove' in item_name:
if state.has('Titans Mitts', item.player):
@@ -458,20 +473,22 @@ class ALTTPWorld(World):
elif 'Shield' in item_name:
if state.has('Mirror Shield', item.player):
return
elif state.has('Red Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 3:
elif (state.has('Red Shield', item.player) and
self.difficulty_requirements.progressive_shield_limit >= 3):
return 'Mirror Shield'
elif state.has('Blue Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 2:
elif (state.has('Blue Shield', item.player) and
self.difficulty_requirements.progressive_shield_limit >= 2):
return 'Red Shield'
elif self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 1:
elif self.difficulty_requirements.progressive_shield_limit >= 1:
return 'Blue Shield'
elif 'Bow' in item_name:
if state.has('Silver Bow', item.player):
return
elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2
or self.multiworld.glitches_required[item.player] == 'no_glitches'
or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon
elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2
or self.multiworld.glitches_required[self.player] == 'no_glitches'
or self.multiworld.swordless[self.player]): # modes where silver bow is always required for ganon
return 'Silver Bow'
elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1:
elif self.difficulty_requirements.progressive_bow_limit >= 1:
return 'Bow'
elif item.advancement:
return item_name
@@ -660,7 +677,7 @@ class ALTTPWorld(World):
trash_counts = {}
for player in multiworld.get_game_players("A Link to the Past"):
world = multiworld.worlds[player]
if not multiworld.ganonstower_vanilla[player] or \
if not world.ganonstower_vanilla or \
world.options.glitches_required.current_key in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}:
pass
elif 'triforce_hunt' in world.options.goal.current_key and ('local' in world.options.goal.current_key or multiworld.players == 1):
@@ -701,10 +718,10 @@ class ALTTPWorld(World):
player_name = self.multiworld.get_player_name(self.player)
spoiler_handle.write("\n\nMedallions:\n")
spoiler_handle.write(f"\nMisery Mire ({player_name}):"
f" {self.multiworld.required_medallions[self.player][0]}")
f" {self.required_medallions[0]}")
spoiler_handle.write(
f"\nTurtle Rock ({player_name}):"
f" {self.multiworld.required_medallions[self.player][1]}")
f" {self.required_medallions[1]}")
spoiler_handle.write("\n\nFairy Fountain Bottle Fill:\n")
spoiler_handle.write(f"\nPyramid Fairy ({player_name}):"
f" {self.pyramid_fairy_bottle_fill}")
@@ -815,8 +832,8 @@ class ALTTPWorld(World):
slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options}
slot_data.update({
'mm_medalion': self.multiworld.required_medallions[self.player][0],
'tr_medalion': self.multiworld.required_medallions[self.player][1],
'mm_medalion': self.required_medallions[0],
'tr_medalion': self.required_medallions[1],
}
)
return slot_data

View File

@@ -7,7 +7,9 @@ from worlds import AutoWorldRegister
class LTTPTestBase(unittest.TestCase):
def world_setup(self):
from worlds.alttp.Options import Medallion
self.multiworld = MultiWorld(1)
self.multiworld.game[1] = "A Link to the Past"
self.multiworld.state = CollectionState(self.multiworld)
self.multiworld.set_seed(None)
args = Namespace()
@@ -15,3 +17,6 @@ class LTTPTestBase(unittest.TestCase):
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
self.multiworld.set_options(args)
self.world = self.multiworld.worlds[1]
# by default medallion access is randomized, for unittests we set it to vanilla
self.world.options.misery_mire_medallion.value = Medallion.option_ether
self.world.options.turtle_rock_medallion.value = Medallion.option_quake

View File

@@ -13,9 +13,9 @@ class TestDungeon(LTTPTestBase):
self.world_setup()
self.starting_regions = [] # Where to start exploring
self.remove_exits = [] # Block dungeon exits
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2
create_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
create_shops(self.multiworld, 1)
@@ -23,7 +23,7 @@ class TestDungeon(LTTPTestBase):
connect_simple(self.multiworld, exitname, regionname, 1)
connect_simple(self.multiworld, 'Big Bomb Shop', 'Big Bomb Shop', 1)
self.multiworld.get_region('Menu', 1).exits = []
self.multiworld.swamp_patch_required[1] = True
self.multiworld.worlds[1].swamp_patch_required = True
self.world.set_rules()
self.world.create_items()
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))

View File

@@ -33,22 +33,26 @@ class TestGanonsTower(TestDungeon):
["Ganons Tower - Randomizer Room - Top Left", False, []],
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']],
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']],
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Top Right", False, []],
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']],
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']],
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Bottom Left", False, []],
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']],
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']],
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Bottom Right", False, []],
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']],
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']],
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Bomb Upgrade (50)']],
["Ganons Tower - Firesnake Room", False, []],
["Ganons Tower - Firesnake Room", False, [], ['Hammer']],

View File

@@ -9,12 +9,16 @@ class TestTowerOfHera(TestDungeon):
["Tower of Hera - Big Key Chest", False, []],
["Tower of Hera - Big Key Chest", False, [], ['Small Key (Tower of Hera)']],
["Tower of Hera - Big Key Chest", False, [], ['Lamp', 'Fire Rod']],
["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Lamp']],
["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Lamp', 'Bomb Upgrade (50)']],
["Tower of Hera - Big Key Chest", True, ['Small Key (Tower of Hera)', 'Fire Rod']],
["Tower of Hera - Basement Cage", True, []],
["Tower of Hera - Basement Cage", False, []],
["Tower of Hera - Basement Cage", True, ['Bomb Upgrade (50)']],
["Tower of Hera - Basement Cage", True, ['Progressive Sword']],
["Tower of Hera - Map Chest", True, []],
["Tower of Hera - Map Chest", False, []],
["Tower of Hera - Map Chest", True, ['Bomb Upgrade (50)']],
["Tower of Hera - Map Chest", True, ['Progressive Sword']],
["Tower of Hera - Compass Chest", False, []],
["Tower of Hera - Compass Chest", False, [], ['Big Key (Tower of Hera)']],

View File

@@ -13,16 +13,15 @@ from worlds.alttp.test import LTTPTestBase
class TestInverted(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.mode[1].value = 2
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2
create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons()
create_shops(self.multiworld, 1)
link_inverted_entrances(self.multiworld, 1)
self.world.create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world))
self.multiworld.get_location('Agahnim 1', 1).item = None

View File

@@ -11,7 +11,7 @@ class TestInvertedBombRules(LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.mode[1].value = 2
create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()

View File

@@ -17,14 +17,13 @@ class TestInvertedMinor(TestBase, LTTPTestBase):
self.multiworld.mode[1].value = 2
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.shuffle_capacity_upgrades[1].value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons()
create_shops(self.multiworld, 1)
link_inverted_entrances(self.multiworld, 1)
self.world.create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world))
self.multiworld.get_location('Agahnim 1', 1).item = None

View File

@@ -41,7 +41,8 @@ class TestDungeons(TestInvertedOWG):
["Tower of Hera - Basement Cage", False, []],
["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl']],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl', 'Bomb Upgrade (50)']],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Sword']],
["Castle Tower - Room 03", False, []],
["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],

View File

@@ -17,14 +17,13 @@ class TestInvertedOWG(TestBase, LTTPTestBase):
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
self.multiworld.mode[1].value = 2
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.shuffle_capacity_upgrades[1].value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
create_inverted_regions(self.multiworld, 1)
self.world.create_dungeons()
create_shops(self.multiworld, 1)
link_inverted_entrances(self.multiworld, 1)
self.world.create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world))
self.multiworld.get_location('Agahnim 1', 1).item = None

View File

@@ -13,12 +13,11 @@ class TestMinor(TestBase, LTTPTestBase):
self.world_setup()
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.shuffle_capacity_upgrades[1].value = 2
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.world.er_seed = 0
self.world.create_regions()
self.world.create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(
['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1',

View File

@@ -34,11 +34,11 @@ class TestDungeons(TestVanillaOWG):
["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Flute", "Lamp"]],
["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Magic Mirror", "Hammer"]],
["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Magic Mirror", "Hookshot"]],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots']],
["Tower of Hera - Basement Cage", True, ["Flute", "Magic Mirror"]],
["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror"]],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Bomb Upgrade (50)']],
["Tower of Hera - Basement Cage", True, ["Flute", "Magic Mirror", 'Bomb Upgrade (50)']],
["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror", 'Bomb Upgrade (50)']],
["Tower of Hera - Basement Cage", True, ["Flute", "Hookshot", "Hammer"]],
["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror"]],
["Tower of Hera - Basement Cage", True, ["Progressive Glove", "Lamp", "Magic Mirror", 'Bomb Upgrade (50)']],
["Castle Tower - Room 03", False, []],
["Castle Tower - Room 03", False, ['Progressive Sword'], ['Progressive Sword', 'Cape', 'Beat Agahnim 1']],

View File

@@ -11,14 +11,13 @@ from worlds.alttp.test import LTTPTestBase
class TestVanillaOWG(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2
self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world))
self.multiworld.get_location('Agahnim 1', 1).item = None

View File

@@ -11,13 +11,12 @@ class TestVanilla(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches")
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
self.multiworld.bombless_start[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.shuffle_capacity_upgrades[1].value = 2
self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items()
self.multiworld.required_medallions[1] = ['Ether', 'Quake']
self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld))
self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world))
self.multiworld.get_location('Agahnim 1', 1).item = None

View File

@@ -1,6 +1,38 @@
# Celeste 64 - Changelog
## v1.2
### Features:
- New optional Location Checks
- Friendsanity
- Signsanity
- Carsanity
- Move Shuffle
- Basic movement abilities can be shuffled into the item pool
- Ground Dash
- Air Dash
- Skid Jump
- Climb
- Logic Difficulty
- Completely overhauled logic system
- Standard or Hard logic difficulty can be chosen
- Badeline Chasers
- Opt-in options which cause Badelines to start following you as you play, which will kill on contact
- These can be set to spawn based on either:
- The number of locations you've checked
- The number of Strawberry items you've received
- How fast they follow behind you can be specified
### Quality of Life:
- The maximum number of Strawberries in the item pool can be directly set
- The required amount of Strawberries is now set via percentage
- All items beyond the amount placed in the item pool will be `Raspberry` items, which have no effect
- Any unique items placed into the `start_inventory` will not be placed into the item pool
## v1.1 - First Stable Release
### Features:

View File

@@ -16,43 +16,29 @@ class Celeste64ItemData(NamedTuple):
type: ItemClassification = ItemClassification.filler
item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.strawberry: Celeste64ItemData(
code = celeste_64_base_id + 0,
type=ItemClassification.progression_skip_balancing,
),
ItemName.dash_refill: Celeste64ItemData(
code = celeste_64_base_id + 1,
type=ItemClassification.progression,
),
ItemName.double_dash_refill: Celeste64ItemData(
code = celeste_64_base_id + 2,
type=ItemClassification.progression,
),
ItemName.feather: Celeste64ItemData(
code = celeste_64_base_id + 3,
type=ItemClassification.progression,
),
ItemName.coin: Celeste64ItemData(
code = celeste_64_base_id + 4,
type=ItemClassification.progression,
),
ItemName.cassette: Celeste64ItemData(
code = celeste_64_base_id + 5,
type=ItemClassification.progression,
),
ItemName.traffic_block: Celeste64ItemData(
code = celeste_64_base_id + 6,
type=ItemClassification.progression,
),
ItemName.spring: Celeste64ItemData(
code = celeste_64_base_id + 7,
type=ItemClassification.progression,
),
ItemName.breakables: Celeste64ItemData(
code = celeste_64_base_id + 8,
type=ItemClassification.progression,
)
collectable_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.strawberry: Celeste64ItemData(celeste_64_base_id + 0x0, ItemClassification.progression_skip_balancing),
ItemName.raspberry: Celeste64ItemData(celeste_64_base_id + 0x9, ItemClassification.filler),
}
unlockable_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.dash_refill: Celeste64ItemData(celeste_64_base_id + 0x1, ItemClassification.progression),
ItemName.double_dash_refill: Celeste64ItemData(celeste_64_base_id + 0x2, ItemClassification.progression),
ItemName.feather: Celeste64ItemData(celeste_64_base_id + 0x3, ItemClassification.progression),
ItemName.coin: Celeste64ItemData(celeste_64_base_id + 0x4, ItemClassification.progression),
ItemName.cassette: Celeste64ItemData(celeste_64_base_id + 0x5, ItemClassification.progression),
ItemName.traffic_block: Celeste64ItemData(celeste_64_base_id + 0x6, ItemClassification.progression),
ItemName.spring: Celeste64ItemData(celeste_64_base_id + 0x7, ItemClassification.progression),
ItemName.breakables: Celeste64ItemData(celeste_64_base_id + 0x8, ItemClassification.progression),
}
move_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.ground_dash: Celeste64ItemData(celeste_64_base_id + 0xA, ItemClassification.progression),
ItemName.air_dash: Celeste64ItemData(celeste_64_base_id + 0xB, ItemClassification.progression),
ItemName.skid_jump: Celeste64ItemData(celeste_64_base_id + 0xC, ItemClassification.progression),
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
}
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table}
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}

View File

@@ -16,127 +16,67 @@ class Celeste64LocationData(NamedTuple):
address: Optional[int] = None
location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.strawberry_1 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 0,
),
LocationName.strawberry_2 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 1,
),
LocationName.strawberry_3 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 2,
),
LocationName.strawberry_4 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 3,
),
LocationName.strawberry_5 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 4,
),
LocationName.strawberry_6 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 5,
),
LocationName.strawberry_7 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 6,
),
LocationName.strawberry_8 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 7,
),
LocationName.strawberry_9 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 8,
),
LocationName.strawberry_10 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 9,
),
LocationName.strawberry_11 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 10,
),
LocationName.strawberry_12 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 11,
),
LocationName.strawberry_13 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 12,
),
LocationName.strawberry_14 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 13,
),
LocationName.strawberry_15 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 14,
),
LocationName.strawberry_16 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 15,
),
LocationName.strawberry_17 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 16,
),
LocationName.strawberry_18 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 17,
),
LocationName.strawberry_19 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 18,
),
LocationName.strawberry_20 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 19,
),
LocationName.strawberry_21 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 20,
),
LocationName.strawberry_22 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 21,
),
LocationName.strawberry_23 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 22,
),
LocationName.strawberry_24 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 23,
),
LocationName.strawberry_25 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 24,
),
LocationName.strawberry_26 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 25,
),
LocationName.strawberry_27 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 26,
),
LocationName.strawberry_28 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 27,
),
LocationName.strawberry_29 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 28,
),
LocationName.strawberry_30 : Celeste64LocationData(
region = "Forsaken City",
address = celeste_64_base_id + 29,
)
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00),
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01),
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02),
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03),
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04),
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05),
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06),
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07),
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08),
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09),
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A),
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B),
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C),
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D),
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E),
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F),
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10),
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11),
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12),
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13),
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14),
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15),
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16),
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17),
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18),
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19),
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A),
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B),
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C),
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D),
}
friend_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00),
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01),
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02),
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03),
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04),
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05),
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06),
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07),
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08),
}
sign_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00),
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01),
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02),
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03),
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04),
}
car_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00),
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01),
}
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
**friend_location_data_table,
**sign_location_data_table,
**car_location_data_table}
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}

View File

@@ -1,4 +1,5 @@
strawberry = "Strawberry"
raspberry = "Raspberry"
dash_refill = "Dash Refills"
double_dash_refill = "Double Dash Refills"
@@ -9,3 +10,8 @@ cassette = "Cassettes"
traffic_block = "Traffic Blocks"
spring = "Springs"
breakables = "Breakable Blocks"
ground_dash = "Ground Dash"
air_dash = "Air Dash"
skid_jump = "Skid Jump"
climb = "Climb"

View File

@@ -29,3 +29,25 @@ strawberry_27 = "Distant Feather Cassette Strawberry"
strawberry_28 = "Feather Arches Cassette Strawberry"
strawberry_29 = "North-East Tower Cassette Strawberry"
strawberry_30 = "Badeline Cassette Strawberry"
# Friend Locations
granny_1 = "Granny Conversation 1"
granny_2 = "Granny Conversation 2"
granny_3 = "Granny Conversation 3"
theo_1 = "Theo Conversation 1"
theo_2 = "Theo Conversation 2"
theo_3 = "Theo Conversation 3"
badeline_1 = "Badeline Conversation 1"
badeline_2 = "Badeline Conversation 2"
badeline_3 = "Badeline Conversation 3"
# Sign Locations
sign_1 = "Camera Sign"
sign_2 = "Skid Jump Sign"
sign_3 = "Dash Jump Sign"
sign_4 = "Lonely Sign"
sign_5 = "Credits Sign"
# Car Locations
car_1 = "Intro Car"
car_2 = "Secret Car"

View File

@@ -1,25 +1,124 @@
from dataclasses import dataclass
from Options import Range, DeathLink, PerGameCommonOptions
from Options import Choice, Range, Toggle, DeathLink, PerGameCommonOptions
class StrawberriesRequired(Range):
"""How many Strawberries you must receive to finish"""
display_name = "Strawberries Required"
range_start = 0
range_end = 20
default = 15
class DeathLinkAmnesty(Range):
"""How many deaths it takes to send a DeathLink"""
"""
How many deaths it takes to send a DeathLink
"""
display_name = "Death Link Amnesty"
range_start = 1
range_end = 30
default = 10
class TotalStrawberries(Range):
"""
How many Strawberries exist
"""
display_name = "Total Strawberries"
range_start = 0
range_end = 46
default = 20
class StrawberriesRequiredPercentage(Range):
"""
Percentage of existing Strawberries you must receive to finish
"""
display_name = "Strawberries Required Percentage"
range_start = 0
range_end = 100
default = 80
class LogicDifficulty(Choice):
"""
Whether the logic expects you to play the intended way, or to be able to use advanced tricks and skips
"""
display_name = "Logic Difficulty"
option_standard = 0
option_hard = 1
default = 0
class MoveShuffle(Toggle):
"""
Whether the following base movement abilities are shuffled into the item pool:
- Ground Dash
- Air Dash
- Skid Jump
- Climb
NOTE: Having Move Shuffle and Standard Logic Difficulty will guarantee that one of the four Move items will be immediately accessible
WARNING: Combining Move Shuffle and Hard Logic Difficulty can require very difficult tricks
"""
display_name = "Move Shuffle"
class Friendsanity(Toggle):
"""
Whether chatting with your friends grants location checks
"""
display_name = "Friendsanity"
class Signsanity(Toggle):
"""
Whether reading signs grants location checks
"""
display_name = "Signsanity"
class Carsanity(Toggle):
"""
Whether riding on cars grants location checks
"""
display_name = "Carsanity"
class BadelineChaserSource(Choice):
"""
What type of action causes more Badeline Chasers to start spawning
Locations: The number of locations you've checked contributes to Badeline Chasers
Strawberries: The number of Strawberry items you've received contributes to Badeline Chasers
"""
display_name = "Badeline Chaser Source"
option_locations = 0
option_strawberries = 1
default = 0
class BadelineChaserFrequency(Range):
"""
How many of the `Badeline Chaser Source` actions must occur to make each Badeline Chaser start spawning
NOTE: Choosing `0` disables Badeline Chasers entirely
WARNING: Turning on Badeline Chasers alongside Move Shuffle could result in extremely difficult situations
"""
display_name = "Badeline Chaser Frequency"
range_start = 0
range_end = 10
default = 0
class BadelineChaserSpeed(Range):
"""
How many seconds behind you each Badeline Chaser will be
"""
display_name = "Badeline Chaser Speed"
range_start = 2
range_end = 10
default = 3
@dataclass
class Celeste64Options(PerGameCommonOptions):
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty
strawberries_required: StrawberriesRequired
total_strawberries: TotalStrawberries
strawberries_required_percentage: StrawberriesRequiredPercentage
logic_difficulty: LogicDifficulty
move_shuffle: MoveShuffle
friendsanity: Friendsanity
signsanity: Signsanity
carsanity: Carsanity
badeline_chaser_source: BadelineChaserSource
badeline_chaser_frequency: BadelineChaserFrequency
badeline_chaser_speed: BadelineChaserSpeed

View File

@@ -1,3 +1,6 @@
from typing import Dict, List
from BaseClasses import CollectionState
from worlds.generic.Rules import set_rule
from . import Celeste64World
@@ -5,100 +8,336 @@ from .Names import ItemName, LocationName
def set_rules(world: Celeste64World):
set_rule(world.multiworld.get_location(LocationName.strawberry_4, world.player),
lambda state: state.has_all({ItemName.traffic_block,
ItemName.breakables}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_5, world.player),
lambda state: state.has(ItemName.breakables, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_6, world.player),
lambda state: state.has(ItemName.dash_refill, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_8, world.player),
lambda state: state.has(ItemName.traffic_block, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_9, world.player),
lambda state: state.has(ItemName.dash_refill, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_11, world.player),
lambda state: state.has(ItemName.dash_refill, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_12, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.double_dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_13, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.breakables}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_14, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.feather}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_15, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.feather}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_16, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.feather}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_17, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.double_dash_refill,
ItemName.feather,
ItemName.traffic_block}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_18, world.player),
lambda state: state.has(ItemName.double_dash_refill, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_19, world.player),
lambda state: state.has_all({ItemName.double_dash_refill,
ItemName.spring}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_20, world.player),
lambda state: state.has_all({ItemName.dash_refill,
ItemName.feather,
ItemName.breakables}, world.player))
if world.options.logic_difficulty == "standard":
if world.options.move_shuffle:
world.active_logic_mapping = location_standard_moves_logic
else:
world.active_logic_mapping = location_standard_logic
else:
if world.options.move_shuffle:
world.active_logic_mapping = location_hard_moves_logic
else:
world.active_logic_mapping = location_hard_logic
set_rule(world.multiworld.get_location(LocationName.strawberry_21, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.traffic_block,
ItemName.breakables}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_22, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.dash_refill,
ItemName.breakables}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_23, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.dash_refill,
ItemName.coin}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_24, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.traffic_block,
ItemName.dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_25, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.dash_refill,
ItemName.double_dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_26, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_27, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.feather,
ItemName.coin,
ItemName.dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_28, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.feather,
ItemName.coin,
ItemName.dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_29, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.feather,
ItemName.coin,
ItemName.dash_refill}, world.player))
set_rule(world.multiworld.get_location(LocationName.strawberry_30, world.player),
lambda state: state.has_all({ItemName.cassette,
ItemName.feather,
ItemName.traffic_block,
ItemName.spring,
ItemName.breakables,
ItemName.dash_refill,
ItemName.double_dash_refill}, world.player))
for location in world.multiworld.get_locations(world.player):
set_rule(location, lambda state, location=location: location_rule(state, world, location.name))
if world.options.logic_difficulty == "standard":
if world.options.move_shuffle:
world.goal_logic_mapping = goal_standard_moves_logic
else:
world.goal_logic_mapping = goal_standard_logic
else:
if world.options.move_shuffle:
world.goal_logic_mapping = goal_hard_moves_logic
else:
world.goal_logic_mapping = goal_hard_logic
# Completion condition.
world.multiworld.completion_condition[world.player] = lambda state: (state.has(ItemName.strawberry,world.player,world.options.strawberries_required.value) and
state.has_all({ItemName.feather,
ItemName.traffic_block,
ItemName.breakables,
ItemName.dash_refill,
ItemName.double_dash_refill}, world.player))
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world)
goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]]
goal_hard_logic: List[List[str]] = [[]]
goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]]
goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]]
location_standard_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_6: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_7: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_8: [[ItemName.traffic_block]],
LocationName.strawberry_9: [[ItemName.dash_refill]],
LocationName.strawberry_11: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.traffic_block, ItemName.double_dash_refill]],
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables],
[ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]],
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables],
[ItemName.traffic_block, ItemName.feather, ItemName.breakables]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.coin]],
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill],
[ItemName.cassette, ItemName.traffic_block]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]],
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.sign_2: [[ItemName.breakables]],
LocationName.sign_3: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.car_2: [[ItemName.breakables]],
}
location_hard_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_13: [[ItemName.breakables]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
LocationName.strawberry_20: [[ItemName.breakables]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_22: [[ItemName.cassette]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]],
LocationName.strawberry_24: [[ItemName.cassette]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]],
LocationName.strawberry_26: [[ItemName.cassette]],
LocationName.strawberry_27: [[ItemName.cassette]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]],
LocationName.strawberry_29: [[ItemName.cassette]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]],
LocationName.sign_2: [[ItemName.breakables]],
LocationName.car_2: [[ItemName.breakables]],
}
location_standard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.strawberry_2: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_5: [[ItemName.air_dash]],
LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.climb]],
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash],
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]],
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.granny_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.granny_2: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.granny_3: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.sign_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
}
location_hard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_5: [[ItemName.ground_dash],
[ItemName.air_dash]],
LocationName.strawberry_8: [[ItemName.traffic_block],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.air_dash],
[ItemName.climb]],
LocationName.strawberry_11: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_12: [[ItemName.feather],
[ItemName.ground_dash],
[ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
[ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_15: [[ItemName.feather],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
LocationName.strawberry_18: [[ItemName.air_dash, ItemName.climb],
[ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_19: [[ItemName.air_dash, ItemName.skid_jump],
[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
[ItemName.spring, ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_20: [[ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_22: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash],
[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash]],
LocationName.strawberry_24: [[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash],
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.ground_dash],
[ItemName.cassette, ItemName.air_dash]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.air_dash, ItemName.skid_jump],
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash],
[ItemName.cassette, ItemName.coin, ItemName.ground_dash],
[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.air_dash],
[ItemName.cassette, ItemName.feather, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
}
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
if loc not in world.active_logic_mapping:
return True
for possible_access in world.active_logic_mapping[loc]:
if state.has_all(possible_access, world.player):
return True
return False
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
return False
for possible_access in world.goal_logic_mapping:
if state.has_all(possible_access, world.player):
return True
return False

View File

@@ -1,10 +1,12 @@
from typing import List
from copy import deepcopy
from typing import Dict, List
from BaseClasses import ItemClassification, Region, Tutorial
from BaseClasses import ItemClassification, Location, Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import Celeste64Item, item_data_table, item_table
from .Locations import Celeste64Location, location_data_table, location_table
from .Names import ItemName
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
sign_location_data_table, car_location_data_table, location_table
from .Names import ItemName, LocationName
from .Options import Celeste64Options
@@ -27,6 +29,7 @@ class Celeste64World(World):
"""Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer.
Created in a week(ish) by the Celeste team to celebrate the games sixth anniversary 🍓✨"""
# Class Data
game = "Celeste 64"
web = Celeste64WebWorld()
options_dataclass = Celeste64Options
@@ -34,13 +37,18 @@ class Celeste64World(World):
location_name_to_id = location_table
item_name_to_id = item_table
# Instance Data
strawberries_required: int
active_logic_mapping: Dict[str, List[List[str]]]
goal_logic_mapping: Dict[str, List[List[str]]]
def create_item(self, name: str) -> Celeste64Item:
# Only make required amount of strawberries be Progression
if getattr(self, "options", None) and name == ItemName.strawberry:
if getattr(self, "strawberries_required", None) and name == ItemName.strawberry:
classification: ItemClassification = ItemClassification.filler
self.prog_strawberries = getattr(self, "prog_strawberries", 0)
if self.prog_strawberries < self.options.strawberries_required.value:
if self.prog_strawberries < self.strawberries_required:
classification = ItemClassification.progression_skip_balancing
self.prog_strawberries += 1
@@ -51,9 +59,48 @@ class Celeste64World(World):
def create_items(self) -> None:
item_pool: List[Celeste64Item] = []
item_pool += [self.create_item(name) for name in item_data_table.keys()]
location_count: int = 30
item_pool += [self.create_item(ItemName.strawberry) for _ in range(21)]
if self.options.friendsanity:
location_count += 9
if self.options.signsanity:
location_count += 5
if self.options.carsanity:
location_count += 2
item_pool += [self.create_item(name)
for name in unlockable_item_data_table.keys()
if name not in self.options.start_inventory]
if self.options.move_shuffle:
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
if self.options.logic_difficulty == "standard":
# If the start_inventory already includes a move, don't worry about giving it one
if not [move for move in move_items_for_itempool if move in self.options.start_inventory]:
chosen_start_move = self.random.choice(move_items_for_itempool)
move_items_for_itempool.remove(chosen_start_move)
if self.options.carsanity:
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
intro_car_loc.place_locked_item(self.create_item(chosen_start_move))
location_count -= 1
else:
self.multiworld.push_precollected(self.create_item(chosen_start_move))
item_pool += [self.create_item(name)
for name in move_items_for_itempool
if name not in self.options.start_inventory]
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool))
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
item_pool += [self.create_item(ItemName.strawberry) for _ in range(real_total_strawberries)]
filler_item_count: int = location_count - len(item_pool)
item_pool += [self.create_item(ItemName.raspberry) for _ in range(filler_item_count)]
self.multiworld.itempool += item_pool
@@ -69,14 +116,33 @@ class Celeste64World(World):
for region_name, region_data in region_data_table.items():
region = self.multiworld.get_region(region_name, self.player)
region.add_locations({
location_name: location_data.address for location_name, location_data in location_data_table.items()
location_name: location_data.address for location_name, location_data in strawberry_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
if self.options.friendsanity:
region.add_locations({
location_name: location_data.address for location_name, location_data in friend_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
if self.options.signsanity:
region.add_locations({
location_name: location_data.address for location_name, location_data in sign_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
if self.options.carsanity:
region.add_locations({
location_name: location_data.address for location_name, location_data in car_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
region.add_exits(region_data_table[region_name].connecting_regions)
def get_filler_item_name(self) -> str:
return ItemName.strawberry
return ItemName.raspberry
def set_rules(self) -> None:
@@ -88,5 +154,12 @@ class Celeste64World(World):
return {
"death_link": self.options.death_link.value,
"death_link_amnesty": self.options.death_link_amnesty.value,
"strawberries_required": self.options.strawberries_required.value
"strawberries_required": self.strawberries_required,
"move_shuffle": self.options.move_shuffle.value,
"friendsanity": self.options.friendsanity.value,
"signsanity": self.options.signsanity.value,
"carsanity": self.options.carsanity.value,
"badeline_chaser_source": self.options.badeline_chaser_source.value,
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,
}

View File

@@ -3,6 +3,11 @@
## Required Software
- Archipelago Build of Celeste 64 from: [Celeste 64 Archipelago Releases Page](https://github.com/PoryGoneDev/Celeste64/releases/)
## Optional Software
- Celeste 64 Tracker
- PopTracker from: [PopTracker Releases Page](https://github.com/black-sliver/PopTracker/releases/)
- Celeste 64 Archipelago PopTracker pack from: [Celeste 64 AP Tracker Releases Page](https://github.com/PoryGone/Celeste-64-AP-Tracker/releases/)
## Installation Procedures (Windows)
1. Download the above release and extract it.

View File

@@ -4,7 +4,7 @@ import settings
import base64
import logging
from BaseClasses import Item, Region, MultiWorld, Tutorial, ItemClassification
from BaseClasses import Item, Region, Tutorial, ItemClassification
from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts
from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id
from .entrances import verify_entrances, get_warp_entrances
@@ -14,11 +14,11 @@ from .stages import get_locations_from_stage, get_normal_stage_exits, vanilla_st
from .regions import get_region_info
from .rules import CV64Rules
from .data import iname, rname, ename
from ..AutoWorld import WebWorld, World
from worlds.AutoWorld import WebWorld, World
from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \
randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \
get_countdown_numbers
from .rom import LocalRom, patch_rom, get_base_rom_path, CV64DeltaPatch
from .rom import RomData, write_patch, get_base_rom_path, CV64ProcedurePatch, CV64_US_10_HASH
from .client import Castlevania64Client
@@ -27,7 +27,7 @@ class CV64Settings(settings.Group):
"""File name of the CV64 US 1.0 rom"""
copy_to = "Castlevania (USA).z64"
description = "CV64 (US 1.0) ROM File"
md5s = [CV64DeltaPatch.hash]
md5s = [CV64_US_10_HASH]
rom_file: RomFile = RomFile(RomFile.copy_to)
@@ -86,12 +86,6 @@ class CV64World(World):
web = CV64Web()
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
rom_file = get_base_rom_path()
if not os.path.exists(rom_file):
raise FileNotFoundError(rom_file)
def generate_early(self) -> None:
# Generate the player's unique authentication
self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16))
@@ -276,18 +270,13 @@ class CV64World(World):
offset_data.update(get_start_inventory_data(self.player, self.options,
self.multiworld.precollected_items[self.player]))
cv64_rom = LocalRom(get_base_rom_path())
patch = CV64ProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
write_patch(self, patch, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations)
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.z64")
rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}"
f"{patch.patch_file_ending}")
patch_rom(self, cv64_rom, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations)
cv64_rom.write_to_file(rompath)
patch = CV64DeltaPatch(os.path.splitext(rompath)[0] + CV64DeltaPatch.patch_file_ending, player=self.player,
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
patch.write()
os.unlink(rompath)
patch.write(rom_path)
def get_filler_item_name(self) -> str:
return self.random.choice(filler_item_names)

View File

@@ -14,111 +14,111 @@ if TYPE_CHECKING:
from . import CV64World
rom_sub_weapon_offsets = {
0x10C6EB: (0x10, rname.forest_of_silence), # Forest
0x10C6F3: (0x0F, rname.forest_of_silence),
0x10C6FB: (0x0E, rname.forest_of_silence),
0x10C703: (0x0D, rname.forest_of_silence),
0x10C6EB: (b"\x10", rname.forest_of_silence), # Forest
0x10C6F3: (b"\x0F", rname.forest_of_silence),
0x10C6FB: (b"\x0E", rname.forest_of_silence),
0x10C703: (b"\x0D", rname.forest_of_silence),
0x10C81F: (0x0F, rname.castle_wall), # Castle Wall
0x10C827: (0x10, rname.castle_wall),
0x10C82F: (0x0E, rname.castle_wall),
0x7F9A0F: (0x0D, rname.castle_wall),
0x10C81F: (b"\x0F", rname.castle_wall), # Castle Wall
0x10C827: (b"\x10", rname.castle_wall),
0x10C82F: (b"\x0E", rname.castle_wall),
0x7F9A0F: (b"\x0D", rname.castle_wall),
0x83A5D9: (0x0E, rname.villa), # Villa
0x83A5E5: (0x0D, rname.villa),
0x83A5F1: (0x0F, rname.villa),
0xBFC903: (0x10, rname.villa),
0x10C987: (0x10, rname.villa),
0x10C98F: (0x0D, rname.villa),
0x10C997: (0x0F, rname.villa),
0x10CF73: (0x10, rname.villa),
0x83A5D9: (b"\x0E", rname.villa), # Villa
0x83A5E5: (b"\x0D", rname.villa),
0x83A5F1: (b"\x0F", rname.villa),
0xBFC903: (b"\x10", rname.villa),
0x10C987: (b"\x10", rname.villa),
0x10C98F: (b"\x0D", rname.villa),
0x10C997: (b"\x0F", rname.villa),
0x10CF73: (b"\x10", rname.villa),
0x10CA57: (0x0D, rname.tunnel), # Tunnel
0x10CA5F: (0x0E, rname.tunnel),
0x10CA67: (0x10, rname.tunnel),
0x10CA6F: (0x0D, rname.tunnel),
0x10CA77: (0x0F, rname.tunnel),
0x10CA7F: (0x0E, rname.tunnel),
0x10CA57: (b"\x0D", rname.tunnel), # Tunnel
0x10CA5F: (b"\x0E", rname.tunnel),
0x10CA67: (b"\x10", rname.tunnel),
0x10CA6F: (b"\x0D", rname.tunnel),
0x10CA77: (b"\x0F", rname.tunnel),
0x10CA7F: (b"\x0E", rname.tunnel),
0x10CBC7: (0x0E, rname.castle_center), # Castle Center
0x10CC0F: (0x0D, rname.castle_center),
0x10CC5B: (0x0F, rname.castle_center),
0x10CBC7: (b"\x0E", rname.castle_center), # Castle Center
0x10CC0F: (b"\x0D", rname.castle_center),
0x10CC5B: (b"\x0F", rname.castle_center),
0x10CD3F: (0x0E, rname.tower_of_execution), # Character towers
0x10CD65: (0x0D, rname.tower_of_execution),
0x10CE2B: (0x0E, rname.tower_of_science),
0x10CE83: (0x10, rname.duel_tower),
0x10CD3F: (b"\x0E", rname.tower_of_execution), # Character towers
0x10CD65: (b"\x0D", rname.tower_of_execution),
0x10CE2B: (b"\x0E", rname.tower_of_science),
0x10CE83: (b"\x10", rname.duel_tower),
0x10CF8B: (0x0F, rname.room_of_clocks), # Room of Clocks
0x10CF93: (0x0D, rname.room_of_clocks),
0x10CF8B: (b"\x0F", rname.room_of_clocks), # Room of Clocks
0x10CF93: (b"\x0D", rname.room_of_clocks),
0x99BC5A: (0x0D, rname.clock_tower), # Clock Tower
0x10CECB: (0x10, rname.clock_tower),
0x10CED3: (0x0F, rname.clock_tower),
0x10CEDB: (0x0E, rname.clock_tower),
0x10CEE3: (0x0D, rname.clock_tower),
0x99BC5A: (b"\x0D", rname.clock_tower), # Clock Tower
0x10CECB: (b"\x10", rname.clock_tower),
0x10CED3: (b"\x0F", rname.clock_tower),
0x10CEDB: (b"\x0E", rname.clock_tower),
0x10CEE3: (b"\x0D", rname.clock_tower),
}
rom_sub_weapon_flags = {
0x10C6EC: 0x0200FF04, # Forest of Silence
0x10C6FC: 0x0400FF04,
0x10C6F4: 0x0800FF04,
0x10C704: 0x4000FF04,
0x10C6EC: b"\x02\x00\xFF\x04", # Forest of Silence
0x10C6FC: b"\x04\x00\xFF\x04",
0x10C6F4: b"\x08\x00\xFF\x04",
0x10C704: b"\x40\x00\xFF\x04",
0x10C831: 0x08, # Castle Wall
0x10C829: 0x10,
0x10C821: 0x20,
0xBFCA97: 0x04,
0x10C831: b"\x08", # Castle Wall
0x10C829: b"\x10",
0x10C821: b"\x20",
0xBFCA97: b"\x04",
# Villa
0xBFC926: 0xFF04,
0xBFC93A: 0x80,
0xBFC93F: 0x01,
0xBFC943: 0x40,
0xBFC947: 0x80,
0x10C989: 0x10,
0x10C991: 0x20,
0x10C999: 0x40,
0x10CF77: 0x80,
0xBFC926: b"\xFF\x04",
0xBFC93A: b"\x80",
0xBFC93F: b"\x01",
0xBFC943: b"\x40",
0xBFC947: b"\x80",
0x10C989: b"\x10",
0x10C991: b"\x20",
0x10C999: b"\x40",
0x10CF77: b"\x80",
0x10CA58: 0x4000FF0E, # Tunnel
0x10CA6B: 0x80,
0x10CA60: 0x1000FF05,
0x10CA70: 0x2000FF05,
0x10CA78: 0x4000FF05,
0x10CA80: 0x8000FF05,
0x10CA58: b"\x40\x00\xFF\x0E", # Tunnel
0x10CA6B: b"\x80",
0x10CA60: b"\x10\x00\xFF\x05",
0x10CA70: b"\x20\x00\xFF\x05",
0x10CA78: b"\x40\x00\xFF\x05",
0x10CA80: b"\x80\x00\xFF\x05",
0x10CBCA: 0x02, # Castle Center
0x10CC10: 0x80,
0x10CC5C: 0x40,
0x10CBCA: b"\x02", # Castle Center
0x10CC10: b"\x80",
0x10CC5C: b"\x40",
0x10CE86: 0x01, # Duel Tower
0x10CD43: 0x02, # Tower of Execution
0x10CE2E: 0x20, # Tower of Science
0x10CE86: b"\x01", # Duel Tower
0x10CD43: b"\x02", # Tower of Execution
0x10CE2E: b"\x20", # Tower of Science
0x10CF8E: 0x04, # Room of Clocks
0x10CF96: 0x08,
0x10CF8E: b"\x04", # Room of Clocks
0x10CF96: b"\x08",
0x10CECE: 0x08, # Clock Tower
0x10CED6: 0x10,
0x10CEE6: 0x20,
0x10CEDE: 0x80,
0x10CECE: b"\x08", # Clock Tower
0x10CED6: b"\x10",
0x10CEE6: b"\x20",
0x10CEDE: b"\x80",
}
rom_empty_breakables_flags = {
0x10C74D: 0x40FF05, # Forest of Silence
0x10C765: 0x20FF0E,
0x10C774: 0x0800FF0E,
0x10C755: 0x80FF05,
0x10C784: 0x0100FF0E,
0x10C73C: 0x0200FF0E,
0x10C74D: b"\x40\xFF\x05", # Forest of Silence
0x10C765: b"\x20\xFF\x0E",
0x10C774: b"\x08\x00\xFF\x0E",
0x10C755: b"\x80\xFF\x05",
0x10C784: b"\x01\x00\xFF\x0E",
0x10C73C: b"\x02\x00\xFF\x0E",
0x10C8D0: 0x0400FF0E, # Villa foyer
0x10C8D0: b"\x04\x00\xFF\x0E", # Villa foyer
0x10CF9F: 0x08, # Room of Clocks flags
0x10CFA7: 0x01,
0xBFCB6F: 0x04, # Room of Clocks candle property IDs
0xBFCB73: 0x05,
0x10CF9F: b"\x08", # Room of Clocks flags
0x10CFA7: b"\x01",
0xBFCB6F: b"\x04", # Room of Clocks candle property IDs
0xBFCB73: b"\x05",
}
rom_axe_cross_lower_values = {
@@ -269,19 +269,18 @@ renon_item_dialogue = {
}
def randomize_lighting(world: "CV64World") -> Dict[int, int]:
def randomize_lighting(world: "CV64World") -> Dict[int, bytes]:
"""Generates randomized data for the map lighting table."""
randomized_lighting = {}
for entry in range(67):
for sub_entry in range(19):
if sub_entry not in [3, 7, 11, 15] and entry != 4:
# The fourth entry in the lighting table affects the lighting on some item pickups; skip it
randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = \
world.random.randint(0, 255)
randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = bytes([world.random.randint(0, 255)])
return randomized_lighting
def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]:
def shuffle_sub_weapons(world: "CV64World") -> Dict[int, bytes]:
"""Shuffles the sub-weapons amongst themselves."""
sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if
rom_sub_weapon_offsets[offset][1] in world.active_stage_exits}
@@ -295,7 +294,7 @@ def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]:
return dict(zip(sub_weapon_dict, sub_bytes))
def randomize_music(world: "CV64World") -> Dict[int, int]:
def randomize_music(world: "CV64World") -> Dict[int, bytes]:
"""Generates randomized or disabled data for all the music in the game."""
music_array = bytearray(0x7A)
for number in music_sfx_ids:
@@ -340,15 +339,10 @@ def randomize_music(world: "CV64World") -> Dict[int, int]:
music_array[i] = fade_in_songs[i]
del (music_array[0x00: 0x10])
# Convert the music array into a data dict
music_offsets = {}
for i in range(len(music_array)):
music_offsets[0xBFCD30 + i] = music_array[i]
return music_offsets
return {0xBFCD30: bytes(music_array)}
def randomize_shop_prices(world: "CV64World") -> Dict[int, int]:
def randomize_shop_prices(world: "CV64World") -> Dict[int, bytes]:
"""Randomize the shop prices based on the minimum and maximum values chosen.
The minimum price will adjust if it's higher than the max."""
min_price = world.options.minimum_gold_price.value
@@ -363,21 +357,15 @@ def randomize_shop_prices(world: "CV64World") -> Dict[int, int]:
shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)]
# Convert the price list into a data dict. Which offset it starts from depends on how many bytes it takes up.
# Convert the price list into a data dict.
price_dict = {}
for i in range(len(shop_price_list)):
if shop_price_list[i] <= 0xFF:
price_dict[0x103D6E + (i*12)] = 0
price_dict[0x103D6F + (i*12)] = shop_price_list[i]
elif shop_price_list[i] <= 0xFFFF:
price_dict[0x103D6E + (i*12)] = shop_price_list[i]
else:
price_dict[0x103D6D + (i*12)] = shop_price_list[i]
price_dict[0x103D6C + (i * 12)] = int.to_bytes(shop_price_list[i], 4, "big")
return price_dict
def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, int]:
def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, bytes]:
"""Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should
increase a number.
@@ -400,16 +388,11 @@ def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Locat
if countdown_number is not None:
countdown_list[countdown_number] += 1
# Convert the Countdown list into a data dict
countdown_dict = {}
for i in range(len(countdown_list)):
countdown_dict[0xBFD818 + i] = countdown_list[i]
return countdown_dict
return {0xBFD818: bytes(countdown_list)}
def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \
-> Tuple[Dict[int, int], List[str], List[bytearray], List[List[Union[int, str, None]]]]:
-> Tuple[Dict[int, bytes], List[str], List[bytearray], List[List[Union[int, str, None]]]]:
"""Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of
the item, the second determines what the item actually is when picked up. All items from other worlds will be AP
items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's
@@ -449,12 +432,11 @@ def get_location_data(world: "CV64World", active_locations: Iterable[Location])
# Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's
# very own, or it belongs to an Item Link that the player is a part of.
if loc.item.player == world.player or (loc.item.player in world.multiworld.groups and
world.player in world.multiworld.groups[loc.item.player]['players']):
if loc.item.player == world.player:
if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None:
location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id")
else:
location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code")
location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code") & 0xFF
else:
# Make the item the unused Wooden Stake - our multiworld item.
location_bytes[get_location_info(loc.name, "offset")] = 0x11
@@ -534,11 +516,12 @@ def get_location_data(world: "CV64World", active_locations: Iterable[Location])
shop_colors_list.append(get_item_text_color(loc))
return location_bytes, shop_name_list, shop_colors_list, shop_desc_list
return {offset: int.to_bytes(byte, 1, "big") for offset, byte in location_bytes.items()}, shop_name_list,\
shop_colors_list, shop_desc_list
def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, int]:
active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, bytes]:
"""Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data.
The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map
to send the player to, and which spot within the map to spawn the player at."""
@@ -551,8 +534,8 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
# Start loading zones
# If the start zone is the start of the line, have it simply refresh the map.
if active_stage_exits[stage]["prev"] == "Menu":
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = 0xFF
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = 0x00
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = b"\xFF"
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x00"
elif active_stage_exits[stage]["prev"]:
loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \
get_stage_info(active_stage_exits[stage]["prev"], "end map id")
@@ -563,7 +546,7 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
if active_stage_exits[stage]["prev"] == rname.castle_center:
if options.character_stages == CharacterStages.option_carrie_only or \
active_stage_exits[rname.castle_center]["alt"] == stage:
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] += 1
loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = b"\x03"
# End loading zones
if active_stage_exits[stage]["next"]:
@@ -582,16 +565,16 @@ def get_loading_zone_bytes(options: CV64Options, starting_stage: str,
return loading_zone_bytes
def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, int]:
def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, bytes]:
"""Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything
has to be handled appropriately."""
start_inventory_data = {0xBFD867: 0, # Jewels
0xBFD87B: 0, # PowerUps
0xBFD883: 0, # Sub-weapon
0xBFD88B: 0} # Ice Traps
start_inventory_data = {}
inventory_items_array = [0 for _ in range(35)]
total_money = 0
total_jewels = 0
total_powerups = 0
total_ice_traps = 0
items_max = 10
@@ -615,42 +598,46 @@ def get_start_inventory_data(player: int, options: CV64Options, precollected_ite
inventory_items_array[inventory_offset] = 2
# Starting sub-weapon
elif sub_equip_id is not None:
start_inventory_data[0xBFD883] = sub_equip_id
start_inventory_data[0xBFD883] = bytes(sub_equip_id)
# Starting PowerUps
elif item.name == iname.powerup:
start_inventory_data[0xBFD87B] += 1
if start_inventory_data[0xBFD87B] > 2:
start_inventory_data[0xBFD87B] = 2
total_powerups += 1
# Can't have more than 2 PowerUps.
if total_powerups > 2:
total_powerups = 2
# Starting Gold
elif "GOLD" in item.name:
total_money += int(item.name[0:4])
# Money cannot be higher than 99999.
if total_money > 99999:
total_money = 99999
# Starting Jewels
elif "jewel" in item.name:
if "L" in item.name:
start_inventory_data[0xBFD867] += 10
total_jewels += 10
else:
start_inventory_data[0xBFD867] += 5
if start_inventory_data[0xBFD867] > 99:
start_inventory_data[0xBFD867] = 99
total_jewels += 5
# Jewels cannot be higher than 99.
if total_jewels > 99:
total_jewels = 99
# Starting Ice Traps
else:
start_inventory_data[0xBFD88B] += 1
if start_inventory_data[0xBFD88B] > 0xFF:
start_inventory_data[0xBFD88B] = 0xFF
total_ice_traps += 1
# Ice Traps cannot be higher than 255.
if total_ice_traps > 0xFF:
total_ice_traps = 0xFF
# Convert the jewels into data.
start_inventory_data[0xBFD867] = bytes([total_jewels])
# Convert the Ice Traps into data.
start_inventory_data[0xBFD88B] = bytes([total_ice_traps])
# Convert the inventory items into data.
for i in range(len(inventory_items_array)):
start_inventory_data[0xBFE518 + i] = inventory_items_array[i]
start_inventory_data[0xBFE518] = bytes(inventory_items_array)
# Convert the starting money into data. Which offset it starts from depends on how many bytes it takes up.
if total_money <= 0xFF:
start_inventory_data[0xBFE517] = total_money
elif total_money <= 0xFFFF:
start_inventory_data[0xBFE516] = total_money
else:
start_inventory_data[0xBFE515] = total_money
# Convert the starting money into data.
start_inventory_data[0xBFE514] = int.to_bytes(total_money, 4, "big")
return start_inventory_data

View File

@@ -197,12 +197,15 @@ deathlink_nitro_edition = [
0xA168FFFD, # SB T0, 0xFFFD (T3)
]
nitro_fall_killer = [
# Custom code to force the instant fall death if at a high enough falling speed after getting killed by the Nitro
# explosion, since the game doesn't run the checks for the fall death after getting hit by said explosion and could
# result in a softlock when getting blown into an abyss.
launch_fall_killer = [
# Custom code to force the instant fall death if at a high enough falling speed after getting killed by something
# that launches you (whether it be the Nitro explosion or a Big Toss hit). The game doesn't normally run the check
# that would trigger the fall death after you get killed by some other means, which could result in a softlock
# when a killing blow launches you into an abyss.
0x3C0C8035, # LUI T4, 0x8035
0x918807E2, # LBU T0, 0x07E2 (T4)
0x24090008, # ADDIU T1, R0, 0x0008
0x11090002, # BEQ T0, T1, [forward 0x02]
0x2409000C, # ADDIU T1, R0, 0x000C
0x15090006, # BNE T0, T1, [forward 0x06]
0x3C098035, # LUI T1, 0x8035
@@ -2863,3 +2866,13 @@ big_tosser = [
0xAD000814, # SW R0, 0x0814 (T0)
0x03200008 # JR T9
]
dog_bite_ice_trap_fix = [
# Sets the freeze timer to 0 when a maze garden dog bites the player to ensure the ice chunk model will break if the
# player gets bitten while frozen via Ice Trap.
0x3C088039, # LUI T0, 0x8039
0xA5009E76, # SH R0, 0x9E76 (T0)
0x3C090F00, # LUI T1, 0x0F00
0x25291CB8, # ADDIU T1, T1, 0x1CB8
0x01200008 # JR T1
]

View File

@@ -27,7 +27,7 @@ in vanilla, contains 5 checks in rando.
#### Bat archway rock
After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front
of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new
to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guranteed spot to find a PowerUp at.
to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guaranteed spot to find a PowerUp at.

View File

@@ -74,7 +74,8 @@ class HardItemPool(Toggle):
class Special1sPerWarp(Range):
"""Sets how many Special1 jewels are needed per warp menu option unlock."""
"""Sets how many Special1 jewels are needed per warp menu option unlock.
This will decrease until the number x 7 is less than or equal to the Total Specail1s if it isn't already."""
range_start = 1
range_end = 10
default = 1
@@ -82,8 +83,7 @@ class Special1sPerWarp(Range):
class TotalSpecial1s(Range):
"""Sets how many Speical1 jewels are in the pool in total.
If this is set to be less than Special1s Per Warp x 7, it will decrease by 1 until it isn't."""
"""Sets how many Speical1 jewels are in the pool in total."""
range_start = 7
range_end = 70
default = 7

File diff suppressed because it is too large Load Diff

View File

@@ -47,9 +47,9 @@ if TYPE_CHECKING:
# corresponding Locations and Entrances will all be created.
stage_info = {
"Forest of Silence": {
"start region": rname.forest_start, "start map id": 0x00, "start spawn id": 0x00,
"mid region": rname.forest_mid, "mid map id": 0x00, "mid spawn id": 0x04,
"end region": rname.forest_end, "end map id": 0x00, "end spawn id": 0x01,
"start region": rname.forest_start, "start map id": b"\x00", "start spawn id": b"\x00",
"mid region": rname.forest_mid, "mid map id": b"\x00", "mid spawn id": b"\x04",
"end region": rname.forest_end, "end map id": b"\x00", "end spawn id": b"\x01",
"endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B,
"save number offsets": [0x1049C5, 0x1049CD, 0x1049D5],
"regions": [rname.forest_start,
@@ -58,9 +58,9 @@ stage_info = {
},
"Castle Wall": {
"start region": rname.cw_start, "start map id": 0x02, "start spawn id": 0x00,
"mid region": rname.cw_start, "mid map id": 0x02, "mid spawn id": 0x07,
"end region": rname.cw_exit, "end map id": 0x02, "end spawn id": 0x10,
"start region": rname.cw_start, "start map id": b"\x02", "start spawn id": b"\x00",
"mid region": rname.cw_start, "mid map id": b"\x02", "mid spawn id": b"\x07",
"end region": rname.cw_exit, "end map id": b"\x02", "end spawn id": b"\x10",
"endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61,
"save number offsets": [0x1049DD, 0x1049E5, 0x1049ED],
"regions": [rname.cw_start,
@@ -69,9 +69,9 @@ stage_info = {
},
"Villa": {
"start region": rname.villa_start, "start map id": 0x03, "start spawn id": 0x00,
"mid region": rname.villa_storeroom, "mid map id": 0x05, "mid spawn id": 0x04,
"end region": rname.villa_crypt, "end map id": 0x1A, "end spawn id": 0x03,
"start region": rname.villa_start, "start map id": b"\x03", "start spawn id": b"\x00",
"mid region": rname.villa_storeroom, "mid map id": b"\x05", "mid spawn id": b"\x04",
"end region": rname.villa_crypt, "end map id": b"\x1A", "end spawn id": b"\x03",
"endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81,
"altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81,
"save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D],
@@ -85,9 +85,9 @@ stage_info = {
},
"Tunnel": {
"start region": rname.tunnel_start, "start map id": 0x07, "start spawn id": 0x00,
"mid region": rname.tunnel_end, "mid map id": 0x07, "mid spawn id": 0x03,
"end region": rname.tunnel_end, "end map id": 0x07, "end spawn id": 0x11,
"start region": rname.tunnel_start, "start map id": b"\x07", "start spawn id": b"\x00",
"mid region": rname.tunnel_end, "mid map id": b"\x07", "mid spawn id": b"\x03",
"end region": rname.tunnel_end, "end map id": b"\x07", "end spawn id": b"\x11",
"endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt",
"save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D],
"regions": [rname.tunnel_start,
@@ -95,9 +95,9 @@ stage_info = {
},
"Underground Waterway": {
"start region": rname.uw_main, "start map id": 0x08, "start spawn id": 0x00,
"mid region": rname.uw_main, "mid map id": 0x08, "mid spawn id": 0x03,
"end region": rname.uw_end, "end map id": 0x08, "end spawn id": 0x01,
"start region": rname.uw_main, "start map id": b"\x08", "start spawn id": b"\x00",
"mid region": rname.uw_main, "mid map id": b"\x08", "mid spawn id": b"\x03",
"end region": rname.uw_end, "end map id": b"\x08", "end spawn id": b"\x01",
"endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie",
"save number offsets": [0x104A35, 0x104A3D],
"regions": [rname.uw_main,
@@ -105,9 +105,9 @@ stage_info = {
},
"Castle Center": {
"start region": rname.cc_main, "start map id": 0x19, "start spawn id": 0x00,
"mid region": rname.cc_main, "mid map id": 0x0E, "mid spawn id": 0x03,
"end region": rname.cc_elev_top, "end map id": 0x0F, "end spawn id": 0x02,
"start region": rname.cc_main, "start map id": b"\x19", "start spawn id": b"\x00",
"mid region": rname.cc_main, "mid map id": b"\x0E", "mid spawn id": b"\x03",
"end region": rname.cc_elev_top, "end map id": b"\x0F", "end spawn id": b"\x02",
"endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9,
"altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1,
"save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75],
@@ -119,20 +119,20 @@ stage_info = {
},
"Duel Tower": {
"start region": rname.dt_main, "start map id": 0x13, "start spawn id": 0x00,
"start region": rname.dt_main, "start map id": b"\x13", "start spawn id": b"\x00",
"startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9,
"mid region": rname.dt_main, "mid map id": 0x13, "mid spawn id": 0x15,
"end region": rname.dt_main, "end map id": 0x13, "end spawn id": 0x01,
"mid region": rname.dt_main, "mid map id": b"\x13", "mid spawn id": b"\x15",
"end region": rname.dt_main, "end map id": b"\x13", "end spawn id": b"\x01",
"endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt",
"save number offsets": [0x104ACD],
"regions": [rname.dt_main]
},
"Tower of Execution": {
"start region": rname.toe_main, "start map id": 0x10, "start spawn id": 0x00,
"start region": rname.toe_main, "start map id": b"\x10", "start spawn id": b"\x00",
"startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19,
"mid region": rname.toe_main, "mid map id": 0x10, "mid spawn id": 0x02,
"end region": rname.toe_main, "end map id": 0x10, "end spawn id": 0x12,
"mid region": rname.toe_main, "mid map id": b"\x10", "mid spawn id": b"\x02",
"end region": rname.toe_main, "end map id": b"\x10", "end spawn id": b"\x12",
"endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt",
"save number offsets": [0x104A7D, 0x104A85],
"regions": [rname.toe_main,
@@ -140,10 +140,10 @@ stage_info = {
},
"Tower of Science": {
"start region": rname.tosci_start, "start map id": 0x12, "start spawn id": 0x00,
"start region": rname.tosci_start, "start map id": b"\x12", "start spawn id": b"\x00",
"startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79,
"mid region": rname.tosci_conveyors, "mid map id": 0x12, "mid spawn id": 0x03,
"end region": rname.tosci_conveyors, "end map id": 0x12, "end spawn id": 0x04,
"mid region": rname.tosci_conveyors, "mid map id": b"\x12", "mid spawn id": b"\x03",
"end region": rname.tosci_conveyors, "end map id": b"\x12", "end spawn id": b"\x04",
"endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie",
"save number offsets": [0x104A95, 0x104A9D, 0x104AA5],
"regions": [rname.tosci_start,
@@ -153,28 +153,28 @@ stage_info = {
},
"Tower of Sorcery": {
"start region": rname.tosor_main, "start map id": 0x11, "start spawn id": 0x00,
"start region": rname.tosor_main, "start map id": b"\x11", "start spawn id": b"\x00",
"startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49,
"mid region": rname.tosor_main, "mid map id": 0x11, "mid spawn id": 0x01,
"end region": rname.tosor_main, "end map id": 0x11, "end spawn id": 0x13,
"mid region": rname.tosor_main, "mid map id": b"\x11", "mid spawn id": b"\x01",
"end region": rname.tosor_main, "end map id": b"\x11", "end spawn id": b"\x13",
"endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie",
"save number offsets": [0x104A8D],
"regions": [rname.tosor_main]
},
"Room of Clocks": {
"start region": rname.roc_main, "start map id": 0x1B, "start spawn id": 0x00,
"mid region": rname.roc_main, "mid map id": 0x1B, "mid spawn id": 0x02,
"end region": rname.roc_main, "end map id": 0x1B, "end spawn id": 0x14,
"start region": rname.roc_main, "start map id": b"\x1B", "start spawn id": b"\x00",
"mid region": rname.roc_main, "mid map id": b"\x1B", "mid spawn id": b"\x02",
"end region": rname.roc_main, "end map id": b"\x1B", "end spawn id": b"\x14",
"endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1,
"save number offsets": [0x104AC5],
"regions": [rname.roc_main]
},
"Clock Tower": {
"start region": rname.ct_start, "start map id": 0x17, "start spawn id": 0x00,
"mid region": rname.ct_middle, "mid map id": 0x17, "mid spawn id": 0x02,
"end region": rname.ct_end, "end map id": 0x17, "end spawn id": 0x03,
"start region": rname.ct_start, "start map id": b"\x17", "start spawn id": b"\x00",
"mid region": rname.ct_middle, "mid map id": b"\x17", "mid spawn id": b"\x02",
"end region": rname.ct_end, "end map id": b"\x17", "end spawn id": b"\x03",
"endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39,
"save number offsets": [0x104AB5, 0x104ABD],
"regions": [rname.ct_start,
@@ -183,8 +183,8 @@ stage_info = {
},
"Castle Keep": {
"start region": rname.ck_main, "start map id": 0x14, "start spawn id": 0x02,
"mid region": rname.ck_main, "mid map id": 0x14, "mid spawn id": 0x03,
"start region": rname.ck_main, "start map id": b"\x14", "start spawn id": b"\x02",
"mid region": rname.ck_main, "mid map id": b"\x14", "mid spawn id": b"\x03",
"end region": rname.ck_drac_chamber,
"save number offsets": [0x104AAD],
"regions": [rname.ck_main]

View File

@@ -25,29 +25,28 @@ To downpatch DS3 for use with Archipelago, use the following instructions from t
1. Launch Steam (in online mode).
2. Press the Windows Key + R. This will open the Run window.
3. Open the Steam console by typing the following string: steam://open/console , Steam should now open in Console Mode.
4. Insert the string of the depot you wish to download. For the AP supported v1.15, you will want to use: download_depot 374320 374321 4471176929659548333.
5. Steam will now download the depot. Note: There is no progress bar of the download in Steam, but it is still downloading in the background.
6. Turn off auto-updates in Steam by right-clicking Dark Souls III in your library > Properties > Updates > set "Automatic Updates" to "Only update this game when I launch it" (or change the value for AutoUpdateBehavior to 1 in "\Steam\steamapps\appmanifest_374320.acf").
7. Back up your existing game folder in "\Steam\steamapps\common\DARK SOULS III".
8. Return back to Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX". Back up this game folder as well.
9. Delete your existing game folder in "\Steam\steamapps\common\DARK SOULS III", then replace it with your game folder in "\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX".
10. Back up and delete your save file "DS30000.sl2" in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type %appdata% and hit enter or: open File Explorer > View > Hidden Items and follow "C:\Users\your username\AppData\Roaming\DarkSoulsIII\numbers".
11. If you did all these steps correctly, you should be able to confirm your game version in the upper left corner after launching Dark Souls III.
3. Open the Steam console by typing the following string: `steam://open/console`. Steam should now open in Console Mode.
4. Insert the string of the depot you wish to download. For the AP-supported v1.15, you will want to use: `download_depot 374320 374321 4471176929659548333`.
5. Steam will now download the depot. Note: There is no progress bar for the download in Steam, but it is still downloading in the background.
6. Back up your existing game executable (`DarkSoulsIII.exe`) found in `\Steam\steamapps\common\DARK SOULS III\Game`. Easiest way to do this is to move it to another directory. If you have file extensions enabled, you can instead rename the executable to `DarkSoulsIII.exe.bak`.
7. Return to the Steam console. Once the download is complete, it should say so along with the temporary local directory in which the depot has been stored. This is usually something like `\Steam\steamapps\content\app_XXXXXX\depot_XXXXXX`.
8. Take the `DarkSoulsIII.exe` from that folder and place it in `\Steam\steamapps\common\DARK SOULS III\Game`.
9. Back up and delete your save file (`DS30000.sl2`) in AppData. AppData is hidden by default. To locate it, press Windows Key + R, type `%appdata%` and hit enter. Alternatively: open File Explorer > View > Hidden Items and follow `C:\Users\<your_username>\AppData\Roaming\DarkSoulsIII\<numbers>`.
10. If you did all these steps correctly, you should be able to confirm your game version in the upper-left corner after launching Dark Souls III.
## Installing the Archipelago mod
Get the dinput8.dll from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and
add it at the root folder of your game (e.g. "SteamLibrary\steamapps\common\DARK SOULS III\Game")
Get the `dinput8.dll` from the [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases) and
add it at the root folder of your game (e.g. `SteamLibrary\steamapps\common\DARK SOULS III\Game`)
## Joining a MultiWorld Game
1. Run Steam in offline mode, both to avoid being banned and to prevent Steam from updating the game files
2. Launch Dark Souls III
3. Type in "/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME}" in the "Windows Command Prompt" that opened
4. Once connected, create a new game, choose a class and wait for the others before starting
5. You can quit and launch at anytime during a game
1. Run Steam in offline mode to avoid being banned.
2. Launch Dark Souls III.
3. Type in `/connect {SERVER_IP}:{SERVER_PORT} {SLOT_NAME} password:{PASSWORD}` in the "Windows Command Prompt" that opened. For example: `/connect archipelago.gg:38281 "Example Name" password:"Example Password"`. The password parameter is only necessary if your game requires one.
4. Once connected, create a new game, choose a class and wait for the others before starting.
5. You can quit and launch at anytime during a game.
## Where do I get a config file?

View File

@@ -1,6 +1,5 @@
import typing
from Options import AssembleOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool
from Options import PerGameCommonOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool
from dataclasses import dataclass
class Goal(Choice):
@@ -140,21 +139,22 @@ class Episode4(Toggle):
display_name = "Episode 4"
options: typing.Dict[str, AssembleOptions] = {
"start_inventory_from_pool": StartInventoryPool,
"goal": Goal,
"difficulty": Difficulty,
"random_monsters": RandomMonsters,
"random_pickups": RandomPickups,
"random_music": RandomMusic,
"flip_levels": FlipLevels,
"allow_death_logic": AllowDeathLogic,
"pro": Pro,
"start_with_computer_area_maps": StartWithComputerAreaMaps,
"death_link": DeathLink,
"reset_level_on_death": ResetLevelOnDeath,
"episode1": Episode1,
"episode2": Episode2,
"episode3": Episode3,
"episode4": Episode4
}
@dataclass
class DOOM1993Options(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
goal: Goal
difficulty: Difficulty
random_monsters: RandomMonsters
random_pickups: RandomPickups
random_music: RandomMusic
flip_levels: FlipLevels
allow_death_logic: AllowDeathLogic
pro: Pro
start_with_computer_area_maps: StartWithComputerAreaMaps
death_link: DeathLink
reset_level_on_death: ResetLevelOnDeath
episode1: Episode1
episode2: Episode2
episode3: Episode3
episode4: Episode4

View File

@@ -7,105 +7,105 @@ if TYPE_CHECKING:
from . import DOOM1993World
def set_episode1_rules(player, world, pro):
def set_episode1_rules(player, multiworld, pro):
# Hangar (E1M1)
set_rule(world.get_entrance("Hub -> Hangar (E1M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Hangar (E1M1) Main", player), lambda state:
state.has("Hangar (E1M1)", player, 1))
# Nuclear Plant (E1M2)
set_rule(world.get_entrance("Hub -> Nuclear Plant (E1M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Nuclear Plant (E1M2) Main", player), lambda state:
(state.has("Nuclear Plant (E1M2)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1)))
set_rule(world.get_entrance("Nuclear Plant (E1M2) Main -> Nuclear Plant (E1M2) Red", player), lambda state:
set_rule(multiworld.get_entrance("Nuclear Plant (E1M2) Main -> Nuclear Plant (E1M2) Red", player), lambda state:
state.has("Nuclear Plant (E1M2) - Red keycard", player, 1))
set_rule(world.get_entrance("Nuclear Plant (E1M2) Red -> Nuclear Plant (E1M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Nuclear Plant (E1M2) Red -> Nuclear Plant (E1M2) Main", player), lambda state:
state.has("Nuclear Plant (E1M2) - Red keycard", player, 1))
# Toxin Refinery (E1M3)
set_rule(world.get_entrance("Hub -> Toxin Refinery (E1M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Toxin Refinery (E1M3) Main", player), lambda state:
(state.has("Toxin Refinery (E1M3)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1)))
set_rule(world.get_entrance("Toxin Refinery (E1M3) Main -> Toxin Refinery (E1M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Main -> Toxin Refinery (E1M3) Blue", player), lambda state:
state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1))
set_rule(world.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Yellow", player), lambda state:
state.has("Toxin Refinery (E1M3) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Blue -> Toxin Refinery (E1M3) Main", player), lambda state:
state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1))
set_rule(world.get_entrance("Toxin Refinery (E1M3) Yellow -> Toxin Refinery (E1M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Yellow -> Toxin Refinery (E1M3) Blue", player), lambda state:
state.has("Toxin Refinery (E1M3) - Yellow keycard", player, 1))
# Command Control (E1M4)
set_rule(world.get_entrance("Hub -> Command Control (E1M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Command Control (E1M4) Main", player), lambda state:
state.has("Command Control (E1M4)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1))
set_rule(world.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Blue", player), lambda state:
state.has("Command Control (E1M4) - Blue keycard", player, 1) or
state.has("Command Control (E1M4) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Command Control (E1M4) Main -> Command Control (E1M4) Yellow", player), lambda state:
state.has("Command Control (E1M4) - Blue keycard", player, 1) or
state.has("Command Control (E1M4) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Command Control (E1M4) Blue -> Command Control (E1M4) Main", player), lambda state:
state.has("Command Control (E1M4) - Yellow keycard", player, 1) or
state.has("Command Control (E1M4) - Blue keycard", player, 1))
# Phobos Lab (E1M5)
set_rule(world.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state:
state.has("Phobos Lab (E1M5)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1))
set_rule(world.get_entrance("Phobos Lab (E1M5) Main -> Phobos Lab (E1M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Main -> Phobos Lab (E1M5) Yellow", player), lambda state:
state.has("Phobos Lab (E1M5) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Phobos Lab (E1M5) Yellow -> Phobos Lab (E1M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Yellow -> Phobos Lab (E1M5) Blue", player), lambda state:
state.has("Phobos Lab (E1M5) - Blue keycard", player, 1))
set_rule(world.get_entrance("Phobos Lab (E1M5) Blue -> Phobos Lab (E1M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Blue -> Phobos Lab (E1M5) Green", player), lambda state:
state.has("Phobos Lab (E1M5) - Blue keycard", player, 1))
set_rule(world.get_entrance("Phobos Lab (E1M5) Green -> Phobos Lab (E1M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Phobos Lab (E1M5) Green -> Phobos Lab (E1M5) Blue", player), lambda state:
state.has("Phobos Lab (E1M5) - Blue keycard", player, 1))
# Central Processing (E1M6)
set_rule(world.get_entrance("Hub -> Central Processing (E1M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Central Processing (E1M6) Main", player), lambda state:
state.has("Central Processing (E1M6)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Rocket launcher", player, 1))
set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Yellow", player), lambda state:
state.has("Central Processing (E1M6) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Red", player), lambda state:
set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Red", player), lambda state:
state.has("Central Processing (E1M6) - Red keycard", player, 1))
set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Blue", player), lambda state:
state.has("Central Processing (E1M6) - Blue keycard", player, 1))
set_rule(world.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Nukage", player), lambda state:
set_rule(multiworld.get_entrance("Central Processing (E1M6) Main -> Central Processing (E1M6) Nukage", player), lambda state:
state.has("Central Processing (E1M6) - Blue keycard", player, 1))
set_rule(world.get_entrance("Central Processing (E1M6) Yellow -> Central Processing (E1M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Central Processing (E1M6) Yellow -> Central Processing (E1M6) Main", player), lambda state:
state.has("Central Processing (E1M6) - Yellow keycard", player, 1))
# Computer Station (E1M7)
set_rule(world.get_entrance("Hub -> Computer Station (E1M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Computer Station (E1M7) Main", player), lambda state:
state.has("Computer Station (E1M7)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Rocket launcher", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Red", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Red", player), lambda state:
state.has("Computer Station (E1M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Main -> Computer Station (E1M7) Yellow", player), lambda state:
state.has("Computer Station (E1M7) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Blue -> Computer Station (E1M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Blue -> Computer Station (E1M7) Yellow", player), lambda state:
state.has("Computer Station (E1M7) - Blue keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Red -> Computer Station (E1M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Red -> Computer Station (E1M7) Main", player), lambda state:
state.has("Computer Station (E1M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Blue", player), lambda state:
state.has("Computer Station (E1M7) - Blue keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Courtyard", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Yellow -> Computer Station (E1M7) Courtyard", player), lambda state:
state.has("Computer Station (E1M7) - Yellow keycard", player, 1) and
state.has("Computer Station (E1M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Computer Station (E1M7) Courtyard -> Computer Station (E1M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Computer Station (E1M7) Courtyard -> Computer Station (E1M7) Yellow", player), lambda state:
state.has("Computer Station (E1M7) - Yellow keycard", player, 1))
# Phobos Anomaly (E1M8)
set_rule(world.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state:
(state.has("Phobos Anomaly (E1M8)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
@@ -114,255 +114,260 @@ def set_episode1_rules(player, world, pro):
state.has("BFG9000", player, 1)))
# Military Base (E1M9)
set_rule(world.get_entrance("Hub -> Military Base (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Military Base (E1M9) Main", player), lambda state:
state.has("Military Base (E1M9)", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1))
set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Blue", player), lambda state:
state.has("Military Base (E1M9) - Blue keycard", player, 1))
set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Yellow", player), lambda state:
state.has("Military Base (E1M9) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Red", player), lambda state:
set_rule(multiworld.get_entrance("Military Base (E1M9) Main -> Military Base (E1M9) Red", player), lambda state:
state.has("Military Base (E1M9) - Red keycard", player, 1))
set_rule(world.get_entrance("Military Base (E1M9) Blue -> Military Base (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Military Base (E1M9) Blue -> Military Base (E1M9) Main", player), lambda state:
state.has("Military Base (E1M9) - Blue keycard", player, 1))
set_rule(world.get_entrance("Military Base (E1M9) Yellow -> Military Base (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Military Base (E1M9) Yellow -> Military Base (E1M9) Main", player), lambda state:
state.has("Military Base (E1M9) - Yellow keycard", player, 1))
def set_episode2_rules(player, world, pro):
def set_episode2_rules(player, multiworld, pro):
# Deimos Anomaly (E2M1)
set_rule(world.get_entrance("Hub -> Deimos Anomaly (E2M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Deimos Anomaly (E2M1) Main", player), lambda state:
state.has("Deimos Anomaly (E2M1)", player, 1))
set_rule(world.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Red", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Red", player), lambda state:
state.has("Deimos Anomaly (E2M1) - Red keycard", player, 1))
set_rule(world.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Main -> Deimos Anomaly (E2M1) Blue", player), lambda state:
state.has("Deimos Anomaly (E2M1) - Blue keycard", player, 1))
set_rule(world.get_entrance("Deimos Anomaly (E2M1) Blue -> Deimos Anomaly (E2M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Blue -> Deimos Anomaly (E2M1) Main", player), lambda state:
state.has("Deimos Anomaly (E2M1) - Blue keycard", player, 1))
set_rule(world.get_entrance("Deimos Anomaly (E2M1) Red -> Deimos Anomaly (E2M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Anomaly (E2M1) Red -> Deimos Anomaly (E2M1) Main", player), lambda state:
state.has("Deimos Anomaly (E2M1) - Red keycard", player, 1))
# Containment Area (E2M2)
set_rule(world.get_entrance("Hub -> Containment Area (E2M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Containment Area (E2M2) Main", player), lambda state:
(state.has("Containment Area (E2M2)", player, 1) and
state.has("Shotgun", player, 1)) and
(state.has("Chaingun", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Yellow", player), lambda state:
state.has("Containment Area (E2M2) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Blue", player), lambda state:
state.has("Containment Area (E2M2) - Blue keycard", player, 1))
set_rule(world.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Red", player), lambda state:
set_rule(multiworld.get_entrance("Containment Area (E2M2) Main -> Containment Area (E2M2) Red", player), lambda state:
state.has("Containment Area (E2M2) - Red keycard", player, 1))
set_rule(world.get_entrance("Containment Area (E2M2) Blue -> Containment Area (E2M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Containment Area (E2M2) Blue -> Containment Area (E2M2) Main", player), lambda state:
state.has("Containment Area (E2M2) - Blue keycard", player, 1))
set_rule(world.get_entrance("Containment Area (E2M2) Red -> Containment Area (E2M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Containment Area (E2M2) Red -> Containment Area (E2M2) Main", player), lambda state:
state.has("Containment Area (E2M2) - Red keycard", player, 1))
# Refinery (E2M3)
set_rule(world.get_entrance("Hub -> Refinery (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Refinery (E2M3) Main", player), lambda state:
(state.has("Refinery (E2M3)", player, 1) and
state.has("Shotgun", player, 1)) and
(state.has("Chaingun", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(world.get_entrance("Refinery (E2M3) Main -> Refinery (E2M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Refinery (E2M3) Main -> Refinery (E2M3) Blue", player), lambda state:
state.has("Refinery (E2M3) - Blue keycard", player, 1))
set_rule(world.get_entrance("Refinery (E2M3) Blue -> Refinery (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Refinery (E2M3) Blue -> Refinery (E2M3) Main", player), lambda state:
state.has("Refinery (E2M3) - Blue keycard", player, 1))
# Deimos Lab (E2M4)
set_rule(world.get_entrance("Hub -> Deimos Lab (E2M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Deimos Lab (E2M4) Main", player), lambda state:
state.has("Deimos Lab (E2M4)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Plasma gun", player, 1))
set_rule(world.get_entrance("Deimos Lab (E2M4) Main -> Deimos Lab (E2M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Lab (E2M4) Main -> Deimos Lab (E2M4) Blue", player), lambda state:
state.has("Deimos Lab (E2M4) - Blue keycard", player, 1))
set_rule(world.get_entrance("Deimos Lab (E2M4) Blue -> Deimos Lab (E2M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Deimos Lab (E2M4) Blue -> Deimos Lab (E2M4) Yellow", player), lambda state:
state.has("Deimos Lab (E2M4) - Yellow keycard", player, 1))
# Command Center (E2M5)
set_rule(world.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state:
state.has("Command Center (E2M5)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Plasma gun", player, 1))
# Halls of the Damned (E2M6)
set_rule(world.get_entrance("Hub -> Halls of the Damned (E2M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Halls of the Damned (E2M6) Main", player), lambda state:
state.has("Halls of the Damned (E2M6)", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Plasma gun", player, 1))
set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Blue Yellow Red", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Blue Yellow Red", player), lambda state:
state.has("Halls of the Damned (E2M6) - Blue skull key", player, 1) and
state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1) and
state.has("Halls of the Damned (E2M6) - Red skull key", player, 1))
set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) Yellow", player), lambda state:
state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) One way Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Main -> Halls of the Damned (E2M6) One way Yellow", player), lambda state:
state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Halls of the Damned (E2M6) Blue Yellow Red -> Halls of the Damned (E2M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) Blue Yellow Red -> Halls of the Damned (E2M6) Main", player), lambda state:
state.has("Halls of the Damned (E2M6) - Blue skull key", player, 1) and
state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1) and
state.has("Halls of the Damned (E2M6) - Red skull key", player, 1))
set_rule(world.get_entrance("Halls of the Damned (E2M6) One way Yellow -> Halls of the Damned (E2M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Damned (E2M6) One way Yellow -> Halls of the Damned (E2M6) Main", player), lambda state:
state.has("Halls of the Damned (E2M6) - Yellow skull key", player, 1))
# Spawning Vats (E2M7)
set_rule(world.get_entrance("Hub -> Spawning Vats (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Spawning Vats (E2M7) Main", player), lambda state:
state.has("Spawning Vats (E2M7)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Plasma gun", player, 1) and
state.has("Rocket launcher", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Blue", player), lambda state:
state.has("Spawning Vats (E2M7) - Blue keycard", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Entrance Secret", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Entrance Secret", player), lambda state:
state.has("Spawning Vats (E2M7) - Blue keycard", player, 1) and
state.has("Spawning Vats (E2M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red", player), lambda state:
state.has("Spawning Vats (E2M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Yellow", player), lambda state:
state.has("Spawning Vats (E2M7) - Yellow keycard", player, 1))
if pro:
set_rule(world.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red Exit", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Main -> Spawning Vats (E2M7) Red Exit", player), lambda state:
state.has("Rocket launcher", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Yellow -> Spawning Vats (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Yellow -> Spawning Vats (E2M7) Main", player), lambda state:
state.has("Spawning Vats (E2M7) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Red -> Spawning Vats (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Red -> Spawning Vats (E2M7) Main", player), lambda state:
state.has("Spawning Vats (E2M7) - Red keycard", player, 1))
set_rule(world.get_entrance("Spawning Vats (E2M7) Entrance Secret -> Spawning Vats (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Spawning Vats (E2M7) Entrance Secret -> Spawning Vats (E2M7) Main", player), lambda state:
state.has("Spawning Vats (E2M7) - Blue keycard", player, 1) and
state.has("Spawning Vats (E2M7) - Red keycard", player, 1))
# Tower of Babel (E2M8)
set_rule(world.get_entrance("Hub -> Tower of Babel (E2M8) Main", player), lambda state:
state.has("Tower of Babel (E2M8)", player, 1))
set_rule(multiworld.get_entrance("Hub -> Tower of Babel (E2M8) Main", player), lambda state:
(state.has("Tower of Babel (E2M8)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
# Fortress of Mystery (E2M9)
set_rule(world.get_entrance("Hub -> Fortress of Mystery (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Fortress of Mystery (E2M9) Main", player), lambda state:
(state.has("Fortress of Mystery (E2M9)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Blue", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Blue skull key", player, 1))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Red", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Red", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Red skull key", player, 1))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Main -> Fortress of Mystery (E2M9) Yellow", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Blue -> Fortress of Mystery (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Blue -> Fortress of Mystery (E2M9) Main", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Blue skull key", player, 1))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Red -> Fortress of Mystery (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Red -> Fortress of Mystery (E2M9) Main", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Red skull key", player, 1))
set_rule(world.get_entrance("Fortress of Mystery (E2M9) Yellow -> Fortress of Mystery (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Fortress of Mystery (E2M9) Yellow -> Fortress of Mystery (E2M9) Main", player), lambda state:
state.has("Fortress of Mystery (E2M9) - Yellow skull key", player, 1))
def set_episode3_rules(player, world, pro):
def set_episode3_rules(player, multiworld, pro):
# Hell Keep (E3M1)
set_rule(world.get_entrance("Hub -> Hell Keep (E3M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Hell Keep (E3M1) Main", player), lambda state:
state.has("Hell Keep (E3M1)", player, 1))
set_rule(world.get_entrance("Hell Keep (E3M1) Main -> Hell Keep (E3M1) Narrow", player), lambda state:
set_rule(multiworld.get_entrance("Hell Keep (E3M1) Main -> Hell Keep (E3M1) Narrow", player), lambda state:
state.has("Chaingun", player, 1) or
state.has("Shotgun", player, 1))
# Slough of Despair (E3M2)
set_rule(world.get_entrance("Hub -> Slough of Despair (E3M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Slough of Despair (E3M2) Main", player), lambda state:
(state.has("Slough of Despair (E3M2)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1)))
set_rule(world.get_entrance("Slough of Despair (E3M2) Main -> Slough of Despair (E3M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Slough of Despair (E3M2) Main -> Slough of Despair (E3M2) Blue", player), lambda state:
state.has("Slough of Despair (E3M2) - Blue skull key", player, 1))
set_rule(world.get_entrance("Slough of Despair (E3M2) Blue -> Slough of Despair (E3M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Slough of Despair (E3M2) Blue -> Slough of Despair (E3M2) Main", player), lambda state:
state.has("Slough of Despair (E3M2) - Blue skull key", player, 1))
# Pandemonium (E3M3)
set_rule(world.get_entrance("Hub -> Pandemonium (E3M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Pandemonium (E3M3) Main", player), lambda state:
(state.has("Pandemonium (E3M3)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Pandemonium (E3M3) Main -> Pandemonium (E3M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Pandemonium (E3M3) Main -> Pandemonium (E3M3) Blue", player), lambda state:
state.has("Pandemonium (E3M3) - Blue skull key", player, 1))
set_rule(world.get_entrance("Pandemonium (E3M3) Blue -> Pandemonium (E3M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Pandemonium (E3M3) Blue -> Pandemonium (E3M3) Main", player), lambda state:
state.has("Pandemonium (E3M3) - Blue skull key", player, 1))
# House of Pain (E3M4)
set_rule(world.get_entrance("Hub -> House of Pain (E3M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> House of Pain (E3M4) Main", player), lambda state:
(state.has("House of Pain (E3M4)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("House of Pain (E3M4) Main -> House of Pain (E3M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Main -> House of Pain (E3M4) Blue", player), lambda state:
state.has("House of Pain (E3M4) - Blue skull key", player, 1))
set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Main", player), lambda state:
state.has("House of Pain (E3M4) - Blue skull key", player, 1))
set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Yellow", player), lambda state:
state.has("House of Pain (E3M4) - Yellow skull key", player, 1))
set_rule(world.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Red", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Blue -> House of Pain (E3M4) Red", player), lambda state:
state.has("House of Pain (E3M4) - Red skull key", player, 1))
set_rule(world.get_entrance("House of Pain (E3M4) Red -> House of Pain (E3M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Red -> House of Pain (E3M4) Blue", player), lambda state:
state.has("House of Pain (E3M4) - Red skull key", player, 1))
set_rule(world.get_entrance("House of Pain (E3M4) Yellow -> House of Pain (E3M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("House of Pain (E3M4) Yellow -> House of Pain (E3M4) Blue", player), lambda state:
state.has("House of Pain (E3M4) - Yellow skull key", player, 1))
# Unholy Cathedral (E3M5)
set_rule(world.get_entrance("Hub -> Unholy Cathedral (E3M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Unholy Cathedral (E3M5) Main", player), lambda state:
(state.has("Unholy Cathedral (E3M5)", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Yellow", player), lambda state:
state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Main -> Unholy Cathedral (E3M5) Blue", player), lambda state:
state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1))
set_rule(world.get_entrance("Unholy Cathedral (E3M5) Blue -> Unholy Cathedral (E3M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Blue -> Unholy Cathedral (E3M5) Main", player), lambda state:
state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1))
set_rule(world.get_entrance("Unholy Cathedral (E3M5) Yellow -> Unholy Cathedral (E3M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Yellow -> Unholy Cathedral (E3M5) Main", player), lambda state:
state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1))
# Mt. Erebus (E3M6)
set_rule(world.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state:
state.has("Mt. Erebus (E3M6)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1))
set_rule(world.get_entrance("Mt. Erebus (E3M6) Main -> Mt. Erebus (E3M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Mt. Erebus (E3M6) Main -> Mt. Erebus (E3M6) Blue", player), lambda state:
state.has("Mt. Erebus (E3M6) - Blue skull key", player, 1))
set_rule(world.get_entrance("Mt. Erebus (E3M6) Blue -> Mt. Erebus (E3M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Mt. Erebus (E3M6) Blue -> Mt. Erebus (E3M6) Main", player), lambda state:
state.has("Mt. Erebus (E3M6) - Blue skull key", player, 1))
# Limbo (E3M7)
set_rule(world.get_entrance("Hub -> Limbo (E3M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Limbo (E3M7) Main", player), lambda state:
(state.has("Limbo (E3M7)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Red", player), lambda state:
set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Red", player), lambda state:
state.has("Limbo (E3M7) - Red skull key", player, 1))
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Blue", player), lambda state:
state.has("Limbo (E3M7) - Blue skull key", player, 1))
set_rule(world.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Pink", player), lambda state:
set_rule(multiworld.get_entrance("Limbo (E3M7) Main -> Limbo (E3M7) Pink", player), lambda state:
state.has("Limbo (E3M7) - Blue skull key", player, 1))
set_rule(world.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Limbo (E3M7) Red -> Limbo (E3M7) Yellow", player), lambda state:
state.has("Limbo (E3M7) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Limbo (E3M7) Pink -> Limbo (E3M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("Limbo (E3M7) Pink -> Limbo (E3M7) Green", player), lambda state:
state.has("Limbo (E3M7) - Red skull key", player, 1))
# Dis (E3M8)
set_rule(world.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Dis (E3M8) Main", player), lambda state:
(state.has("Dis (E3M8)", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1)) and
@@ -371,129 +376,129 @@ def set_episode3_rules(player, world, pro):
state.has("Rocket launcher", player, 1)))
# Warrens (E3M9)
set_rule(world.get_entrance("Hub -> Warrens (E3M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Warrens (E3M9) Main", player), lambda state:
(state.has("Warrens (E3M9)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Plasma gun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue", player), lambda state:
state.has("Warrens (E3M9) - Blue skull key", player, 1))
set_rule(world.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue trigger", player), lambda state:
set_rule(multiworld.get_entrance("Warrens (E3M9) Main -> Warrens (E3M9) Blue trigger", player), lambda state:
state.has("Warrens (E3M9) - Blue skull key", player, 1))
set_rule(world.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Main", player), lambda state:
state.has("Warrens (E3M9) - Blue skull key", player, 1))
set_rule(world.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Red", player), lambda state:
set_rule(multiworld.get_entrance("Warrens (E3M9) Blue -> Warrens (E3M9) Red", player), lambda state:
state.has("Warrens (E3M9) - Red skull key", player, 1))
def set_episode4_rules(player, world, pro):
def set_episode4_rules(player, multiworld, pro):
# Hell Beneath (E4M1)
set_rule(world.get_entrance("Hub -> Hell Beneath (E4M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Hell Beneath (E4M1) Main", player), lambda state:
state.has("Hell Beneath (E4M1)", player, 1))
set_rule(world.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Red", player), lambda state:
set_rule(multiworld.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Red", player), lambda state:
(state.has("Hell Beneath (E4M1) - Red skull key", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and (state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Hell Beneath (E4M1) Main -> Hell Beneath (E4M1) Blue", player), lambda state:
state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1) or
state.has("Hell Beneath (E4M1) - Blue skull key", player, 1))
# Perfect Hatred (E4M2)
set_rule(world.get_entrance("Hub -> Perfect Hatred (E4M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Perfect Hatred (E4M2) Main", player), lambda state:
(state.has("Perfect Hatred (E4M2)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Blue", player), lambda state:
state.has("Perfect Hatred (E4M2) - Blue skull key", player, 1))
set_rule(world.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Perfect Hatred (E4M2) Main -> Perfect Hatred (E4M2) Yellow", player), lambda state:
state.has("Perfect Hatred (E4M2) - Yellow skull key", player, 1))
# Sever the Wicked (E4M3)
set_rule(world.get_entrance("Hub -> Sever the Wicked (E4M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Sever the Wicked (E4M3) Main", player), lambda state:
(state.has("Sever the Wicked (E4M3)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Sever the Wicked (E4M3) Main -> Sever the Wicked (E4M3) Red", player), lambda state:
set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Main -> Sever the Wicked (E4M3) Red", player), lambda state:
state.has("Sever the Wicked (E4M3) - Red skull key", player, 1))
set_rule(world.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Blue", player), lambda state:
state.has("Sever the Wicked (E4M3) - Blue skull key", player, 1))
set_rule(world.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Red -> Sever the Wicked (E4M3) Main", player), lambda state:
state.has("Sever the Wicked (E4M3) - Red skull key", player, 1))
set_rule(world.get_entrance("Sever the Wicked (E4M3) Blue -> Sever the Wicked (E4M3) Red", player), lambda state:
set_rule(multiworld.get_entrance("Sever the Wicked (E4M3) Blue -> Sever the Wicked (E4M3) Red", player), lambda state:
state.has("Sever the Wicked (E4M3) - Blue skull key", player, 1))
# Unruly Evil (E4M4)
set_rule(world.get_entrance("Hub -> Unruly Evil (E4M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Unruly Evil (E4M4) Main", player), lambda state:
(state.has("Unruly Evil (E4M4)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Unruly Evil (E4M4) Main -> Unruly Evil (E4M4) Red", player), lambda state:
set_rule(multiworld.get_entrance("Unruly Evil (E4M4) Main -> Unruly Evil (E4M4) Red", player), lambda state:
state.has("Unruly Evil (E4M4) - Red skull key", player, 1))
# They Will Repent (E4M5)
set_rule(world.get_entrance("Hub -> They Will Repent (E4M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> They Will Repent (E4M5) Main", player), lambda state:
(state.has("They Will Repent (E4M5)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("They Will Repent (E4M5) Main -> They Will Repent (E4M5) Red", player), lambda state:
set_rule(multiworld.get_entrance("They Will Repent (E4M5) Main -> They Will Repent (E4M5) Red", player), lambda state:
state.has("They Will Repent (E4M5) - Red skull key", player, 1))
set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Main", player), lambda state:
state.has("They Will Repent (E4M5) - Red skull key", player, 1))
set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Yellow", player), lambda state:
state.has("They Will Repent (E4M5) - Yellow skull key", player, 1))
set_rule(world.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("They Will Repent (E4M5) Red -> They Will Repent (E4M5) Blue", player), lambda state:
state.has("They Will Repent (E4M5) - Blue skull key", player, 1))
# Against Thee Wickedly (E4M6)
set_rule(world.get_entrance("Hub -> Against Thee Wickedly (E4M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Against Thee Wickedly (E4M6) Main", player), lambda state:
(state.has("Against Thee Wickedly (E4M6)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Main -> Against Thee Wickedly (E4M6) Blue", player), lambda state:
state.has("Against Thee Wickedly (E4M6) - Blue skull key", player, 1))
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Yellow", player), lambda state:
state.has("Against Thee Wickedly (E4M6) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Red", player), lambda state:
set_rule(multiworld.get_entrance("Against Thee Wickedly (E4M6) Blue -> Against Thee Wickedly (E4M6) Red", player), lambda state:
state.has("Against Thee Wickedly (E4M6) - Red skull key", player, 1))
# And Hell Followed (E4M7)
set_rule(world.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> And Hell Followed (E4M7) Main", player), lambda state:
(state.has("And Hell Followed (E4M7)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Blue", player), lambda state:
state.has("And Hell Followed (E4M7) - Blue skull key", player, 1))
set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Red", player), lambda state:
set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Red", player), lambda state:
state.has("And Hell Followed (E4M7) - Red skull key", player, 1))
set_rule(world.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Main -> And Hell Followed (E4M7) Yellow", player), lambda state:
state.has("And Hell Followed (E4M7) - Yellow skull key", player, 1))
set_rule(world.get_entrance("And Hell Followed (E4M7) Red -> And Hell Followed (E4M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("And Hell Followed (E4M7) Red -> And Hell Followed (E4M7) Main", player), lambda state:
state.has("And Hell Followed (E4M7) - Red skull key", player, 1))
# Unto the Cruel (E4M8)
set_rule(world.get_entrance("Hub -> Unto the Cruel (E4M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Unto the Cruel (E4M8) Main", player), lambda state:
(state.has("Unto the Cruel (E4M8)", player, 1) and
state.has("Chainsaw", player, 1) and
state.has("Shotgun", player, 1) and
@@ -501,37 +506,37 @@ def set_episode4_rules(player, world, pro):
state.has("Rocket launcher", player, 1)) and
(state.has("BFG9000", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Red", player), lambda state:
set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Red", player), lambda state:
state.has("Unto the Cruel (E4M8) - Red skull key", player, 1))
set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Yellow", player), lambda state:
state.has("Unto the Cruel (E4M8) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Orange", player), lambda state:
set_rule(multiworld.get_entrance("Unto the Cruel (E4M8) Main -> Unto the Cruel (E4M8) Orange", player), lambda state:
state.has("Unto the Cruel (E4M8) - Yellow skull key", player, 1) and
state.has("Unto the Cruel (E4M8) - Red skull key", player, 1))
# Fear (E4M9)
set_rule(world.get_entrance("Hub -> Fear (E4M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Fear (E4M9) Main", player), lambda state:
state.has("Fear (E4M9)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Rocket launcher", player, 1) and
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1))
set_rule(world.get_entrance("Fear (E4M9) Main -> Fear (E4M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Fear (E4M9) Main -> Fear (E4M9) Yellow", player), lambda state:
state.has("Fear (E4M9) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Fear (E4M9) Yellow -> Fear (E4M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Fear (E4M9) Yellow -> Fear (E4M9) Main", player), lambda state:
state.has("Fear (E4M9) - Yellow skull key", player, 1))
def set_rules(doom_1993_world: "DOOM1993World", included_episodes, pro):
player = doom_1993_world.player
world = doom_1993_world.multiworld
multiworld = doom_1993_world.multiworld
if included_episodes[0]:
set_episode1_rules(player, world, pro)
set_episode1_rules(player, multiworld, pro)
if included_episodes[1]:
set_episode2_rules(player, world, pro)
set_episode2_rules(player, multiworld, pro)
if included_episodes[2]:
set_episode3_rules(player, world, pro)
set_episode3_rules(player, multiworld, pro)
if included_episodes[3]:
set_episode4_rules(player, world, pro)
set_episode4_rules(player, multiworld, pro)

View File

@@ -2,9 +2,10 @@ import functools
import logging
from typing import Any, Dict, List
from BaseClasses import Entrance, CollectionState, Item, ItemClassification, Location, MultiWorld, Region, Tutorial
from BaseClasses import Entrance, CollectionState, Item, Location, MultiWorld, Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from . import Items, Locations, Maps, Options, Regions, Rules
from . import Items, Locations, Maps, Regions, Rules
from .Options import DOOM1993Options
logger = logging.getLogger("DOOM 1993")
@@ -37,7 +38,8 @@ class DOOM1993World(World):
Developed by id Software, and originally released in 1993, DOOM pioneered and popularized the first-person shooter,
setting a standard for all FPS games.
"""
option_definitions = Options.options
options_dataclass = DOOM1993Options
options: DOOM1993Options
game = "DOOM 1993"
web = DOOM1993Web()
data_version = 3
@@ -78,26 +80,28 @@ class DOOM1993World(World):
"Energy cell pack": 10
}
def __init__(self, world: MultiWorld, player: int):
def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0]
self.location_count = 0
super().__init__(world, player)
super().__init__(multiworld, player)
def get_episode_count(self):
return functools.reduce(lambda count, episode: count + episode, self.included_episodes)
def generate_early(self):
# Cache which episodes are included
for i in range(4):
self.included_episodes[i] = getattr(self.multiworld, f"episode{i + 1}")[self.player].value
self.included_episodes[0] = self.options.episode1.value
self.included_episodes[1] = self.options.episode2.value
self.included_episodes[2] = self.options.episode3.value
self.included_episodes[3] = self.options.episode4.value
# If no episodes selected, select Episode 1
if self.get_episode_count() == 0:
self.included_episodes[0] = 1
def create_regions(self):
pro = getattr(self.multiworld, "pro")[self.player].value
pro = self.options.pro.value
# Main regions
menu_region = Region("Menu", self.player, self.multiworld)
@@ -148,7 +152,7 @@ class DOOM1993World(World):
def completion_rule(self, state: CollectionState):
goal_levels = Maps.map_names
if getattr(self.multiworld, "goal")[self.player].value:
if self.options.goal.value:
goal_levels = self.boss_level_for_espidoes
for map_name in goal_levels:
@@ -167,8 +171,8 @@ class DOOM1993World(World):
return True
def set_rules(self):
pro = getattr(self.multiworld, "pro")[self.player].value
allow_death_logic = getattr(self.multiworld, "allow_death_logic")[self.player].value
pro = self.options.pro.value
allow_death_logic = self.options.allow_death_logic.value
Rules.set_rules(self, self.included_episodes, pro)
self.multiworld.completion_condition[self.player] = lambda state: self.completion_rule(state)
@@ -185,7 +189,7 @@ class DOOM1993World(World):
def create_items(self):
itempool: List[DOOM1993Item] = []
start_with_computer_area_maps: bool = getattr(self.multiworld, "start_with_computer_area_maps")[self.player].value
start_with_computer_area_maps: bool = self.options.start_with_computer_area_maps.value
# Items
for item_id, item in Items.item_table.items():
@@ -225,7 +229,7 @@ class DOOM1993World(World):
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
# Give Computer area maps if option selected
if getattr(self.multiworld, "start_with_computer_area_maps")[self.player].value:
if self.options.start_with_computer_area_maps.value:
for item_id, item_dict in Items.item_table.items():
item_episode = item_dict["episode"]
if item_episode > 0:
@@ -269,7 +273,7 @@ class DOOM1993World(World):
itempool.append(self.create_item(item_name))
def fill_slot_data(self) -> Dict[str, Any]:
slot_data = {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}
slot_data = self.options.as_dict("goal", "difficulty", "random_monsters", "random_pickups", "random_music", "flip_levels", "allow_death_logic", "pro", "start_with_computer_area_maps", "death_link", "reset_level_on_death", "episode1", "episode2", "episode3", "episode4")
# E2M6 and E3M9 each have one way keydoor. You can enter, but required the keycard to get out.
# We used to force place the keycard behind those doors. Limiting the randomness for those items. A change

View File

@@ -15,7 +15,7 @@
1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it.
2. Copy `DOOM.WAD` from your game's installation directory into the newly extracted folder.
You can find the folder in steam by finding the game in your library,
right-clicking it and choosing **Manage -> Browse Local Files**.
right-clicking it and choosing **Manage -> Browse Local Files**. The WAD file is in the `/base/` folder.
## Joining a MultiWorld Game

View File

@@ -7,57 +7,53 @@ if TYPE_CHECKING:
from . import DOOM2World
def set_episode1_rules(player, world, pro):
def set_episode1_rules(player, multiworld, pro):
# Entryway (MAP01)
set_rule(world.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state:
state.has("Entryway (MAP01)", player, 1))
set_rule(world.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Entryway (MAP01) Main", player), lambda state:
state.has("Entryway (MAP01)", player, 1))
# Underhalls (MAP02)
set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state:
state.has("Underhalls (MAP02)", player, 1))
set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state:
state.has("Underhalls (MAP02)", player, 1))
set_rule(world.get_entrance("Hub -> Underhalls (MAP02) Main", player), lambda state:
state.has("Underhalls (MAP02)", player, 1))
set_rule(world.get_entrance("Underhalls (MAP02) Main -> Underhalls (MAP02) Red", player), lambda state:
set_rule(multiworld.get_entrance("Underhalls (MAP02) Main -> Underhalls (MAP02) Red", player), lambda state:
state.has("Underhalls (MAP02) - Red keycard", player, 1))
set_rule(world.get_entrance("Underhalls (MAP02) Blue -> Underhalls (MAP02) Red", player), lambda state:
set_rule(multiworld.get_entrance("Underhalls (MAP02) Blue -> Underhalls (MAP02) Red", player), lambda state:
state.has("Underhalls (MAP02) - Blue keycard", player, 1))
set_rule(world.get_entrance("Underhalls (MAP02) Red -> Underhalls (MAP02) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Underhalls (MAP02) Red -> Underhalls (MAP02) Blue", player), lambda state:
state.has("Underhalls (MAP02) - Blue keycard", player, 1))
# The Gantlet (MAP03)
set_rule(world.get_entrance("Hub -> The Gantlet (MAP03) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Gantlet (MAP03) Main", player), lambda state:
(state.has("The Gantlet (MAP03)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1) or
state.has("Super Shotgun", player, 1)))
set_rule(world.get_entrance("The Gantlet (MAP03) Main -> The Gantlet (MAP03) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Gantlet (MAP03) Main -> The Gantlet (MAP03) Blue", player), lambda state:
state.has("The Gantlet (MAP03) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Gantlet (MAP03) Blue -> The Gantlet (MAP03) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Gantlet (MAP03) Blue -> The Gantlet (MAP03) Red", player), lambda state:
state.has("The Gantlet (MAP03) - Red keycard", player, 1))
# The Focus (MAP04)
set_rule(world.get_entrance("Hub -> The Focus (MAP04) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Focus (MAP04) Main", player), lambda state:
(state.has("The Focus (MAP04)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1) or
state.has("Super Shotgun", player, 1)))
set_rule(world.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Red", player), lambda state:
state.has("The Focus (MAP04) - Red keycard", player, 1))
set_rule(world.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Focus (MAP04) Main -> The Focus (MAP04) Blue", player), lambda state:
state.has("The Focus (MAP04) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Focus (MAP04) Yellow -> The Focus (MAP04) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Focus (MAP04) Yellow -> The Focus (MAP04) Red", player), lambda state:
state.has("The Focus (MAP04) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Yellow", player), lambda state:
state.has("The Focus (MAP04) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Focus (MAP04) Red -> The Focus (MAP04) Main", player), lambda state:
state.has("The Focus (MAP04) - Red keycard", player, 1))
# The Waste Tunnels (MAP05)
set_rule(world.get_entrance("Hub -> The Waste Tunnels (MAP05) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Waste Tunnels (MAP05) Main", player), lambda state:
(state.has("The Waste Tunnels (MAP05)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -65,19 +61,19 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Red", player), lambda state:
state.has("The Waste Tunnels (MAP05) - Red keycard", player, 1))
set_rule(world.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Main -> The Waste Tunnels (MAP05) Blue", player), lambda state:
state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Yellow", player), lambda state:
state.has("The Waste Tunnels (MAP05) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Blue -> The Waste Tunnels (MAP05) Main", player), lambda state:
state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Waste Tunnels (MAP05) Yellow -> The Waste Tunnels (MAP05) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Yellow -> The Waste Tunnels (MAP05) Blue", player), lambda state:
state.has("The Waste Tunnels (MAP05) - Yellow keycard", player, 1))
# The Crusher (MAP06)
set_rule(world.get_entrance("Hub -> The Crusher (MAP06) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Crusher (MAP06) Main", player), lambda state:
(state.has("The Crusher (MAP06)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -85,21 +81,21 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Crusher (MAP06) Main -> The Crusher (MAP06) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Main -> The Crusher (MAP06) Blue", player), lambda state:
state.has("The Crusher (MAP06) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Red", player), lambda state:
state.has("The Crusher (MAP06) - Red keycard", player, 1))
set_rule(world.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Blue -> The Crusher (MAP06) Main", player), lambda state:
state.has("The Crusher (MAP06) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Crusher (MAP06) Yellow -> The Crusher (MAP06) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Yellow -> The Crusher (MAP06) Red", player), lambda state:
state.has("The Crusher (MAP06) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Yellow", player), lambda state:
state.has("The Crusher (MAP06) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Crusher (MAP06) Red -> The Crusher (MAP06) Blue", player), lambda state:
state.has("The Crusher (MAP06) - Red keycard", player, 1))
# Dead Simple (MAP07)
set_rule(world.get_entrance("Hub -> Dead Simple (MAP07) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Dead Simple (MAP07) Main", player), lambda state:
(state.has("Dead Simple (MAP07)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -109,7 +105,7 @@ def set_episode1_rules(player, world, pro):
state.has("BFG9000", player, 1)))
# Tricks and Traps (MAP08)
set_rule(world.get_entrance("Hub -> Tricks and Traps (MAP08) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Tricks and Traps (MAP08) Main", player), lambda state:
(state.has("Tricks and Traps (MAP08)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -117,13 +113,13 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Red", player), lambda state:
set_rule(multiworld.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Red", player), lambda state:
state.has("Tricks and Traps (MAP08) - Red skull key", player, 1))
set_rule(world.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Tricks and Traps (MAP08) Main -> Tricks and Traps (MAP08) Yellow", player), lambda state:
state.has("Tricks and Traps (MAP08) - Yellow skull key", player, 1))
# The Pit (MAP09)
set_rule(world.get_entrance("Hub -> The Pit (MAP09) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Pit (MAP09) Main", player), lambda state:
(state.has("The Pit (MAP09)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -131,15 +127,15 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Yellow", player), lambda state:
state.has("The Pit (MAP09) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Pit (MAP09) Main -> The Pit (MAP09) Blue", player), lambda state:
state.has("The Pit (MAP09) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Pit (MAP09) Yellow -> The Pit (MAP09) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Pit (MAP09) Yellow -> The Pit (MAP09) Main", player), lambda state:
state.has("The Pit (MAP09) - Yellow keycard", player, 1))
# Refueling Base (MAP10)
set_rule(world.get_entrance("Hub -> Refueling Base (MAP10) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Refueling Base (MAP10) Main", player), lambda state:
(state.has("Refueling Base (MAP10)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -147,13 +143,13 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Refueling Base (MAP10) Main -> Refueling Base (MAP10) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Refueling Base (MAP10) Main -> Refueling Base (MAP10) Yellow", player), lambda state:
state.has("Refueling Base (MAP10) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Refueling Base (MAP10) Yellow -> Refueling Base (MAP10) Yellow Blue", player), lambda state:
set_rule(multiworld.get_entrance("Refueling Base (MAP10) Yellow -> Refueling Base (MAP10) Yellow Blue", player), lambda state:
state.has("Refueling Base (MAP10) - Blue keycard", player, 1))
# Circle of Death (MAP11)
set_rule(world.get_entrance("Hub -> Circle of Death (MAP11) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Circle of Death (MAP11) Main", player), lambda state:
(state.has("Circle of Death (MAP11)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -161,15 +157,15 @@ def set_episode1_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Blue", player), lambda state:
state.has("Circle of Death (MAP11) - Blue keycard", player, 1))
set_rule(world.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Red", player), lambda state:
set_rule(multiworld.get_entrance("Circle of Death (MAP11) Main -> Circle of Death (MAP11) Red", player), lambda state:
state.has("Circle of Death (MAP11) - Red keycard", player, 1))
def set_episode2_rules(player, world, pro):
def set_episode2_rules(player, multiworld, pro):
# The Factory (MAP12)
set_rule(world.get_entrance("Hub -> The Factory (MAP12) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Factory (MAP12) Main", player), lambda state:
(state.has("The Factory (MAP12)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -177,13 +173,13 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Yellow", player), lambda state:
state.has("The Factory (MAP12) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Blue", player), lambda state:
state.has("The Factory (MAP12) - Blue keycard", player, 1))
# Downtown (MAP13)
set_rule(world.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state:
(state.has("Downtown (MAP13)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -191,15 +187,15 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Yellow", player), lambda state:
state.has("Downtown (MAP13) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Red", player), lambda state:
set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Red", player), lambda state:
state.has("Downtown (MAP13) - Red keycard", player, 1))
set_rule(world.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Downtown (MAP13) Main -> Downtown (MAP13) Blue", player), lambda state:
state.has("Downtown (MAP13) - Blue keycard", player, 1))
# The Inmost Dens (MAP14)
set_rule(world.get_entrance("Hub -> The Inmost Dens (MAP14) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Inmost Dens (MAP14) Main", player), lambda state:
(state.has("The Inmost Dens (MAP14)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -207,17 +203,17 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Inmost Dens (MAP14) Main -> The Inmost Dens (MAP14) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Main -> The Inmost Dens (MAP14) Red", player), lambda state:
state.has("The Inmost Dens (MAP14) - Red skull key", player, 1))
set_rule(world.get_entrance("The Inmost Dens (MAP14) Blue -> The Inmost Dens (MAP14) Red East", player), lambda state:
set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Blue -> The Inmost Dens (MAP14) Red East", player), lambda state:
state.has("The Inmost Dens (MAP14) - Blue skull key", player, 1))
set_rule(world.get_entrance("The Inmost Dens (MAP14) Red -> The Inmost Dens (MAP14) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Red -> The Inmost Dens (MAP14) Main", player), lambda state:
state.has("The Inmost Dens (MAP14) - Red skull key", player, 1))
set_rule(world.get_entrance("The Inmost Dens (MAP14) Red East -> The Inmost Dens (MAP14) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Inmost Dens (MAP14) Red East -> The Inmost Dens (MAP14) Blue", player), lambda state:
state.has("The Inmost Dens (MAP14) - Blue skull key", player, 1))
# Industrial Zone (MAP15)
set_rule(world.get_entrance("Hub -> Industrial Zone (MAP15) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Industrial Zone (MAP15) Main", player), lambda state:
(state.has("Industrial Zone (MAP15)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -225,17 +221,17 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow East", player), lambda state:
set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow East", player), lambda state:
state.has("Industrial Zone (MAP15) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow West", player), lambda state:
set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Main -> Industrial Zone (MAP15) Yellow West", player), lambda state:
state.has("Industrial Zone (MAP15) - Yellow keycard", player, 1))
set_rule(world.get_entrance("Industrial Zone (MAP15) Blue -> Industrial Zone (MAP15) Yellow East", player), lambda state:
set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Blue -> Industrial Zone (MAP15) Yellow East", player), lambda state:
state.has("Industrial Zone (MAP15) - Blue keycard", player, 1))
set_rule(world.get_entrance("Industrial Zone (MAP15) Yellow East -> Industrial Zone (MAP15) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Industrial Zone (MAP15) Yellow East -> Industrial Zone (MAP15) Blue", player), lambda state:
state.has("Industrial Zone (MAP15) - Blue keycard", player, 1))
# Suburbs (MAP16)
set_rule(world.get_entrance("Hub -> Suburbs (MAP16) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Suburbs (MAP16) Main", player), lambda state:
(state.has("Suburbs (MAP16)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -243,13 +239,13 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Red", player), lambda state:
set_rule(multiworld.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Red", player), lambda state:
state.has("Suburbs (MAP16) - Red skull key", player, 1))
set_rule(world.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Suburbs (MAP16) Main -> Suburbs (MAP16) Blue", player), lambda state:
state.has("Suburbs (MAP16) - Blue skull key", player, 1))
# Tenements (MAP17)
set_rule(world.get_entrance("Hub -> Tenements (MAP17) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Tenements (MAP17) Main", player), lambda state:
(state.has("Tenements (MAP17)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -257,15 +253,15 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Tenements (MAP17) Main -> Tenements (MAP17) Red", player), lambda state:
set_rule(multiworld.get_entrance("Tenements (MAP17) Main -> Tenements (MAP17) Red", player), lambda state:
state.has("Tenements (MAP17) - Red keycard", player, 1))
set_rule(world.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Yellow", player), lambda state:
state.has("Tenements (MAP17) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Tenements (MAP17) Red -> Tenements (MAP17) Blue", player), lambda state:
state.has("Tenements (MAP17) - Blue keycard", player, 1))
# The Courtyard (MAP18)
set_rule(world.get_entrance("Hub -> The Courtyard (MAP18) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Courtyard (MAP18) Main", player), lambda state:
(state.has("The Courtyard (MAP18)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -273,17 +269,17 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Yellow", player), lambda state:
state.has("The Courtyard (MAP18) - Yellow skull key", player, 1))
set_rule(world.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Courtyard (MAP18) Main -> The Courtyard (MAP18) Blue", player), lambda state:
state.has("The Courtyard (MAP18) - Blue skull key", player, 1))
set_rule(world.get_entrance("The Courtyard (MAP18) Blue -> The Courtyard (MAP18) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Courtyard (MAP18) Blue -> The Courtyard (MAP18) Main", player), lambda state:
state.has("The Courtyard (MAP18) - Blue skull key", player, 1))
set_rule(world.get_entrance("The Courtyard (MAP18) Yellow -> The Courtyard (MAP18) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Courtyard (MAP18) Yellow -> The Courtyard (MAP18) Main", player), lambda state:
state.has("The Courtyard (MAP18) - Yellow skull key", player, 1))
# The Citadel (MAP19)
set_rule(world.get_entrance("Hub -> The Citadel (MAP19) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Citadel (MAP19) Main", player), lambda state:
(state.has("The Citadel (MAP19)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -291,15 +287,15 @@ def set_episode2_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("The Citadel (MAP19) Main -> The Citadel (MAP19) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (MAP19) Main -> The Citadel (MAP19) Red", player), lambda state:
(state.has("The Citadel (MAP19) - Red skull key", player, 1)) and (state.has("The Citadel (MAP19) - Blue skull key", player, 1) or
state.has("The Citadel (MAP19) - Yellow skull key", player, 1)))
set_rule(world.get_entrance("The Citadel (MAP19) Red -> The Citadel (MAP19) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (MAP19) Red -> The Citadel (MAP19) Main", player), lambda state:
(state.has("The Citadel (MAP19) - Red skull key", player, 1)) and (state.has("The Citadel (MAP19) - Yellow skull key", player, 1) or
state.has("The Citadel (MAP19) - Blue skull key", player, 1)))
# Gotcha! (MAP20)
set_rule(world.get_entrance("Hub -> Gotcha! (MAP20) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Gotcha! (MAP20) Main", player), lambda state:
(state.has("Gotcha! (MAP20)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -309,9 +305,9 @@ def set_episode2_rules(player, world, pro):
state.has("BFG9000", player, 1)))
def set_episode3_rules(player, world, pro):
def set_episode3_rules(player, multiworld, pro):
# Nirvana (MAP21)
set_rule(world.get_entrance("Hub -> Nirvana (MAP21) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Nirvana (MAP21) Main", player), lambda state:
(state.has("Nirvana (MAP21)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -319,19 +315,19 @@ def set_episode3_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Nirvana (MAP21) Main -> Nirvana (MAP21) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Nirvana (MAP21) Main -> Nirvana (MAP21) Yellow", player), lambda state:
state.has("Nirvana (MAP21) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Main", player), lambda state:
set_rule(multiworld.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Main", player), lambda state:
state.has("Nirvana (MAP21) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Magenta", player), lambda state:
set_rule(multiworld.get_entrance("Nirvana (MAP21) Yellow -> Nirvana (MAP21) Magenta", player), lambda state:
state.has("Nirvana (MAP21) - Red skull key", player, 1) and
state.has("Nirvana (MAP21) - Blue skull key", player, 1))
set_rule(world.get_entrance("Nirvana (MAP21) Magenta -> Nirvana (MAP21) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Nirvana (MAP21) Magenta -> Nirvana (MAP21) Yellow", player), lambda state:
state.has("Nirvana (MAP21) - Red skull key", player, 1) and
state.has("Nirvana (MAP21) - Blue skull key", player, 1))
# The Catacombs (MAP22)
set_rule(world.get_entrance("Hub -> The Catacombs (MAP22) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Catacombs (MAP22) Main", player), lambda state:
(state.has("The Catacombs (MAP22)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -339,15 +335,15 @@ def set_episode3_rules(player, world, pro):
(state.has("BFG9000", player, 1) or
state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(world.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Blue", player), lambda state:
state.has("The Catacombs (MAP22) - Blue skull key", player, 1))
set_rule(world.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (MAP22) Main -> The Catacombs (MAP22) Red", player), lambda state:
state.has("The Catacombs (MAP22) - Red skull key", player, 1))
set_rule(world.get_entrance("The Catacombs (MAP22) Red -> The Catacombs (MAP22) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (MAP22) Red -> The Catacombs (MAP22) Main", player), lambda state:
state.has("The Catacombs (MAP22) - Red skull key", player, 1))
# Barrels o Fun (MAP23)
set_rule(world.get_entrance("Hub -> Barrels o Fun (MAP23) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Barrels o Fun (MAP23) Main", player), lambda state:
(state.has("Barrels o Fun (MAP23)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -355,13 +351,13 @@ def set_episode3_rules(player, world, pro):
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(world.get_entrance("Barrels o Fun (MAP23) Main -> Barrels o Fun (MAP23) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Barrels o Fun (MAP23) Main -> Barrels o Fun (MAP23) Yellow", player), lambda state:
state.has("Barrels o Fun (MAP23) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Barrels o Fun (MAP23) Yellow -> Barrels o Fun (MAP23) Main", player), lambda state:
set_rule(multiworld.get_entrance("Barrels o Fun (MAP23) Yellow -> Barrels o Fun (MAP23) Main", player), lambda state:
state.has("Barrels o Fun (MAP23) - Yellow skull key", player, 1))
# The Chasm (MAP24)
set_rule(world.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state:
state.has("The Chasm (MAP24)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -369,13 +365,13 @@ def set_episode3_rules(player, world, pro):
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(world.get_entrance("The Chasm (MAP24) Main -> The Chasm (MAP24) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (MAP24) Main -> The Chasm (MAP24) Red", player), lambda state:
state.has("The Chasm (MAP24) - Red keycard", player, 1))
set_rule(world.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Main", player), lambda state:
state.has("The Chasm (MAP24) - Red keycard", player, 1))
# Bloodfalls (MAP25)
set_rule(world.get_entrance("Hub -> Bloodfalls (MAP25) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Bloodfalls (MAP25) Main", player), lambda state:
state.has("Bloodfalls (MAP25)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -383,13 +379,13 @@ def set_episode3_rules(player, world, pro):
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(world.get_entrance("Bloodfalls (MAP25) Main -> Bloodfalls (MAP25) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Bloodfalls (MAP25) Main -> Bloodfalls (MAP25) Blue", player), lambda state:
state.has("Bloodfalls (MAP25) - Blue skull key", player, 1))
set_rule(world.get_entrance("Bloodfalls (MAP25) Blue -> Bloodfalls (MAP25) Main", player), lambda state:
set_rule(multiworld.get_entrance("Bloodfalls (MAP25) Blue -> Bloodfalls (MAP25) Main", player), lambda state:
state.has("Bloodfalls (MAP25) - Blue skull key", player, 1))
# The Abandoned Mines (MAP26)
set_rule(world.get_entrance("Hub -> The Abandoned Mines (MAP26) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Abandoned Mines (MAP26) Main", player), lambda state:
state.has("The Abandoned Mines (MAP26)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -397,19 +393,19 @@ def set_episode3_rules(player, world, pro):
state.has("BFG9000", player, 1) and
state.has("Plasma gun", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Yellow", player), lambda state:
state.has("The Abandoned Mines (MAP26) - Yellow keycard", player, 1))
set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Red", player), lambda state:
state.has("The Abandoned Mines (MAP26) - Red keycard", player, 1))
set_rule(world.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Main -> The Abandoned Mines (MAP26) Blue", player), lambda state:
state.has("The Abandoned Mines (MAP26) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Abandoned Mines (MAP26) Blue -> The Abandoned Mines (MAP26) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Blue -> The Abandoned Mines (MAP26) Main", player), lambda state:
state.has("The Abandoned Mines (MAP26) - Blue keycard", player, 1))
set_rule(world.get_entrance("The Abandoned Mines (MAP26) Yellow -> The Abandoned Mines (MAP26) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Abandoned Mines (MAP26) Yellow -> The Abandoned Mines (MAP26) Main", player), lambda state:
state.has("The Abandoned Mines (MAP26) - Yellow keycard", player, 1))
# Monster Condo (MAP27)
set_rule(world.get_entrance("Hub -> Monster Condo (MAP27) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Monster Condo (MAP27) Main", player), lambda state:
state.has("Monster Condo (MAP27)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -417,17 +413,17 @@ def set_episode3_rules(player, world, pro):
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Yellow", player), lambda state:
state.has("Monster Condo (MAP27) - Yellow skull key", player, 1))
set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Red", player), lambda state:
set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Red", player), lambda state:
state.has("Monster Condo (MAP27) - Red skull key", player, 1))
set_rule(world.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Monster Condo (MAP27) Main -> Monster Condo (MAP27) Blue", player), lambda state:
state.has("Monster Condo (MAP27) - Blue skull key", player, 1))
set_rule(world.get_entrance("Monster Condo (MAP27) Red -> Monster Condo (MAP27) Main", player), lambda state:
set_rule(multiworld.get_entrance("Monster Condo (MAP27) Red -> Monster Condo (MAP27) Main", player), lambda state:
state.has("Monster Condo (MAP27) - Red skull key", player, 1))
# The Spirit World (MAP28)
set_rule(world.get_entrance("Hub -> The Spirit World (MAP28) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Spirit World (MAP28) Main", player), lambda state:
state.has("The Spirit World (MAP28)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Rocket launcher", player, 1) and
@@ -435,17 +431,17 @@ def set_episode3_rules(player, world, pro):
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(world.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Yellow", player), lambda state:
state.has("The Spirit World (MAP28) - Yellow skull key", player, 1))
set_rule(world.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Red", player), lambda state:
set_rule(multiworld.get_entrance("The Spirit World (MAP28) Main -> The Spirit World (MAP28) Red", player), lambda state:
state.has("The Spirit World (MAP28) - Red skull key", player, 1))
set_rule(world.get_entrance("The Spirit World (MAP28) Yellow -> The Spirit World (MAP28) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Spirit World (MAP28) Yellow -> The Spirit World (MAP28) Main", player), lambda state:
state.has("The Spirit World (MAP28) - Yellow skull key", player, 1))
set_rule(world.get_entrance("The Spirit World (MAP28) Red -> The Spirit World (MAP28) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Spirit World (MAP28) Red -> The Spirit World (MAP28) Main", player), lambda state:
state.has("The Spirit World (MAP28) - Red skull key", player, 1))
# The Living End (MAP29)
set_rule(world.get_entrance("Hub -> The Living End (MAP29) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Living End (MAP29) Main", player), lambda state:
state.has("The Living End (MAP29)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -455,7 +451,7 @@ def set_episode3_rules(player, world, pro):
state.has("Super Shotgun", player, 1))
# Icon of Sin (MAP30)
set_rule(world.get_entrance("Hub -> Icon of Sin (MAP30) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Icon of Sin (MAP30) Main", player), lambda state:
state.has("Icon of Sin (MAP30)", player, 1) and
state.has("Rocket launcher", player, 1) and
state.has("Shotgun", player, 1) and
@@ -465,9 +461,9 @@ def set_episode3_rules(player, world, pro):
state.has("Super Shotgun", player, 1))
def set_episode4_rules(player, world, pro):
def set_episode4_rules(player, multiworld, pro):
# Wolfenstein2 (MAP31)
set_rule(world.get_entrance("Hub -> Wolfenstein2 (MAP31) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Wolfenstein2 (MAP31) Main", player), lambda state:
(state.has("Wolfenstein2 (MAP31)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -477,7 +473,7 @@ def set_episode4_rules(player, world, pro):
state.has("BFG9000", player, 1)))
# Grosse2 (MAP32)
set_rule(world.get_entrance("Hub -> Grosse2 (MAP32) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Grosse2 (MAP32) Main", player), lambda state:
(state.has("Grosse2 (MAP32)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
@@ -489,13 +485,13 @@ def set_episode4_rules(player, world, pro):
def set_rules(doom_ii_world: "DOOM2World", included_episodes, pro):
player = doom_ii_world.player
world = doom_ii_world.multiworld
multiworld = doom_ii_world.multiworld
if included_episodes[0]:
set_episode1_rules(player, world, pro)
set_episode1_rules(player, multiworld, pro)
if included_episodes[1]:
set_episode2_rules(player, world, pro)
set_episode2_rules(player, multiworld, pro)
if included_episodes[2]:
set_episode3_rules(player, world, pro)
set_episode3_rules(player, multiworld, pro)
if included_episodes[3]:
set_episode4_rules(player, world, pro)
set_episode4_rules(player, multiworld, pro)

View File

@@ -74,11 +74,11 @@ class DOOM2World(World):
"Energy cell pack": 10
}
def __init__(self, world: MultiWorld, player: int):
def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0]
self.location_count = 0
super().__init__(world, player)
super().__init__(multiworld, player)
def get_episode_count(self):
# Don't include 4th, those are secret levels they are additive

View File

@@ -13,7 +13,7 @@
1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it.
2. Copy DOOM2.WAD from your steam install into the extracted folder.
You can find the folder in steam by finding the game in your library,
right clicking it and choosing *Manage→Browse Local Files*.
right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder.
## Joining a MultiWorld Game

View File

@@ -521,7 +521,7 @@ rcon_port = args.rcon_port
rcon_password = args.rcon_password if args.rcon_password else ''.join(
random.choice(string.ascii_letters) for x in range(32))
factorio_server_logger = logging.getLogger("FactorioServer")
options = Utils.get_options()
options = Utils.get_settings()
executable = options["factorio_options"]["executable"]
server_settings = args.server_settings if args.server_settings \
else options["factorio_options"].get("server_settings", None)

View File

@@ -1 +1,2 @@
factorio-rcon-py>=2.0.1
factorio-rcon-py>=2.1.1; python_version >= '3.9'
factorio-rcon-py==2.0.1; python_version <= '3.8'

View File

@@ -12,7 +12,7 @@
- An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other
compatible hardware
- Your legally obtained Final Fantasy Mystic Quest 1.1 ROM file, probably named `Final Fantasy - Mystic Quest (U) (V1.1).sfc`
- Your legally obtained Final Fantasy Mystic Quest NA 1.0 or 1.1 ROM file, probably named `Final Fantasy - Mystic Quest (U) (V1.0).sfc` or `Final Fantasy - Mystic Quest (U) (V1.1).sfc`
The Archipelago community cannot supply you with this.
## Installation Procedures
@@ -54,7 +54,7 @@ validator page: [YAML Validation page](/mysterycheck)
2. You will be presented with a "Seed Info" page.
3. Click the "Create New Room" link.
4. You will be presented with a server page, from which you can download your `.apmq` patch file.
5. Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM
5. Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest ROM
and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM.
7. Since this is a single-player game, you will no longer need the client, so feel free to close it.
@@ -66,7 +66,7 @@ When you join a multiworld game, you will be asked to provide your config file t
the host will provide you with either a link to download your patch file, or with a zip file containing
everyone's patch files. Your patch file should have a `.apmq` extension.
Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest 1.1 ROM
Go to the [FFMQR website](https://ffmqrando.net/Archipelago) and select your Final Fantasy Mystic Quest ROM
and the .apmq file you received, choose optional preferences, and click `Generate` to get your patched ROM.
Manually launch the SNI Client, and run the patched ROM in your chosen software or hardware.

View File

@@ -31,7 +31,8 @@ website: [SublimeText Website](https://www.sublimetext.com)
This program out of the box supports the correct formatting for the YAML file, so you will be able to use the tab key
and get proper highlighting for any potential errors made while editing the file. If using any other text editor you
should ensure your indentation is done correctly with two spaces.
should ensure your indentation is done correctly with two spaces. After editing your YAML file, you can validate it at
the website's [validation page](/check).
A typical YAML file will look like:
@@ -281,7 +282,8 @@ reasonable, but submitting a ChecksFinder alongside another game OR submitting m
OK)
To configure your file to generate multiple worlds, use 3 dashes `---` on an empty line to separate the ending of one
world and the beginning of another world.
world and the beginning of another world. You can also combine multiple files by uploading them to the
[validation page](/check).
### Example

View File

@@ -95,7 +95,9 @@ The following commands are available in the clients that use the CommonClient, f
- `/received` Displays all the items you have received from all players, including yourself.
- `/missing` Displays all the locations along with their current status (checked/missing).
- `/items` Lists all the item names for the current game.
- `/item_groups` Lists all the item group names for the current game.
- `/locations` Lists all the location names for the current game.
- `/location_groups` Lists all the location group names for the current game.
- `/ready` Sends ready status to the server.
- Typing anything that doesn't start with `/` will broadcast a message to all players.

View File

@@ -1266,7 +1266,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 10,
'doom_type': 79,
'region': "The River of Fire (E2M3) Main"},
'region': "The River of Fire (E2M3) Green"},
371179: {'name': 'The River of Fire (E2M3) - Green key',
'episode': 2,
'check_sanity': False,
@@ -1301,7 +1301,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 122,
'doom_type': 2003,
'region': "The River of Fire (E2M3) Main"},
'region': "The River of Fire (E2M3) Green"},
371184: {'name': 'The River of Fire (E2M3) - Hellstaff',
'episode': 2,
'check_sanity': False,
@@ -1364,7 +1364,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 299,
'doom_type': 32,
'region': "The River of Fire (E2M3) Main"},
'region': "The River of Fire (E2M3) Green"},
371193: {'name': 'The River of Fire (E2M3) - Morph Ovum',
'episode': 2,
'check_sanity': False,
@@ -1385,7 +1385,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 413,
'doom_type': 2002,
'region': "The River of Fire (E2M3) Main"},
'region': "The River of Fire (E2M3) Green"},
371196: {'name': 'The River of Fire (E2M3) - Firemace 2',
'episode': 2,
'check_sanity': True,
@@ -2610,7 +2610,7 @@ location_table: Dict[int, LocationDict] = {
'map': 2,
'index': 172,
'doom_type': 33,
'region': "The Cesspool (E3M2) Main"},
'region': "The Cesspool (E3M2) Yellow"},
371371: {'name': 'The Cesspool (E3M2) - Bag of Holding 2',
'episode': 3,
'check_sanity': False,
@@ -4360,7 +4360,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 297,
'doom_type': 2002,
'region': "Ambulatory (E4M3) Green"},
'region': "Ambulatory (E4M3) Green Lock"},
371621: {'name': 'Ambulatory (E4M3) - Firemace 2',
'episode': 4,
'check_sanity': False,
@@ -6040,7 +6040,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 234,
'doom_type': 85,
'region': "Quay (E5M3) Blue"},
'region': "Quay (E5M3) Cyan"},
371861: {'name': 'Quay (E5M3) - Map Scroll',
'episode': 5,
'check_sanity': True,
@@ -6075,7 +6075,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 239,
'doom_type': 86,
'region': "Quay (E5M3) Blue"},
'region': "Quay (E5M3) Cyan"},
371866: {'name': 'Quay (E5M3) - Torch',
'episode': 5,
'check_sanity': False,
@@ -6089,7 +6089,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 242,
'doom_type': 2002,
'region': "Quay (E5M3) Blue"},
'region': "Quay (E5M3) Cyan"},
371868: {'name': 'Quay (E5M3) - Firemace 2',
'episode': 5,
'check_sanity': False,
@@ -6124,7 +6124,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': 247,
'doom_type': 2002,
'region': "Quay (E5M3) Blue"},
'region': "Quay (E5M3) Cyan"},
371873: {'name': 'Quay (E5M3) - Bag of Holding 2',
'episode': 5,
'check_sanity': True,
@@ -6138,7 +6138,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3,
'index': -1,
'doom_type': -1,
'region': "Quay (E5M3) Blue"},
'region': "Quay (E5M3) Cyan"},
371875: {'name': 'Courtyard (E5M4) - Blue key',
'episode': 5,
'check_sanity': False,

View File

@@ -1,6 +1,5 @@
import typing
from Options import AssembleOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool
from Options import PerGameCommonOptions, Choice, Toggle, DeathLink, DefaultOnToggle, StartInventoryPool
from dataclasses import dataclass
class Goal(Choice):
@@ -146,22 +145,22 @@ class Episode5(Toggle):
display_name = "Episode 5"
options: typing.Dict[str, AssembleOptions] = {
"start_inventory_from_pool": StartInventoryPool,
"goal": Goal,
"difficulty": Difficulty,
"random_monsters": RandomMonsters,
"random_pickups": RandomPickups,
"random_music": RandomMusic,
"allow_death_logic": AllowDeathLogic,
"pro": Pro,
"check_sanity": CheckSanity,
"start_with_map_scrolls": StartWithMapScrolls,
"reset_level_on_death": ResetLevelOnDeath,
"death_link": DeathLink,
"episode1": Episode1,
"episode2": Episode2,
"episode3": Episode3,
"episode4": Episode4,
"episode5": Episode5
}
@dataclass
class HereticOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
goal: Goal
difficulty: Difficulty
random_monsters: RandomMonsters
random_pickups: RandomPickups
random_music: RandomMusic
allow_death_logic: AllowDeathLogic
pro: Pro
check_sanity: CheckSanity
start_with_map_scrolls: StartWithMapScrolls
reset_level_on_death: ResetLevelOnDeath
death_link: DeathLink
episode1: Episode1
episode2: Episode2
episode3: Episode3
episode4: Episode4
episode5: Episode5

View File

@@ -604,7 +604,8 @@ regions:List[RegionDict] = [
"connections":[
{"target":"Ambulatory (E4M3) Blue","pro":False},
{"target":"Ambulatory (E4M3) Yellow","pro":False},
{"target":"Ambulatory (E4M3) Green","pro":False}]},
{"target":"Ambulatory (E4M3) Green","pro":False},
{"target":"Ambulatory (E4M3) Green Lock","pro":False}]},
{"name":"Ambulatory (E4M3) Blue",
"connects_to_hub":False,
"episode":4,
@@ -619,6 +620,12 @@ regions:List[RegionDict] = [
"connects_to_hub":False,
"episode":4,
"connections":[{"target":"Ambulatory (E4M3) Main","pro":False}]},
{"name":"Ambulatory (E4M3) Green Lock",
"connects_to_hub":False,
"episode":4,
"connections":[
{"target":"Ambulatory (E4M3) Green","pro":False},
{"target":"Ambulatory (E4M3) Main","pro":False}]},
# Sepulcher (E4M4)
{"name":"Sepulcher (E4M4) Main",
@@ -767,9 +774,7 @@ regions:List[RegionDict] = [
{"name":"Quay (E5M3) Blue",
"connects_to_hub":False,
"episode":5,
"connections":[
{"target":"Quay (E5M3) Green","pro":False},
{"target":"Quay (E5M3) Main","pro":False}]},
"connections":[{"target":"Quay (E5M3) Main","pro":False}]},
{"name":"Quay (E5M3) Yellow",
"connects_to_hub":False,
"episode":5,
@@ -779,7 +784,11 @@ regions:List[RegionDict] = [
"episode":5,
"connections":[
{"target":"Quay (E5M3) Main","pro":False},
{"target":"Quay (E5M3) Blue","pro":False}]},
{"target":"Quay (E5M3) Cyan","pro":False}]},
{"name":"Quay (E5M3) Cyan",
"connects_to_hub":False,
"episode":5,
"connections":[{"target":"Quay (E5M3) Main","pro":False}]},
# Courtyard (E5M4)
{"name":"Courtyard (E5M4) Main",

View File

@@ -7,185 +7,185 @@ if TYPE_CHECKING:
from . import HereticWorld
def set_episode1_rules(player, world, pro):
def set_episode1_rules(player, multiworld, pro):
# The Docks (E1M1)
set_rule(world.get_entrance("Hub -> The Docks (E1M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Docks (E1M1) Main", player), lambda state:
state.has("The Docks (E1M1)", player, 1))
set_rule(world.get_entrance("The Docks (E1M1) Main -> The Docks (E1M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Docks (E1M1) Main -> The Docks (E1M1) Yellow", player), lambda state:
state.has("The Docks (E1M1) - Yellow key", player, 1))
# The Dungeons (E1M2)
set_rule(world.get_entrance("Hub -> The Dungeons (E1M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Dungeons (E1M2) Main", player), lambda state:
(state.has("The Dungeons (E1M2)", player, 1)) and
(state.has("Dragon Claw", player, 1) or
state.has("Ethereal Crossbow", player, 1)))
set_rule(world.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Yellow", player), lambda state:
state.has("The Dungeons (E1M2) - Yellow key", player, 1))
set_rule(world.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Dungeons (E1M2) Main -> The Dungeons (E1M2) Green", player), lambda state:
state.has("The Dungeons (E1M2) - Green key", player, 1))
set_rule(world.get_entrance("The Dungeons (E1M2) Blue -> The Dungeons (E1M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Dungeons (E1M2) Blue -> The Dungeons (E1M2) Yellow", player), lambda state:
state.has("The Dungeons (E1M2) - Blue key", player, 1))
set_rule(world.get_entrance("The Dungeons (E1M2) Yellow -> The Dungeons (E1M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Dungeons (E1M2) Yellow -> The Dungeons (E1M2) Blue", player), lambda state:
state.has("The Dungeons (E1M2) - Blue key", player, 1))
# The Gatehouse (E1M3)
set_rule(world.get_entrance("Hub -> The Gatehouse (E1M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Gatehouse (E1M3) Main", player), lambda state:
(state.has("The Gatehouse (E1M3)", player, 1)) and
(state.has("Ethereal Crossbow", player, 1) or
state.has("Dragon Claw", player, 1)))
set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Yellow", player), lambda state:
state.has("The Gatehouse (E1M3) - Yellow key", player, 1))
set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Sea", player), lambda state:
set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Sea", player), lambda state:
state.has("The Gatehouse (E1M3) - Yellow key", player, 1))
set_rule(world.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Main -> The Gatehouse (E1M3) Green", player), lambda state:
state.has("The Gatehouse (E1M3) - Green key", player, 1))
set_rule(world.get_entrance("The Gatehouse (E1M3) Green -> The Gatehouse (E1M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Gatehouse (E1M3) Green -> The Gatehouse (E1M3) Main", player), lambda state:
state.has("The Gatehouse (E1M3) - Green key", player, 1))
# The Guard Tower (E1M4)
set_rule(world.get_entrance("Hub -> The Guard Tower (E1M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Guard Tower (E1M4) Main", player), lambda state:
(state.has("The Guard Tower (E1M4)", player, 1)) and
(state.has("Ethereal Crossbow", player, 1) or
state.has("Dragon Claw", player, 1)))
set_rule(world.get_entrance("The Guard Tower (E1M4) Main -> The Guard Tower (E1M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Main -> The Guard Tower (E1M4) Yellow", player), lambda state:
state.has("The Guard Tower (E1M4) - Yellow key", player, 1))
set_rule(world.get_entrance("The Guard Tower (E1M4) Yellow -> The Guard Tower (E1M4) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Yellow -> The Guard Tower (E1M4) Green", player), lambda state:
state.has("The Guard Tower (E1M4) - Green key", player, 1))
set_rule(world.get_entrance("The Guard Tower (E1M4) Green -> The Guard Tower (E1M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Guard Tower (E1M4) Green -> The Guard Tower (E1M4) Yellow", player), lambda state:
state.has("The Guard Tower (E1M4) - Green key", player, 1))
# The Citadel (E1M5)
set_rule(world.get_entrance("Hub -> The Citadel (E1M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Citadel (E1M5) Main", player), lambda state:
(state.has("The Citadel (E1M5)", player, 1) and
state.has("Ethereal Crossbow", player, 1)) and
(state.has("Dragon Claw", player, 1) or
state.has("Gauntlets of the Necromancer", player, 1)))
set_rule(world.get_entrance("The Citadel (E1M5) Main -> The Citadel (E1M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (E1M5) Main -> The Citadel (E1M5) Yellow", player), lambda state:
state.has("The Citadel (E1M5) - Yellow key", player, 1))
set_rule(world.get_entrance("The Citadel (E1M5) Blue -> The Citadel (E1M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (E1M5) Blue -> The Citadel (E1M5) Green", player), lambda state:
state.has("The Citadel (E1M5) - Blue key", player, 1))
set_rule(world.get_entrance("The Citadel (E1M5) Yellow -> The Citadel (E1M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (E1M5) Yellow -> The Citadel (E1M5) Green", player), lambda state:
state.has("The Citadel (E1M5) - Green key", player, 1))
set_rule(world.get_entrance("The Citadel (E1M5) Green -> The Citadel (E1M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Citadel (E1M5) Green -> The Citadel (E1M5) Blue", player), lambda state:
state.has("The Citadel (E1M5) - Blue key", player, 1))
# The Cathedral (E1M6)
set_rule(world.get_entrance("Hub -> The Cathedral (E1M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Cathedral (E1M6) Main", player), lambda state:
(state.has("The Cathedral (E1M6)", player, 1) and
state.has("Ethereal Crossbow", player, 1)) and
(state.has("Gauntlets of the Necromancer", player, 1) or
state.has("Dragon Claw", player, 1)))
set_rule(world.get_entrance("The Cathedral (E1M6) Main -> The Cathedral (E1M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Cathedral (E1M6) Main -> The Cathedral (E1M6) Yellow", player), lambda state:
state.has("The Cathedral (E1M6) - Yellow key", player, 1))
set_rule(world.get_entrance("The Cathedral (E1M6) Yellow -> The Cathedral (E1M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Cathedral (E1M6) Yellow -> The Cathedral (E1M6) Green", player), lambda state:
state.has("The Cathedral (E1M6) - Green key", player, 1))
# The Crypts (E1M7)
set_rule(world.get_entrance("Hub -> The Crypts (E1M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Crypts (E1M7) Main", player), lambda state:
(state.has("The Crypts (E1M7)", player, 1) and
state.has("Ethereal Crossbow", player, 1)) and
(state.has("Gauntlets of the Necromancer", player, 1) or
state.has("Dragon Claw", player, 1)))
set_rule(world.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Yellow", player), lambda state:
state.has("The Crypts (E1M7) - Yellow key", player, 1))
set_rule(world.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Crypts (E1M7) Main -> The Crypts (E1M7) Green", player), lambda state:
state.has("The Crypts (E1M7) - Green key", player, 1))
set_rule(world.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Green", player), lambda state:
state.has("The Crypts (E1M7) - Green key", player, 1))
set_rule(world.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Crypts (E1M7) Yellow -> The Crypts (E1M7) Blue", player), lambda state:
state.has("The Crypts (E1M7) - Blue key", player, 1))
set_rule(world.get_entrance("The Crypts (E1M7) Green -> The Crypts (E1M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Crypts (E1M7) Green -> The Crypts (E1M7) Main", player), lambda state:
state.has("The Crypts (E1M7) - Green key", player, 1))
# Hell's Maw (E1M8)
set_rule(world.get_entrance("Hub -> Hell's Maw (E1M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Hell's Maw (E1M8) Main", player), lambda state:
state.has("Hell's Maw (E1M8)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1))
# The Graveyard (E1M9)
set_rule(world.get_entrance("Hub -> The Graveyard (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Graveyard (E1M9) Main", player), lambda state:
state.has("The Graveyard (E1M9)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1))
set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Yellow", player), lambda state:
state.has("The Graveyard (E1M9) - Yellow key", player, 1))
set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Green", player), lambda state:
state.has("The Graveyard (E1M9) - Green key", player, 1))
set_rule(world.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Graveyard (E1M9) Main -> The Graveyard (E1M9) Blue", player), lambda state:
state.has("The Graveyard (E1M9) - Blue key", player, 1))
set_rule(world.get_entrance("The Graveyard (E1M9) Yellow -> The Graveyard (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Graveyard (E1M9) Yellow -> The Graveyard (E1M9) Main", player), lambda state:
state.has("The Graveyard (E1M9) - Yellow key", player, 1))
set_rule(world.get_entrance("The Graveyard (E1M9) Green -> The Graveyard (E1M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Graveyard (E1M9) Green -> The Graveyard (E1M9) Main", player), lambda state:
state.has("The Graveyard (E1M9) - Green key", player, 1))
def set_episode2_rules(player, world, pro):
def set_episode2_rules(player, multiworld, pro):
# The Crater (E2M1)
set_rule(world.get_entrance("Hub -> The Crater (E2M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Crater (E2M1) Main", player), lambda state:
state.has("The Crater (E2M1)", player, 1))
set_rule(world.get_entrance("The Crater (E2M1) Main -> The Crater (E2M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Crater (E2M1) Main -> The Crater (E2M1) Yellow", player), lambda state:
state.has("The Crater (E2M1) - Yellow key", player, 1))
set_rule(world.get_entrance("The Crater (E2M1) Yellow -> The Crater (E2M1) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Crater (E2M1) Yellow -> The Crater (E2M1) Green", player), lambda state:
state.has("The Crater (E2M1) - Green key", player, 1))
set_rule(world.get_entrance("The Crater (E2M1) Green -> The Crater (E2M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Crater (E2M1) Green -> The Crater (E2M1) Yellow", player), lambda state:
state.has("The Crater (E2M1) - Green key", player, 1))
# The Lava Pits (E2M2)
set_rule(world.get_entrance("Hub -> The Lava Pits (E2M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Lava Pits (E2M2) Main", player), lambda state:
(state.has("The Lava Pits (E2M2)", player, 1)) and
(state.has("Ethereal Crossbow", player, 1) or
state.has("Dragon Claw", player, 1)))
set_rule(world.get_entrance("The Lava Pits (E2M2) Main -> The Lava Pits (E2M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Main -> The Lava Pits (E2M2) Yellow", player), lambda state:
state.has("The Lava Pits (E2M2) - Yellow key", player, 1))
set_rule(world.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Green", player), lambda state:
state.has("The Lava Pits (E2M2) - Green key", player, 1))
set_rule(world.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Yellow -> The Lava Pits (E2M2) Main", player), lambda state:
state.has("The Lava Pits (E2M2) - Yellow key", player, 1))
set_rule(world.get_entrance("The Lava Pits (E2M2) Green -> The Lava Pits (E2M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Lava Pits (E2M2) Green -> The Lava Pits (E2M2) Yellow", player), lambda state:
state.has("The Lava Pits (E2M2) - Green key", player, 1))
# The River of Fire (E2M3)
set_rule(world.get_entrance("Hub -> The River of Fire (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The River of Fire (E2M3) Main", player), lambda state:
state.has("The River of Fire (E2M3)", player, 1) and
state.has("Dragon Claw", player, 1) and
state.has("Ethereal Crossbow", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Yellow", player), lambda state:
state.has("The River of Fire (E2M3) - Yellow key", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Blue", player), lambda state:
state.has("The River of Fire (E2M3) - Blue key", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Main -> The River of Fire (E2M3) Green", player), lambda state:
state.has("The River of Fire (E2M3) - Green key", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Blue -> The River of Fire (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Blue -> The River of Fire (E2M3) Main", player), lambda state:
state.has("The River of Fire (E2M3) - Blue key", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Yellow -> The River of Fire (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Yellow -> The River of Fire (E2M3) Main", player), lambda state:
state.has("The River of Fire (E2M3) - Yellow key", player, 1))
set_rule(world.get_entrance("The River of Fire (E2M3) Green -> The River of Fire (E2M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("The River of Fire (E2M3) Green -> The River of Fire (E2M3) Main", player), lambda state:
state.has("The River of Fire (E2M3) - Green key", player, 1))
# The Ice Grotto (E2M4)
set_rule(world.get_entrance("Hub -> The Ice Grotto (E2M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Ice Grotto (E2M4) Main", player), lambda state:
(state.has("The Ice Grotto (E2M4)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1)) and
(state.has("Hellstaff", player, 1) or
state.has("Firemace", player, 1)))
set_rule(world.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Green", player), lambda state:
state.has("The Ice Grotto (E2M4) - Green key", player, 1))
set_rule(world.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Main -> The Ice Grotto (E2M4) Yellow", player), lambda state:
state.has("The Ice Grotto (E2M4) - Yellow key", player, 1))
set_rule(world.get_entrance("The Ice Grotto (E2M4) Blue -> The Ice Grotto (E2M4) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Blue -> The Ice Grotto (E2M4) Green", player), lambda state:
state.has("The Ice Grotto (E2M4) - Blue key", player, 1))
set_rule(world.get_entrance("The Ice Grotto (E2M4) Yellow -> The Ice Grotto (E2M4) Magenta", player), lambda state:
set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Yellow -> The Ice Grotto (E2M4) Magenta", player), lambda state:
state.has("The Ice Grotto (E2M4) - Green key", player, 1) and
state.has("The Ice Grotto (E2M4) - Blue key", player, 1))
set_rule(world.get_entrance("The Ice Grotto (E2M4) Green -> The Ice Grotto (E2M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Ice Grotto (E2M4) Green -> The Ice Grotto (E2M4) Blue", player), lambda state:
state.has("The Ice Grotto (E2M4) - Blue key", player, 1))
# The Catacombs (E2M5)
set_rule(world.get_entrance("Hub -> The Catacombs (E2M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Catacombs (E2M5) Main", player), lambda state:
(state.has("The Catacombs (E2M5)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -193,17 +193,17 @@ def set_episode2_rules(player, world, pro):
(state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("The Catacombs (E2M5) Main -> The Catacombs (E2M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (E2M5) Main -> The Catacombs (E2M5) Yellow", player), lambda state:
state.has("The Catacombs (E2M5) - Yellow key", player, 1))
set_rule(world.get_entrance("The Catacombs (E2M5) Blue -> The Catacombs (E2M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (E2M5) Blue -> The Catacombs (E2M5) Green", player), lambda state:
state.has("The Catacombs (E2M5) - Blue key", player, 1))
set_rule(world.get_entrance("The Catacombs (E2M5) Yellow -> The Catacombs (E2M5) Green", player), lambda state:
state.has("The Catacombs (E2M5) - Yellow key", player, 1))
set_rule(world.get_entrance("The Catacombs (E2M5) Green -> The Catacombs (E2M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Catacombs (E2M5) Yellow -> The Catacombs (E2M5) Green", player), lambda state:
state.has("The Catacombs (E2M5) - Green key", player, 1))
set_rule(multiworld.get_entrance("The Catacombs (E2M5) Green -> The Catacombs (E2M5) Blue", player), lambda state:
state.has("The Catacombs (E2M5) - Blue key", player, 1))
# The Labyrinth (E2M6)
set_rule(world.get_entrance("Hub -> The Labyrinth (E2M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Labyrinth (E2M6) Main", player), lambda state:
(state.has("The Labyrinth (E2M6)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -211,17 +211,17 @@ def set_episode2_rules(player, world, pro):
(state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Blue", player), lambda state:
state.has("The Labyrinth (E2M6) - Blue key", player, 1))
set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Yellow", player), lambda state:
state.has("The Labyrinth (E2M6) - Yellow key", player, 1))
set_rule(world.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Main -> The Labyrinth (E2M6) Green", player), lambda state:
state.has("The Labyrinth (E2M6) - Green key", player, 1))
set_rule(world.get_entrance("The Labyrinth (E2M6) Blue -> The Labyrinth (E2M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Labyrinth (E2M6) Blue -> The Labyrinth (E2M6) Main", player), lambda state:
state.has("The Labyrinth (E2M6) - Blue key", player, 1))
# The Great Hall (E2M7)
set_rule(world.get_entrance("Hub -> The Great Hall (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Great Hall (E2M7) Main", player), lambda state:
(state.has("The Great Hall (E2M7)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -229,19 +229,19 @@ def set_episode2_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Yellow", player), lambda state:
state.has("The Great Hall (E2M7) - Yellow key", player, 1))
set_rule(world.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Great Hall (E2M7) Main -> The Great Hall (E2M7) Green", player), lambda state:
state.has("The Great Hall (E2M7) - Green key", player, 1))
set_rule(world.get_entrance("The Great Hall (E2M7) Blue -> The Great Hall (E2M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Great Hall (E2M7) Blue -> The Great Hall (E2M7) Yellow", player), lambda state:
state.has("The Great Hall (E2M7) - Blue key", player, 1))
set_rule(world.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Blue", player), lambda state:
state.has("The Great Hall (E2M7) - Blue key", player, 1))
set_rule(world.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Great Hall (E2M7) Yellow -> The Great Hall (E2M7) Main", player), lambda state:
state.has("The Great Hall (E2M7) - Yellow key", player, 1))
# The Portals of Chaos (E2M8)
set_rule(world.get_entrance("Hub -> The Portals of Chaos (E2M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Portals of Chaos (E2M8) Main", player), lambda state:
state.has("The Portals of Chaos (E2M8)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -251,7 +251,7 @@ def set_episode2_rules(player, world, pro):
state.has("Hellstaff", player, 1))
# The Glacier (E2M9)
set_rule(world.get_entrance("Hub -> The Glacier (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Glacier (E2M9) Main", player), lambda state:
(state.has("The Glacier (E2M9)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -259,51 +259,51 @@ def set_episode2_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Yellow", player), lambda state:
state.has("The Glacier (E2M9) - Yellow key", player, 1))
set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Blue", player), lambda state:
state.has("The Glacier (E2M9) - Blue key", player, 1))
set_rule(world.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Glacier (E2M9) Main -> The Glacier (E2M9) Green", player), lambda state:
state.has("The Glacier (E2M9) - Green key", player, 1))
set_rule(world.get_entrance("The Glacier (E2M9) Blue -> The Glacier (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Glacier (E2M9) Blue -> The Glacier (E2M9) Main", player), lambda state:
state.has("The Glacier (E2M9) - Blue key", player, 1))
set_rule(world.get_entrance("The Glacier (E2M9) Yellow -> The Glacier (E2M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Glacier (E2M9) Yellow -> The Glacier (E2M9) Main", player), lambda state:
state.has("The Glacier (E2M9) - Yellow key", player, 1))
def set_episode3_rules(player, world, pro):
def set_episode3_rules(player, multiworld, pro):
# The Storehouse (E3M1)
set_rule(world.get_entrance("Hub -> The Storehouse (E3M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Storehouse (E3M1) Main", player), lambda state:
state.has("The Storehouse (E3M1)", player, 1))
set_rule(world.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Yellow", player), lambda state:
state.has("The Storehouse (E3M1) - Yellow key", player, 1))
set_rule(world.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Storehouse (E3M1) Main -> The Storehouse (E3M1) Green", player), lambda state:
state.has("The Storehouse (E3M1) - Green key", player, 1))
set_rule(world.get_entrance("The Storehouse (E3M1) Yellow -> The Storehouse (E3M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Storehouse (E3M1) Yellow -> The Storehouse (E3M1) Main", player), lambda state:
state.has("The Storehouse (E3M1) - Yellow key", player, 1))
set_rule(world.get_entrance("The Storehouse (E3M1) Green -> The Storehouse (E3M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Storehouse (E3M1) Green -> The Storehouse (E3M1) Main", player), lambda state:
state.has("The Storehouse (E3M1) - Green key", player, 1))
# The Cesspool (E3M2)
set_rule(world.get_entrance("Hub -> The Cesspool (E3M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Cesspool (E3M2) Main", player), lambda state:
state.has("The Cesspool (E3M2)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1))
set_rule(world.get_entrance("The Cesspool (E3M2) Main -> The Cesspool (E3M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Cesspool (E3M2) Main -> The Cesspool (E3M2) Yellow", player), lambda state:
state.has("The Cesspool (E3M2) - Yellow key", player, 1))
set_rule(world.get_entrance("The Cesspool (E3M2) Blue -> The Cesspool (E3M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Cesspool (E3M2) Blue -> The Cesspool (E3M2) Green", player), lambda state:
state.has("The Cesspool (E3M2) - Blue key", player, 1))
set_rule(world.get_entrance("The Cesspool (E3M2) Yellow -> The Cesspool (E3M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Cesspool (E3M2) Yellow -> The Cesspool (E3M2) Green", player), lambda state:
state.has("The Cesspool (E3M2) - Green key", player, 1))
set_rule(world.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Blue", player), lambda state:
state.has("The Cesspool (E3M2) - Blue key", player, 1))
set_rule(world.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Cesspool (E3M2) Green -> The Cesspool (E3M2) Yellow", player), lambda state:
state.has("The Cesspool (E3M2) - Green key", player, 1))
# The Confluence (E3M3)
set_rule(world.get_entrance("Hub -> The Confluence (E3M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Confluence (E3M3) Main", player), lambda state:
(state.has("The Confluence (E3M3)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1)) and
@@ -311,19 +311,19 @@ def set_episode3_rules(player, world, pro):
state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Green", player), lambda state:
state.has("The Confluence (E3M3) - Green key", player, 1))
set_rule(world.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Confluence (E3M3) Main -> The Confluence (E3M3) Yellow", player), lambda state:
state.has("The Confluence (E3M3) - Yellow key", player, 1))
set_rule(world.get_entrance("The Confluence (E3M3) Blue -> The Confluence (E3M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Confluence (E3M3) Blue -> The Confluence (E3M3) Green", player), lambda state:
state.has("The Confluence (E3M3) - Blue key", player, 1))
set_rule(world.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Main", player), lambda state:
state.has("The Confluence (E3M3) - Green key", player, 1))
set_rule(world.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Confluence (E3M3) Green -> The Confluence (E3M3) Blue", player), lambda state:
state.has("The Confluence (E3M3) - Blue key", player, 1))
# The Azure Fortress (E3M4)
set_rule(world.get_entrance("Hub -> The Azure Fortress (E3M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Azure Fortress (E3M4) Main", player), lambda state:
(state.has("The Azure Fortress (E3M4)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -331,13 +331,13 @@ def set_episode3_rules(player, world, pro):
(state.has("Firemace", player, 1) or
state.has("Phoenix Rod", player, 1) or
state.has("Gauntlets of the Necromancer", player, 1)))
set_rule(world.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Green", player), lambda state:
state.has("The Azure Fortress (E3M4) - Green key", player, 1))
set_rule(world.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Azure Fortress (E3M4) Main -> The Azure Fortress (E3M4) Yellow", player), lambda state:
state.has("The Azure Fortress (E3M4) - Yellow key", player, 1))
# The Ophidian Lair (E3M5)
set_rule(world.get_entrance("Hub -> The Ophidian Lair (E3M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Ophidian Lair (E3M5) Main", player), lambda state:
(state.has("The Ophidian Lair (E3M5)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -345,13 +345,13 @@ def set_episode3_rules(player, world, pro):
(state.has("Gauntlets of the Necromancer", player, 1) or
state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1)))
set_rule(world.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Yellow", player), lambda state:
state.has("The Ophidian Lair (E3M5) - Yellow key", player, 1))
set_rule(world.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Ophidian Lair (E3M5) Main -> The Ophidian Lair (E3M5) Green", player), lambda state:
state.has("The Ophidian Lair (E3M5) - Green key", player, 1))
# The Halls of Fear (E3M6)
set_rule(world.get_entrance("Hub -> The Halls of Fear (E3M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Halls of Fear (E3M6) Main", player), lambda state:
(state.has("The Halls of Fear (E3M6)", player, 1) and
state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1) and
@@ -359,17 +359,17 @@ def set_episode3_rules(player, world, pro):
state.has("Ethereal Crossbow", player, 1)) and
(state.has("Gauntlets of the Necromancer", player, 1) or
state.has("Phoenix Rod", player, 1)))
set_rule(world.get_entrance("The Halls of Fear (E3M6) Main -> The Halls of Fear (E3M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Main -> The Halls of Fear (E3M6) Yellow", player), lambda state:
state.has("The Halls of Fear (E3M6) - Yellow key", player, 1))
set_rule(world.get_entrance("The Halls of Fear (E3M6) Blue -> The Halls of Fear (E3M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Blue -> The Halls of Fear (E3M6) Yellow", player), lambda state:
state.has("The Halls of Fear (E3M6) - Blue key", player, 1))
set_rule(world.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Blue", player), lambda state:
state.has("The Halls of Fear (E3M6) - Blue key", player, 1))
set_rule(world.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Halls of Fear (E3M6) Yellow -> The Halls of Fear (E3M6) Green", player), lambda state:
state.has("The Halls of Fear (E3M6) - Green key", player, 1))
# The Chasm (E3M7)
set_rule(world.get_entrance("Hub -> The Chasm (E3M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Chasm (E3M7) Main", player), lambda state:
(state.has("The Chasm (E3M7)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -377,19 +377,19 @@ def set_episode3_rules(player, world, pro):
state.has("Hellstaff", player, 1)) and
(state.has("Gauntlets of the Necromancer", player, 1) or
state.has("Phoenix Rod", player, 1)))
set_rule(world.get_entrance("The Chasm (E3M7) Main -> The Chasm (E3M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (E3M7) Main -> The Chasm (E3M7) Yellow", player), lambda state:
state.has("The Chasm (E3M7) - Yellow key", player, 1))
set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Main", player), lambda state:
state.has("The Chasm (E3M7) - Yellow key", player, 1))
set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Green", player), lambda state:
state.has("The Chasm (E3M7) - Green key", player, 1))
set_rule(world.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (E3M7) Yellow -> The Chasm (E3M7) Blue", player), lambda state:
state.has("The Chasm (E3M7) - Blue key", player, 1))
set_rule(world.get_entrance("The Chasm (E3M7) Green -> The Chasm (E3M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Chasm (E3M7) Green -> The Chasm (E3M7) Yellow", player), lambda state:
state.has("The Chasm (E3M7) - Green key", player, 1))
# D'Sparil'S Keep (E3M8)
set_rule(world.get_entrance("Hub -> D'Sparil'S Keep (E3M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> D'Sparil'S Keep (E3M8) Main", player), lambda state:
state.has("D'Sparil'S Keep (E3M8)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -399,7 +399,7 @@ def set_episode3_rules(player, world, pro):
state.has("Hellstaff", player, 1))
# The Aquifier (E3M9)
set_rule(world.get_entrance("Hub -> The Aquifier (E3M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> The Aquifier (E3M9) Main", player), lambda state:
state.has("The Aquifier (E3M9)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -407,23 +407,23 @@ def set_episode3_rules(player, world, pro):
state.has("Phoenix Rod", player, 1) and
state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1))
set_rule(world.get_entrance("The Aquifier (E3M9) Main -> The Aquifier (E3M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Main -> The Aquifier (E3M9) Yellow", player), lambda state:
state.has("The Aquifier (E3M9) - Yellow key", player, 1))
set_rule(world.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Green", player), lambda state:
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Green", player), lambda state:
state.has("The Aquifier (E3M9) - Green key", player, 1))
set_rule(world.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Main", player), lambda state:
state.has("The Aquifier (E3M9) - Yellow key", player, 1))
set_rule(world.get_entrance("The Aquifier (E3M9) Green -> The Aquifier (E3M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Green -> The Aquifier (E3M9) Yellow", player), lambda state:
state.has("The Aquifier (E3M9) - Green key", player, 1))
def set_episode4_rules(player, world, pro):
def set_episode4_rules(player, multiworld, pro):
# Catafalque (E4M1)
set_rule(world.get_entrance("Hub -> Catafalque (E4M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Catafalque (E4M1) Main", player), lambda state:
state.has("Catafalque (E4M1)", player, 1))
set_rule(world.get_entrance("Catafalque (E4M1) Main -> Catafalque (E4M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Catafalque (E4M1) Main -> Catafalque (E4M1) Yellow", player), lambda state:
state.has("Catafalque (E4M1) - Yellow key", player, 1))
set_rule(world.get_entrance("Catafalque (E4M1) Yellow -> Catafalque (E4M1) Green", player), lambda state:
set_rule(multiworld.get_entrance("Catafalque (E4M1) Yellow -> Catafalque (E4M1) Green", player), lambda state:
(state.has("Catafalque (E4M1) - Green key", player, 1)) and (state.has("Ethereal Crossbow", player, 1) or
state.has("Dragon Claw", player, 1) or
state.has("Phoenix Rod", player, 1) or
@@ -431,23 +431,23 @@ def set_episode4_rules(player, world, pro):
state.has("Hellstaff", player, 1)))
# Blockhouse (E4M2)
set_rule(world.get_entrance("Hub -> Blockhouse (E4M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Blockhouse (E4M2) Main", player), lambda state:
state.has("Blockhouse (E4M2)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1))
set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Yellow", player), lambda state:
state.has("Blockhouse (E4M2) - Yellow key", player, 1))
set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Green", player), lambda state:
state.has("Blockhouse (E4M2) - Green key", player, 1))
set_rule(world.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Blockhouse (E4M2) Main -> Blockhouse (E4M2) Blue", player), lambda state:
state.has("Blockhouse (E4M2) - Blue key", player, 1))
set_rule(world.get_entrance("Blockhouse (E4M2) Green -> Blockhouse (E4M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Blockhouse (E4M2) Green -> Blockhouse (E4M2) Main", player), lambda state:
state.has("Blockhouse (E4M2) - Green key", player, 1))
set_rule(world.get_entrance("Blockhouse (E4M2) Blue -> Blockhouse (E4M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Blockhouse (E4M2) Blue -> Blockhouse (E4M2) Main", player), lambda state:
state.has("Blockhouse (E4M2) - Blue key", player, 1))
# Ambulatory (E4M3)
set_rule(world.get_entrance("Hub -> Ambulatory (E4M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Ambulatory (E4M3) Main", player), lambda state:
(state.has("Ambulatory (E4M3)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -455,15 +455,17 @@ def set_episode4_rules(player, world, pro):
(state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Blue", player), lambda state:
state.has("Ambulatory (E4M3) - Blue key", player, 1))
set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Yellow", player), lambda state:
state.has("Ambulatory (E4M3) - Yellow key", player, 1))
set_rule(world.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green", player), lambda state:
state.has("Ambulatory (E4M3) - Green key", player, 1))
set_rule(multiworld.get_entrance("Ambulatory (E4M3) Main -> Ambulatory (E4M3) Green Lock", player), lambda state:
state.has("Ambulatory (E4M3) - Green key", player, 1))
# Sepulcher (E4M4)
set_rule(world.get_entrance("Hub -> Sepulcher (E4M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Sepulcher (E4M4) Main", player), lambda state:
(state.has("Sepulcher (E4M4)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -473,7 +475,7 @@ def set_episode4_rules(player, world, pro):
state.has("Hellstaff", player, 1)))
# Great Stair (E4M5)
set_rule(world.get_entrance("Hub -> Great Stair (E4M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Great Stair (E4M5) Main", player), lambda state:
(state.has("Great Stair (E4M5)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -481,19 +483,19 @@ def set_episode4_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Hellstaff", player, 1) or
state.has("Phoenix Rod", player, 1)))
set_rule(world.get_entrance("Great Stair (E4M5) Main -> Great Stair (E4M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Great Stair (E4M5) Main -> Great Stair (E4M5) Yellow", player), lambda state:
state.has("Great Stair (E4M5) - Yellow key", player, 1))
set_rule(world.get_entrance("Great Stair (E4M5) Blue -> Great Stair (E4M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("Great Stair (E4M5) Blue -> Great Stair (E4M5) Green", player), lambda state:
state.has("Great Stair (E4M5) - Blue key", player, 1))
set_rule(world.get_entrance("Great Stair (E4M5) Yellow -> Great Stair (E4M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("Great Stair (E4M5) Yellow -> Great Stair (E4M5) Green", player), lambda state:
state.has("Great Stair (E4M5) - Green key", player, 1))
set_rule(world.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Blue", player), lambda state:
state.has("Great Stair (E4M5) - Blue key", player, 1))
set_rule(world.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Great Stair (E4M5) Green -> Great Stair (E4M5) Yellow", player), lambda state:
state.has("Great Stair (E4M5) - Green key", player, 1))
# Halls of the Apostate (E4M6)
set_rule(world.get_entrance("Hub -> Halls of the Apostate (E4M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Halls of the Apostate (E4M6) Main", player), lambda state:
(state.has("Halls of the Apostate (E4M6)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -501,19 +503,19 @@ def set_episode4_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Halls of the Apostate (E4M6) Main -> Halls of the Apostate (E4M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Main -> Halls of the Apostate (E4M6) Yellow", player), lambda state:
state.has("Halls of the Apostate (E4M6) - Yellow key", player, 1))
set_rule(world.get_entrance("Halls of the Apostate (E4M6) Blue -> Halls of the Apostate (E4M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Blue -> Halls of the Apostate (E4M6) Green", player), lambda state:
state.has("Halls of the Apostate (E4M6) - Blue key", player, 1))
set_rule(world.get_entrance("Halls of the Apostate (E4M6) Yellow -> Halls of the Apostate (E4M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Yellow -> Halls of the Apostate (E4M6) Green", player), lambda state:
state.has("Halls of the Apostate (E4M6) - Green key", player, 1))
set_rule(world.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Yellow", player), lambda state:
state.has("Halls of the Apostate (E4M6) - Green key", player, 1))
set_rule(world.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Halls of the Apostate (E4M6) Green -> Halls of the Apostate (E4M6) Blue", player), lambda state:
state.has("Halls of the Apostate (E4M6) - Blue key", player, 1))
# Ramparts of Perdition (E4M7)
set_rule(world.get_entrance("Hub -> Ramparts of Perdition (E4M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Ramparts of Perdition (E4M7) Main", player), lambda state:
(state.has("Ramparts of Perdition (E4M7)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -521,21 +523,21 @@ def set_episode4_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Main -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Main -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Yellow key", player, 1))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Blue -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Blue -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Blue key", player, 1))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Main", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Yellow key", player, 1))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Green", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Green key", player, 1))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Yellow -> Ramparts of Perdition (E4M7) Blue", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Blue key", player, 1))
set_rule(world.get_entrance("Ramparts of Perdition (E4M7) Green -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ramparts of Perdition (E4M7) Green -> Ramparts of Perdition (E4M7) Yellow", player), lambda state:
state.has("Ramparts of Perdition (E4M7) - Green key", player, 1))
# Shattered Bridge (E4M8)
set_rule(world.get_entrance("Hub -> Shattered Bridge (E4M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Shattered Bridge (E4M8) Main", player), lambda state:
state.has("Shattered Bridge (E4M8)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -543,13 +545,13 @@ def set_episode4_rules(player, world, pro):
state.has("Phoenix Rod", player, 1) and
state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1))
set_rule(world.get_entrance("Shattered Bridge (E4M8) Main -> Shattered Bridge (E4M8) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Shattered Bridge (E4M8) Main -> Shattered Bridge (E4M8) Yellow", player), lambda state:
state.has("Shattered Bridge (E4M8) - Yellow key", player, 1))
set_rule(world.get_entrance("Shattered Bridge (E4M8) Yellow -> Shattered Bridge (E4M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Shattered Bridge (E4M8) Yellow -> Shattered Bridge (E4M8) Main", player), lambda state:
state.has("Shattered Bridge (E4M8) - Yellow key", player, 1))
# Mausoleum (E4M9)
set_rule(world.get_entrance("Hub -> Mausoleum (E4M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Mausoleum (E4M9) Main", player), lambda state:
(state.has("Mausoleum (E4M9)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
@@ -557,102 +559,100 @@ def set_episode4_rules(player, world, pro):
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Mausoleum (E4M9) Main -> Mausoleum (E4M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Mausoleum (E4M9) Main -> Mausoleum (E4M9) Yellow", player), lambda state:
state.has("Mausoleum (E4M9) - Yellow key", player, 1))
set_rule(world.get_entrance("Mausoleum (E4M9) Yellow -> Mausoleum (E4M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Mausoleum (E4M9) Yellow -> Mausoleum (E4M9) Main", player), lambda state:
state.has("Mausoleum (E4M9) - Yellow key", player, 1))
def set_episode5_rules(player, world, pro):
def set_episode5_rules(player, multiworld, pro):
# Ochre Cliffs (E5M1)
set_rule(world.get_entrance("Hub -> Ochre Cliffs (E5M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Ochre Cliffs (E5M1) Main", player), lambda state:
state.has("Ochre Cliffs (E5M1)", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Main -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Main -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Yellow key", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Blue -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Blue -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Blue key", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Main", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Main", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Yellow key", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Green", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Green", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Green key", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Yellow -> Ochre Cliffs (E5M1) Blue", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Blue key", player, 1))
set_rule(world.get_entrance("Ochre Cliffs (E5M1) Green -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Ochre Cliffs (E5M1) Green -> Ochre Cliffs (E5M1) Yellow", player), lambda state:
state.has("Ochre Cliffs (E5M1) - Green key", player, 1))
# Rapids (E5M2)
set_rule(world.get_entrance("Hub -> Rapids (E5M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Rapids (E5M2) Main", player), lambda state:
state.has("Rapids (E5M2)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1))
set_rule(world.get_entrance("Rapids (E5M2) Main -> Rapids (E5M2) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Rapids (E5M2) Main -> Rapids (E5M2) Yellow", player), lambda state:
state.has("Rapids (E5M2) - Yellow key", player, 1))
set_rule(world.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Main", player), lambda state:
set_rule(multiworld.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Main", player), lambda state:
state.has("Rapids (E5M2) - Yellow key", player, 1))
set_rule(world.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Green", player), lambda state:
set_rule(multiworld.get_entrance("Rapids (E5M2) Yellow -> Rapids (E5M2) Green", player), lambda state:
state.has("Rapids (E5M2) - Green key", player, 1))
# Quay (E5M3)
set_rule(world.get_entrance("Hub -> Quay (E5M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Quay (E5M3) Main", player), lambda state:
(state.has("Quay (E5M3)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1) or
state.has("Firemace", player, 1)))
set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Yellow", player), lambda state:
state.has("Quay (E5M3) - Yellow key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Green", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Green", player), lambda state:
state.has("Quay (E5M3) - Green key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Main -> Quay (E5M3) Blue", player), lambda state:
state.has("Quay (E5M3) - Blue key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Blue -> Quay (E5M3) Green", player), lambda state:
state.has("Quay (E5M3) - Blue key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Yellow -> Quay (E5M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Yellow -> Quay (E5M3) Main", player), lambda state:
state.has("Quay (E5M3) - Yellow key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Main", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Main", player), lambda state:
state.has("Quay (E5M3) - Green key", player, 1))
set_rule(world.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Quay (E5M3) Green -> Quay (E5M3) Cyan", player), lambda state:
state.has("Quay (E5M3) - Blue key", player, 1))
# Courtyard (E5M4)
set_rule(world.get_entrance("Hub -> Courtyard (E5M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Courtyard (E5M4) Main", player), lambda state:
(state.has("Courtyard (E5M4)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Kakis", player), lambda state:
set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Kakis", player), lambda state:
state.has("Courtyard (E5M4) - Yellow key", player, 1) or
state.has("Courtyard (E5M4) - Green key", player, 1))
set_rule(world.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Blue", player), lambda state:
state.has("Courtyard (E5M4) - Blue key", player, 1))
set_rule(world.get_entrance("Courtyard (E5M4) Blue -> Courtyard (E5M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Courtyard (E5M4) Blue -> Courtyard (E5M4) Main", player), lambda state:
state.has("Courtyard (E5M4) - Blue key", player, 1))
set_rule(world.get_entrance("Courtyard (E5M4) Kakis -> Courtyard (E5M4) Main", player), lambda state:
set_rule(multiworld.get_entrance("Courtyard (E5M4) Kakis -> Courtyard (E5M4) Main", player), lambda state:
state.has("Courtyard (E5M4) - Yellow key", player, 1) or
state.has("Courtyard (E5M4) - Green key", player, 1))
# Hydratyr (E5M5)
set_rule(world.get_entrance("Hub -> Hydratyr (E5M5) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Hydratyr (E5M5) Main", player), lambda state:
(state.has("Hydratyr (E5M5)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
state.has("Firemace", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Hydratyr (E5M5) Main -> Hydratyr (E5M5) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Hydratyr (E5M5) Main -> Hydratyr (E5M5) Yellow", player), lambda state:
state.has("Hydratyr (E5M5) - Yellow key", player, 1))
set_rule(world.get_entrance("Hydratyr (E5M5) Blue -> Hydratyr (E5M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("Hydratyr (E5M5) Blue -> Hydratyr (E5M5) Green", player), lambda state:
state.has("Hydratyr (E5M5) - Blue key", player, 1))
set_rule(world.get_entrance("Hydratyr (E5M5) Yellow -> Hydratyr (E5M5) Green", player), lambda state:
set_rule(multiworld.get_entrance("Hydratyr (E5M5) Yellow -> Hydratyr (E5M5) Green", player), lambda state:
state.has("Hydratyr (E5M5) - Green key", player, 1))
set_rule(world.get_entrance("Hydratyr (E5M5) Green -> Hydratyr (E5M5) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Hydratyr (E5M5) Green -> Hydratyr (E5M5) Blue", player), lambda state:
state.has("Hydratyr (E5M5) - Blue key", player, 1))
# Colonnade (E5M6)
set_rule(world.get_entrance("Hub -> Colonnade (E5M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Colonnade (E5M6) Main", player), lambda state:
(state.has("Colonnade (E5M6)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -660,19 +660,19 @@ def set_episode5_rules(player, world, pro):
state.has("Gauntlets of the Necromancer", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Yellow", player), lambda state:
state.has("Colonnade (E5M6) - Yellow key", player, 1))
set_rule(world.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Colonnade (E5M6) Main -> Colonnade (E5M6) Blue", player), lambda state:
state.has("Colonnade (E5M6) - Blue key", player, 1))
set_rule(world.get_entrance("Colonnade (E5M6) Blue -> Colonnade (E5M6) Main", player), lambda state:
set_rule(multiworld.get_entrance("Colonnade (E5M6) Blue -> Colonnade (E5M6) Main", player), lambda state:
state.has("Colonnade (E5M6) - Blue key", player, 1))
set_rule(world.get_entrance("Colonnade (E5M6) Yellow -> Colonnade (E5M6) Green", player), lambda state:
set_rule(multiworld.get_entrance("Colonnade (E5M6) Yellow -> Colonnade (E5M6) Green", player), lambda state:
state.has("Colonnade (E5M6) - Green key", player, 1))
set_rule(world.get_entrance("Colonnade (E5M6) Green -> Colonnade (E5M6) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Colonnade (E5M6) Green -> Colonnade (E5M6) Yellow", player), lambda state:
state.has("Colonnade (E5M6) - Green key", player, 1))
# Foetid Manse (E5M7)
set_rule(world.get_entrance("Hub -> Foetid Manse (E5M7) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Foetid Manse (E5M7) Main", player), lambda state:
(state.has("Foetid Manse (E5M7)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -680,15 +680,15 @@ def set_episode5_rules(player, world, pro):
state.has("Gauntlets of the Necromancer", player, 1)) and
(state.has("Phoenix Rod", player, 1) or
state.has("Hellstaff", player, 1)))
set_rule(world.get_entrance("Foetid Manse (E5M7) Main -> Foetid Manse (E5M7) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Main -> Foetid Manse (E5M7) Yellow", player), lambda state:
state.has("Foetid Manse (E5M7) - Yellow key", player, 1))
set_rule(world.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Green", player), lambda state:
set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Green", player), lambda state:
state.has("Foetid Manse (E5M7) - Green key", player, 1))
set_rule(world.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Foetid Manse (E5M7) Yellow -> Foetid Manse (E5M7) Blue", player), lambda state:
state.has("Foetid Manse (E5M7) - Blue key", player, 1))
# Field of Judgement (E5M8)
set_rule(world.get_entrance("Hub -> Field of Judgement (E5M8) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Field of Judgement (E5M8) Main", player), lambda state:
state.has("Field of Judgement (E5M8)", player, 1) and
state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and
@@ -699,7 +699,7 @@ def set_episode5_rules(player, world, pro):
state.has("Bag of Holding", player, 1))
# Skein of D'Sparil (E5M9)
set_rule(world.get_entrance("Hub -> Skein of D'Sparil (E5M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Hub -> Skein of D'Sparil (E5M9) Main", player), lambda state:
state.has("Skein of D'Sparil (E5M9)", player, 1) and
state.has("Bag of Holding", player, 1) and
state.has("Hellstaff", player, 1) and
@@ -708,29 +708,29 @@ def set_episode5_rules(player, world, pro):
state.has("Ethereal Crossbow", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Firemace", player, 1))
set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Blue", player), lambda state:
set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Blue", player), lambda state:
state.has("Skein of D'Sparil (E5M9) - Blue key", player, 1))
set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Yellow", player), lambda state:
set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Yellow", player), lambda state:
state.has("Skein of D'Sparil (E5M9) - Yellow key", player, 1))
set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Green", player), lambda state:
set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Main -> Skein of D'Sparil (E5M9) Green", player), lambda state:
state.has("Skein of D'Sparil (E5M9) - Green key", player, 1))
set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Yellow -> Skein of D'Sparil (E5M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Yellow -> Skein of D'Sparil (E5M9) Main", player), lambda state:
state.has("Skein of D'Sparil (E5M9) - Yellow key", player, 1))
set_rule(world.get_entrance("Skein of D'Sparil (E5M9) Green -> Skein of D'Sparil (E5M9) Main", player), lambda state:
set_rule(multiworld.get_entrance("Skein of D'Sparil (E5M9) Green -> Skein of D'Sparil (E5M9) Main", player), lambda state:
state.has("Skein of D'Sparil (E5M9) - Green key", player, 1))
def set_rules(heretic_world: "HereticWorld", included_episodes, pro):
player = heretic_world.player
world = heretic_world.multiworld
multiworld = heretic_world.multiworld
if included_episodes[0]:
set_episode1_rules(player, world, pro)
set_episode1_rules(player, multiworld, pro)
if included_episodes[1]:
set_episode2_rules(player, world, pro)
set_episode2_rules(player, multiworld, pro)
if included_episodes[2]:
set_episode3_rules(player, world, pro)
set_episode3_rules(player, multiworld, pro)
if included_episodes[3]:
set_episode4_rules(player, world, pro)
set_episode4_rules(player, multiworld, pro)
if included_episodes[4]:
set_episode5_rules(player, world, pro)
set_episode5_rules(player, multiworld, pro)

View File

@@ -2,9 +2,10 @@ import functools
import logging
from typing import Any, Dict, List, Set
from BaseClasses import Entrance, CollectionState, Item, ItemClassification, Location, MultiWorld, Region, Tutorial
from BaseClasses import Entrance, CollectionState, Item, Location, MultiWorld, Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from . import Items, Locations, Maps, Options, Regions, Rules
from . import Items, Locations, Maps, Regions, Rules
from .Options import HereticOptions
logger = logging.getLogger("Heretic")
@@ -36,7 +37,8 @@ class HereticWorld(World):
"""
Heretic is a dark fantasy first-person shooter video game released in December 1994. It was developed by Raven Software.
"""
option_definitions = Options.options
options_dataclass = HereticOptions
options: HereticOptions
game = "Heretic"
web = HereticWeb()
data_version = 3
@@ -56,7 +58,7 @@ class HereticWorld(World):
"Ochre Cliffs (E5M1)"
]
boss_level_for_espidoes: List[str] = [
boss_level_for_episode: List[str] = [
"Hell's Maw (E1M8)",
"The Portals of Chaos (E2M8)",
"D'Sparil'S Keep (E3M8)",
@@ -77,27 +79,30 @@ class HereticWorld(World):
"Shadowsphere": 1
}
def __init__(self, world: MultiWorld, player: int):
def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0, 0]
self.location_count = 0
super().__init__(world, player)
super().__init__(multiworld, player)
def get_episode_count(self):
return functools.reduce(lambda count, episode: count + episode, self.included_episodes)
def generate_early(self):
# Cache which episodes are included
for i in range(5):
self.included_episodes[i] = getattr(self.multiworld, f"episode{i + 1}")[self.player].value
self.included_episodes[0] = self.options.episode1.value
self.included_episodes[1] = self.options.episode2.value
self.included_episodes[2] = self.options.episode3.value
self.included_episodes[3] = self.options.episode4.value
self.included_episodes[4] = self.options.episode5.value
# If no episodes selected, select Episode 1
if self.get_episode_count() == 0:
self.included_episodes[0] = 1
def create_regions(self):
pro = getattr(self.multiworld, "pro")[self.player].value
check_sanity = getattr(self.multiworld, "check_sanity")[self.player].value
pro = self.options.pro.value
check_sanity = self.options.check_sanity.value
# Main regions
menu_region = Region("Menu", self.player, self.multiworld)
@@ -148,8 +153,8 @@ class HereticWorld(World):
def completion_rule(self, state: CollectionState):
goal_levels = Maps.map_names
if getattr(self.multiworld, "goal")[self.player].value:
goal_levels = self.boss_level_for_espidoes
if self.options.goal.value:
goal_levels = self.boss_level_for_episode
for map_name in goal_levels:
if map_name + " - Exit" not in self.location_name_to_id:
@@ -167,8 +172,8 @@ class HereticWorld(World):
return True
def set_rules(self):
pro = getattr(self.multiworld, "pro")[self.player].value
allow_death_logic = getattr(self.multiworld, "allow_death_logic")[self.player].value
pro = self.options.pro.value
allow_death_logic = self.options.allow_death_logic.value
Rules.set_rules(self, self.included_episodes, pro)
self.multiworld.completion_condition[self.player] = lambda state: self.completion_rule(state)
@@ -185,7 +190,7 @@ class HereticWorld(World):
def create_items(self):
itempool: List[HereticItem] = []
start_with_map_scrolls: bool = getattr(self.multiworld, "start_with_map_scrolls")[self.player].value
start_with_map_scrolls: bool = self.options.start_with_map_scrolls.value
# Items
for item_id, item in Items.item_table.items():
@@ -225,7 +230,7 @@ class HereticWorld(World):
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
# Give Computer area maps if option selected
if getattr(self.multiworld, "start_with_map_scrolls")[self.player].value:
if self.options.start_with_map_scrolls.value:
for item_id, item_dict in Items.item_table.items():
item_episode = item_dict["episode"]
if item_episode > 0:
@@ -275,7 +280,7 @@ class HereticWorld(World):
itempool.append(self.create_item(item_name))
def fill_slot_data(self) -> Dict[str, Any]:
slot_data = self.options.as_dict("difficulty", "random_monsters", "random_pickups", "random_music", "allow_death_logic", "pro", "death_link", "reset_level_on_death", "check_sanity")
slot_data = self.options.as_dict("goal", "difficulty", "random_monsters", "random_pickups", "random_music", "allow_death_logic", "pro", "death_link", "reset_level_on_death", "check_sanity")
# Make sure we send proper episode settings
slot_data["episode1"] = self.included_episodes[0]

View File

@@ -13,7 +13,7 @@
1. Download [APDOOM.zip](https://github.com/Daivuk/apdoom/releases) and extract it.
2. Copy HERETIC.WAD from your steam install into the extracted folder.
You can find the folder in steam by finding the game in your library,
right clicking it and choosing *Manage→Browse Local Files*.
right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder.
## Joining a MultiWorld Game

View File

@@ -199,8 +199,14 @@ class HKWorld(World):
self.multiworld.regions.append(menu_region)
# wp_exclusions = self.white_palace_exclusions()
# check for any goal that godhome events are relevant to
all_event_names = event_names.copy()
if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]:
from .GodhomeData import godhome_event_names
all_event_names.update(set(godhome_event_names))
# Link regions
for event_name in event_names:
for event_name in all_event_names:
#if event_name in wp_exclusions:
# continue
loc = HKLocation(self.player, event_name, None, menu_region)
@@ -307,12 +313,6 @@ class HKWorld(World):
randomized = True
_add("Elevator_Pass", "Elevator_Pass", randomized)
# check for any goal that godhome events are relevant to
if self.multiworld.Goal[self.player] in [Goal.option_godhome, Goal.option_godhome_flower]:
from .GodhomeData import godhome_event_names
for item_name in godhome_event_names:
_add(item_name, item_name, False)
for shop, locations in self.created_multi_locations.items():
for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value):
loc = self.create_location(shop)

View File

@@ -1,4 +1,5 @@
from Options import Choice, Toggle, DefaultOnToggle, DeathLink
from dataclasses import dataclass
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions
class PartyShuffle(Toggle):
"""Shuffles party members into the pool.
@@ -31,11 +32,11 @@ class Hylics2DeathLink(DeathLink):
Note that this also includes death by using the PERISH gesture.
Can be toggled via in-game console command "/deathlink"."""
hylics2_options = {
"party_shuffle": PartyShuffle,
"gesture_shuffle" : GestureShuffle,
"medallion_shuffle" : MedallionShuffle,
"random_start" : RandomStart,
"extra_items_in_logic": ExtraLogic,
"death_link": Hylics2DeathLink
}
@dataclass
class Hylics2Options(PerGameCommonOptions):
party_shuffle: PartyShuffle
gesture_shuffle: GestureShuffle
medallion_shuffle: MedallionShuffle
random_start: RandomStart
extra_items_in_logic: ExtraLogic
death_link: Hylics2DeathLink

View File

@@ -129,6 +129,12 @@ def set_rules(hylics2world):
world = hylics2world.multiworld
player = hylics2world.player
extra = hylics2world.options.extra_items_in_logic
party = hylics2world.options.party_shuffle
medallion = hylics2world.options.medallion_shuffle
random_start = hylics2world.options.random_start
start_location = hylics2world.start_location
# Afterlife
add_rule(world.get_location("Afterlife: TV", player),
lambda state: cave_key(state, player))
@@ -346,7 +352,7 @@ def set_rules(hylics2world):
lambda state: upper_chamber_key(state, player))
# extra rules if Extra Items in Logic is enabled
if world.extra_items_in_logic[player]:
if extra:
for i in world.get_region("Foglast", player).entrances:
add_rule(i, lambda state: charge_up(state, player))
for i in world.get_region("Sage Airship", player).entrances:
@@ -368,7 +374,7 @@ def set_rules(hylics2world):
))
# extra rules if Shuffle Party Members is enabled
if world.party_shuffle[player]:
if party:
for i in world.get_region("Arcade Island", player).entrances:
add_rule(i, lambda state: party_3(state, player))
for i in world.get_region("Foglast", player).entrances:
@@ -406,7 +412,7 @@ def set_rules(hylics2world):
lambda state: party_3(state, player))
# extra rules if Shuffle Red Medallions is enabled
if world.medallion_shuffle[player]:
if medallion:
add_rule(world.get_location("New Muldul: Upper House Medallion", player),
lambda state: upper_house_key(state, player))
add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
@@ -461,7 +467,7 @@ def set_rules(hylics2world):
lambda state: upper_chamber_key(state, player))
# extra rules if Shuffle Red Medallions and Party Shuffle are enabled
if world.party_shuffle[player] and world.medallion_shuffle[player]:
if party and medallion:
add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
lambda state: party_3(state, player))
add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
@@ -493,8 +499,7 @@ def set_rules(hylics2world):
add_rule(i, lambda state: enter_hylemxylem(state, player))
# random start logic (default)
if ((not world.random_start[player]) or \
(world.random_start[player] and hylics2world.start_location == "Waynehouse")):
if not random_start or random_start and start_location == "Waynehouse":
# entrances
for i in world.get_region("Viewax", player).entrances:
add_rule(i, lambda state: (
@@ -509,7 +514,7 @@ def set_rules(hylics2world):
add_rule(i, lambda state: airship(state, player))
# random start logic (Viewax's Edifice)
elif (world.random_start[player] and hylics2world.start_location == "Viewax's Edifice"):
elif random_start and start_location == "Viewax's Edifice":
for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: (
air_dash(state, player)
@@ -540,7 +545,7 @@ def set_rules(hylics2world):
add_rule(i, lambda state: airship(state, player))
# random start logic (TV Island)
elif (world.random_start[player] and hylics2world.start_location == "TV Island"):
elif random_start and start_location == "TV Island":
for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: airship(state, player))
for i in world.get_region("New Muldul", player).entrances:
@@ -559,7 +564,7 @@ def set_rules(hylics2world):
add_rule(i, lambda state: airship(state, player))
# random start logic (Shield Facility)
elif (world.random_start[player] and hylics2world.start_location == "Shield Facility"):
elif random_start and start_location == "Shield Facility":
for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: airship(state, player))
for i in world.get_region("New Muldul", player).entrances:

View File

@@ -1,7 +1,8 @@
from typing import Dict, List, Any
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
from worlds.generic.Rules import set_rule
from . import Exits, Items, Locations, Options, Rules
from . import Exits, Items, Locations, Rules
from .Options import Hylics2Options
from worlds.AutoWorld import WebWorld, World
@@ -32,7 +33,9 @@ class Hylics2World(World):
item_name_to_id = {data["name"]: item_id for item_id, data in all_items.items()}
location_name_to_id = {data["name"]: loc_id for loc_id, data in all_locations.items()}
option_definitions = Options.hylics2_options
options_dataclass = Hylics2Options
options: Hylics2Options
data_version = 3
@@ -55,7 +58,7 @@ class Hylics2World(World):
# set random starting location if option is enabled
def generate_early(self):
if self.multiworld.random_start[self.player]:
if self.options.random_start:
i = self.random.randint(0, 3)
if i == 0:
self.start_location = "Waynehouse"
@@ -77,17 +80,17 @@ class Hylics2World(World):
pool.append(self.create_item(item["name"]))
# add party members if option is enabled
if self.multiworld.party_shuffle[self.player]:
if self.options.party_shuffle:
for item in Items.party_item_table.values():
pool.append(self.create_item(item["name"]))
# handle gesture shuffle
if not self.multiworld.gesture_shuffle[self.player]: # add gestures to pool like normal
if not self.options.gesture_shuffle: # add gestures to pool like normal
for item in Items.gesture_item_table.values():
pool.append(self.create_item(item["name"]))
# add '10 Bones' items if medallion shuffle is enabled
if self.multiworld.medallion_shuffle[self.player]:
if self.options.medallion_shuffle:
for item in Items.medallion_item_table.values():
for _ in range(item["count"]):
pool.append(self.create_item(item["name"]))
@@ -98,7 +101,7 @@ class Hylics2World(World):
def pre_fill(self):
# handle gesture shuffle options
if self.multiworld.gesture_shuffle[self.player] == 2: # vanilla locations
if self.options.gesture_shuffle == 2: # vanilla locations
gestures = Items.gesture_item_table
self.multiworld.get_location("Waynehouse: TV", self.player)\
.place_locked_item(self.create_item("POROMER BLEB"))
@@ -119,13 +122,13 @@ class Hylics2World(World):
self.multiworld.get_location("Sage Airship: TV", self.player)\
.place_locked_item(self.create_item("BOMBO - GENESIS"))
elif self.multiworld.gesture_shuffle[self.player] == 1: # TVs only
elif self.options.gesture_shuffle == 1: # TVs only
gestures = [gesture["name"] for gesture in Items.gesture_item_table.values()]
tvs = [tv["name"] for tv in Locations.tv_location_table.values()]
# if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
# placed at Sage Airship: TV or Foglast: TV
if self.multiworld.extra_items_in_logic[self.player]:
if self.options.extra_items_in_logic:
tv = self.random.choice(tvs)
while tv == "Sage Airship: TV" or tv == "Foglast: TV":
tv = self.random.choice(tvs)
@@ -144,11 +147,11 @@ class Hylics2World(World):
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {
"party_shuffle": self.multiworld.party_shuffle[self.player].value,
"medallion_shuffle": self.multiworld.medallion_shuffle[self.player].value,
"random_start" : self.multiworld.random_start[self.player].value,
"party_shuffle": self.options.party_shuffle.value,
"medallion_shuffle": self.options.medallion_shuffle.value,
"random_start" : self.options.random_start.value,
"start_location" : self.start_location,
"death_link": self.multiworld.death_link[self.player].value
"death_link": self.options.death_link.value
}
return slot_data
@@ -186,7 +189,7 @@ class Hylics2World(World):
# create entrance and connect it to parent and destination regions
ent = Entrance(self.player, f"{reg.name} {k}", reg)
reg.exits.append(ent)
if k == "New Game" and self.multiworld.random_start[self.player]:
if k == "New Game" and self.options.random_start:
if self.start_location == "Waynehouse":
ent.connect(region_table[2])
elif self.start_location == "Viewax's Edifice":
@@ -209,13 +212,13 @@ class Hylics2World(World):
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
# add party member locations if option is enabled
if self.multiworld.party_shuffle[self.player]:
if self.options.party_shuffle:
for i, data in Locations.party_location_table.items():
region_table[data["region"]].locations\
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
# add medallion locations if option is enabled
if self.multiworld.medallion_shuffle[self.player]:
if self.options.medallion_shuffle:
for i, data in Locations.medallion_location_table.items():
region_table[data["region"]].locations\
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))

Some files were not shown because too many files have changed in this diff Show More